Browse Source

Merge pull request #3051 from nocodb/fix/gui-v2-misc

fix(gui-v2): Misc issues
pull/3093/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
242db97fa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      packages/nc-gui-v2/components.d.ts
  2. 20
      packages/nc-gui-v2/components/dashboard/settings/AppStore.vue
  3. 16
      packages/nc-gui-v2/components/dashboard/settings/Metadata.vue
  4. 14
      packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue
  5. 30
      packages/nc-gui-v2/components/dashboard/settings/app-store/AppInstall.vue
  6. 17
      packages/nc-gui-v2/components/dlg/AirtableImport.vue
  7. 43
      packages/nc-gui-v2/components/dlg/QuickImport.vue
  8. 15
      packages/nc-gui-v2/components/dlg/TableRename.vue
  9. 13
      packages/nc-gui-v2/components/smartsheet-column/FormulaOptions.vue
  10. 23
      packages/nc-gui-v2/components/smartsheet-header/Menu.vue
  11. 28
      packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue
  12. 16
      packages/nc-gui-v2/components/smartsheet-toolbar/SharedViewList.vue
  13. 24
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue
  14. 18
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/LockMenu.vue
  15. 28
      packages/nc-gui-v2/components/tabs/auth/ApiTokenManagement.vue
  16. 40
      packages/nc-gui-v2/components/tabs/auth/UserManagement.vue
  17. 39
      packages/nc-gui-v2/components/tabs/auth/user-management/ShareBase.vue
  18. 20
      packages/nc-gui-v2/components/tabs/auth/user-management/UsersModal.vue
  19. 470
      packages/nc-gui-v2/components/template/Editor.vue
  20. 20
      packages/nc-gui-v2/components/template/utils.ts
  21. 21
      packages/nc-gui-v2/components/webhook/Editor.vue
  22. 17
      packages/nc-gui-v2/components/webhook/List.vue
  23. 12
      packages/nc-gui-v2/components/webhook/Test.vue
  24. 24
      packages/nc-gui-v2/composables/useColumnCreateStore.ts
  25. 18
      packages/nc-gui-v2/composables/useTable.ts
  26. 4
      packages/nc-gui-v2/composables/useViewData.ts
  27. 4
      packages/nc-gui-v2/composables/useViews.ts
  28. 15
      packages/nc-gui-v2/package-lock.json
  29. 3
      packages/nc-gui-v2/package.json
  30. 11
      packages/nc-gui-v2/pages/index/index.vue
  31. 17
      packages/nc-gui-v2/pages/project/index/[id].vue
  32. 18
      packages/nc-gui-v2/pages/project/index/create-external.vue
  33. 8
      packages/nc-gui-v2/pages/project/index/create.vue
  34. 10
      packages/nc-gui-v2/pages/projects/index.vue
  35. 8
      packages/nc-gui-v2/plugins/toastr.ts
  36. 1
      packages/nc-gui-v2/utils/index.ts
  37. 4
      packages/nc-gui-v2/utils/parsers/ExcelTemplateAdapter.ts

10
packages/nc-gui-v2/components.d.ts vendored

@ -24,7 +24,6 @@ declare module '@vue/runtime-core' {
AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AImage: typeof import('ant-design-vue/es')['Image']
AInput: typeof import('ant-design-vue/es')['Input']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
@ -42,7 +41,6 @@ declare module '@vue/runtime-core' {
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARate: typeof import('ant-design-vue/es')['Rate']
@ -68,20 +66,16 @@ declare module '@vue/runtime-core' {
CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default']
IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default']
MaterialSymbolsArrowBackRounded: typeof import('~icons/material-symbols/arrow-back-rounded')['default']
MaterialSymbolsArrowForwardRounded: typeof import('~icons/material-symbols/arrow-forward-rounded')['default']
MaterialSymbolsAttachFile: typeof import('~icons/material-symbols/attach-file')['default']
MaterialSymbolsChevronLeftRounded: typeof import('~icons/material-symbols/chevron-left-rounded')['default']
MaterialSymbolsChevronRightRounded: typeof import('~icons/material-symbols/chevron-right-rounded')['default']
MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default']
MaterialSymbolsMenu: typeof import('~icons/material-symbols/menu')['default']
MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default']
MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default']
MdiAccountGroup: typeof import('~icons/mdi/account-group')['default']
MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default']
MdiApi: typeof import('~icons/mdi/api')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowExpandIcon: typeof import('~icons/mdi/arrow-expand-icon')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiAt: typeof import('~icons/mdi/at')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default']
@ -94,10 +88,8 @@ declare module '@vue/runtime-core' {
MdiCloseThick: typeof import('~icons/mdi/close-thick')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiContentSave: typeof import('~icons/mdi/content-save')['default']
MdiDatabase: typeof import('~icons/mdi/database')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiDownload: typeof import('~icons/mdi/download')['default']
MdiDrag: typeof import('~icons/mdi/drag')['default']
@ -123,11 +115,9 @@ declare module '@vue/runtime-core' {
MdiNotebookCheckOutline: typeof import('~icons/mdi/notebook-check-outline')['default']
MdiNumeric: typeof import('~icons/mdi/numeric')['default']
MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default']
MdiOperator: typeof import('~icons/mdi/operator')['default']
MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default']
MdiReload: typeof import('~icons/mdi/reload')['default']
MdiSearch: typeof import('~icons/mdi/search')['default']
MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiStar: typeof import('~icons/mdi/star')['default']

20
packages/nc-gui-v2/components/dashboard/settings/AppStore.vue

@ -1,12 +1,12 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import AppInstall from './app-store/AppInstall.vue'
import MdiEditIcon from '~icons/ic/round-edit'
import MdiCloseCircleIcon from '~icons/mdi/close-circle-outline'
import MdiPlusIcon from '~icons/mdi/plus'
import { extractSdkResponseErrorMsg } from '~/utils'
const { $api, $e } = useNuxtApp()
const toast = useToast()
let apps = $ref<null | Array<any>>(null)
let showPluginUninstallModal = $ref(false)
@ -22,9 +22,10 @@ const fetchPluginApps = async () => {
tags: p.tags ? p.tags.split(',') : [],
parsedInput: p.input && JSON.parse(p.input),
}))
} catch (e) {
console.error(e)
toast.error('Something went wrong')
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -34,12 +35,15 @@ const resetPlugin = async () => {
input: undefined,
active: false,
})
toast.success('Plugin uninstalled successfully')
notification.success({
message: 'Plugin uninstalled successfully',
})
showPluginUninstallModal = false
await fetchPluginApps()
} catch (e: any) {
console.log(e)
toast.error(e.message)
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:appstore:reset', { app: pluginApp.title })

16
packages/nc-gui-v2/components/dashboard/settings/Metadata.vue

@ -1,12 +1,12 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { h, useNuxtApp, useProject } from '#imports'
import MdiReload from '~icons/mdi/reload'
import MdiDatabaseSync from '~icons/mdi/database-sync'
import { extractSdkResponseErrorMsg } from '~/utils'
const { $api } = useNuxtApp()
const { project } = useProject()
const toast = useToast()
let isLoading = $ref(false)
let isDifferent = $ref(false)
@ -38,14 +38,14 @@ async function syncMetaDiff() {
isLoading = true
await $api.project.metaDiffSync(project.value.id)
toast.info(`Table metadata recreated successfully`)
notification.info({
message: 'Table metadata recreated successfully',
})
await loadMetaDiff()
} catch (e: any) {
if (e.response?.status === 402) {
toast.info(e.message)
} else {
toast.error(e.message)
}
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
isLoading = false
}

14
packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue

@ -1,14 +1,12 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { viewIcons } from '~/utils'
import { notification } from 'ant-design-vue'
import { extractSdkResponseErrorMsg, viewIcons } from '~/utils'
import { computed, h, useNuxtApp, useProject } from '#imports'
const { $api, $e } = useNuxtApp()
const { project } = useProject()
const toast = useToast()
const roles = $ref<string[]>(['editor', 'commenter', 'viewer'])
let isLoading = $ref(false)
@ -49,9 +47,13 @@ async function saveUIAcl() {
project.value.id,
tables.filter((t) => t.edited),
)
toast.success('Updated UI ACL for tables successfully')
notification.success({
message: 'Updated UI ACL for tables successfully',
})
} catch (e: any) {
toast.error(e?.message)
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:proj-meta:ui-acl')
}

30
packages/nc-gui-v2/components/dashboard/settings/app-store/AppInstall.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import type { PluginType } from 'nocodb-sdk'
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
import CloseIcon from '~icons/material-symbols/close-rounded'
@ -25,8 +25,6 @@ enum Action {
Test = 'test',
}
const toast = useToast()
const { $api } = useNuxtApp()
const formRef = ref()
@ -55,10 +53,13 @@ const saveSettings = async () => {
})
emits('saved')
toast.success(plugin?.formDetails.msgOnInstall || 'Plugin settings saved successfully')
} catch (_e: any) {
const e = await extractSdkResponseErrorMsg(_e)
toast.error(e.message)
notification.success({
message: plugin?.formDetails.msgOnInstall || 'Plugin settings saved successfully',
})
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
loadingAction = null
}
@ -76,13 +77,18 @@ const testSettings = async () => {
})
if (res) {
toast.success('Successfully tested plugin settings')
notification.success({
message: 'Successfully tested plugin settings',
})
} else {
toast.info('Invalid credentials')
notification.info({
message: 'Invalid credentials',
})
}
} catch (_e: any) {
const e = await extractSdkResponseErrorMsg(_e)
toast.error(e.message)
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
loadingAction = null
}

17
packages/nc-gui-v2/components/dlg/AirtableImport.vue

@ -1,11 +1,9 @@
<script setup lang="ts">
import io from 'socket.io-client'
import type { Socket } from 'socket.io-client'
import { Form } from 'ant-design-vue'
import { Form, notification } from 'ant-design-vue'
import type { Card as AntCard } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { fieldRequiredValidator } from '~/utils/validation'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { extractSdkResponseErrorMsg, fieldRequiredValidator } from '~/utils'
import MdiCloseCircleOutlineIcon from '~icons/mdi/close-circle-outline'
import MdiCurrencyUsdIcon from '~icons/mdi/currency-usd'
import MdiLoadingIcon from '~icons/mdi/loading'
@ -23,8 +21,6 @@ const baseURL = 'http://localhost:8080' // this.$axios.defaults.baseURL
const { $state } = useNuxtApp()
const toast = useToast()
const { project, loadTables } = useProject()
const showGoToDashboardButton = ref(false)
@ -107,7 +103,9 @@ async function createOrUpdate() {
syncSource.value = data
}
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -158,8 +156,9 @@ async function sync() {
},
})
} catch (e: any) {
console.log(e)
toast.error(e)
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}

43
packages/nc-gui-v2/components/dlg/QuickImport.vue

@ -1,6 +1,5 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { Form } from 'ant-design-vue'
import { Form, notification } from 'ant-design-vue'
import type { TableType } from 'nocodb-sdk'
import type { UploadChangeParam } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
@ -16,9 +15,10 @@ import { useProject } from '#imports'
interface Props {
modelValue: boolean
importType: 'csv' | 'json' | 'excel'
importOnly: boolean
}
const { importType, ...rest } = defineProps<Props>()
const { importType, importOnly, ...rest } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
@ -26,8 +26,6 @@ const { t } = useI18n()
const { tables } = useProject()
const toast = useToast()
const activeKey = ref('uploadTab')
const jsonEditorRef = ref()
@ -40,6 +38,8 @@ const templateData = ref()
const importData = ref()
const importColumns = ref([])
const templateEditorModal = ref(false)
const useForm = Form.useForm
@ -132,7 +132,9 @@ async function handlePreImport() {
await validate()
await parseAndExtractData(importState.url, '')
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
} else if (activeKey.value === 'jsonEditorTab') {
await parseAndExtractData(JSON.stringify(importState.jsonEditor), '')
@ -145,7 +147,9 @@ async function handleImport() {
loading.value = true
await templateEditorRef.value.importTemplate()
} catch (e: any) {
return toast.error(await extractSdkResponseErrorMsg(e))
return notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
loading.value = false
}
@ -156,9 +160,12 @@ async function parseAndExtractData(val: any, name: string) {
try {
templateData.value = null
importData.value = null
importColumns.value = []
const templateGenerator: any = getAdapter(name, val)
if (!templateGenerator) {
toast.error('Template Generator cannot be found!')
notification.error({
message: 'Template Generator cannot be found!',
})
return
}
await templateGenerator.init()
@ -166,16 +173,21 @@ async function parseAndExtractData(val: any, name: string) {
templateData.value = templateGenerator.getTemplate()
templateData.value.tables[0].table_name = populateUniqueTableName()
importData.value = templateGenerator.getData()
if (importOnly) importColumns.value = templateGenerator.getColumns()
templateEditorModal.value = true
} catch (e: any) {
console.log(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
function rejectDrop(fileList: any[]) {
fileList.map((file) => {
return toast.error(`Failed to upload file ${file.name}`)
return notification.error({
message: `Failed to upload file ${file.name}`,
})
})
}
@ -192,9 +204,13 @@ function handleChange(info: UploadChangeParam) {
reader.readAsArrayBuffer(info.file.originFileObj)
}
if (status === 'done') {
toast.success(`Uploaded file ${info.file.name} successfully`)
notification.success({
message: `Uploaded file ${info.file.name} successfully`,
})
} else if (status === 'error') {
toast.error(`Failed to upload file ${info.file.name}`)
notification.error({
message: `Failed to upload file ${info.file.name}`,
})
}
}
@ -265,7 +281,10 @@ function getAdapter(name: string, val: any) {
ref="templateEditorRef"
:project-template="templateData"
:import-data="importData"
:import-columns="importColumns"
:import-only="importOnly"
:quick-import-type="importType"
:max-rows-to-parse="importState.parserConfig.maxRowsToParse"
@import="handleImport"
/>
<a-tabs v-else v-model:activeKey="activeKey" hide-add type="editable-card" :tab-position="top">

15
packages/nc-gui-v2/components/dlg/TableRename.vue

@ -1,11 +1,9 @@
<script setup lang="ts">
import { watchEffect } from '@vue/runtime-core'
import { Form } from 'ant-design-vue'
import { Form, notification } from 'ant-design-vue'
import type { TableType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { useProject, useTabs } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { validateTableName } from '~/utils/validation'
import { extractSdkResponseErrorMsg, validateTableName } from '~/utils'
import { useNuxtApp } from '#app'
interface Props {
@ -16,7 +14,6 @@ interface Props {
const { modelValue = false, tableMeta } = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'updated'])
const { $e, $api } = useNuxtApp()
const toast = useToast()
const dialogShow = computed({
get() {
return modelValue
@ -79,11 +76,15 @@ const renameTable = async () => {
dialogShow.value = false
loadTables()
updateTab({ id: tableMeta?.id }, { title: formState.title })
toast.success('Table renamed successfully')
notification.success({
message: 'Table renamed successfully',
})
$e('a:table:rename')
dialogShow.value = false
} catch (e) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
loading = false
}

13
packages/nc-gui-v2/components/smartsheet-column/FormulaOptions.vue

@ -581,19 +581,6 @@ function scrollToSelectedOption() {
})
}
function getFormulaTypeName(type: string) {
switch (type) {
case 'function':
return 'Function'
case 'op':
return 'Operator'
case 'column':
return 'Column'
default:
return ''
}
}
// set default value
formState.value.formula_raw = (column?.value?.colOptions as Record<string, any>)?.formula_raw || ''

23
packages/nc-gui-v2/components/smartsheet-header/Menu.vue

@ -1,26 +1,28 @@
<script lang="ts" setup>
import { Modal } from 'ant-design-vue'
import { Modal, notification } from 'ant-design-vue'
import { inject } from 'vue'
import { useI18n } from 'vue-i18n'
import { useToast } from 'vue-toastification'
import { useNuxtApp } from '#app'
import { useMetas } from '#imports'
import { ColumnInj, MetaInj } from '~/context'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { extractSdkResponseErrorMsg } from '~/utils'
import MdiEditIcon from '~icons/mdi/pencil'
import MdiStarIcon from '~icons/mdi/star'
import MdiDeleteIcon from '~icons/mdi/delete-outline'
import MdiMenuDownIcon from '~icons/mdi/menu-down'
const { virtual = false } = defineProps<{ virtual?: boolean }>()
const editColumnDropdown = ref(false)
const column = inject(ColumnInj)
const meta = inject(MetaInj)
const { $api } = useNuxtApp()
const { t } = useI18n()
const toast = useToast()
const { getMeta } = useMetas()
const deleteColumn = () =>
@ -34,7 +36,9 @@ const deleteColumn = () =>
await $api.dbTableColumn.delete(column?.value?.id as string)
getMeta(meta?.value?.id as string, true)
} catch (e) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
},
})
@ -43,10 +47,13 @@ const setAsPrimaryValue = async () => {
try {
await $api.dbTableColumn.primaryColumnSet(column?.value?.id as string)
getMeta(meta?.value?.id as string, true)
toast.success('Successfully updated as primary column')
notification.success({
message: 'Successfully updated as primary column',
})
} catch (e) {
console.log(e)
toast.error('Failed to update primary column')
notification.error({
message: 'Failed to update primary column',
})
}
}

28
packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue

@ -2,23 +2,25 @@
import { useClipboard } from '@vueuse/core'
import { ViewTypes } from 'nocodb-sdk'
import { computed } from 'vue'
import { message } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { message, notification } from 'ant-design-vue'
import { useNuxtApp } from '#app'
import { useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { extractSdkResponseErrorMsg } from '~/utils'
import MdiOpenInNewIcon from '~icons/mdi/open-in-new'
import MdiCopyIcon from '~icons/mdi/content-copy'
const { view, $api } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard()
const { $e } = useNuxtApp()
const toast = useToast()
const { dashboardUrl } = useDashboard()
let showShareModel = $ref(false)
const passwordProtected = $ref(false)
const shared = ref()
const allowCSVDownload = computed({
@ -67,10 +69,14 @@ async function saveAllowCSVDownload() {
await $api.dbViewShare.update(shared.value.id, {
meta,
} as any)
notification.success({
message: 'Successfully updated',
})
toast.success('Successfully updated')
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
if (allowCSVDownload?.value) {
$e('a:view:share:enable-csv-download')
@ -84,9 +90,13 @@ const saveShareLinkPassword = async () => {
await $api.dbViewShare.update(shared.value.id, {
password: shared.value.password,
})
toast.success('Successfully updated')
} catch (e) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.success({
message: 'Successfully updated',
})
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:view:share:enable-pwd')

16
packages/nc-gui-v2/components/smartsheet-toolbar/SharedViewList.vue

@ -1,8 +1,7 @@
<script lang="ts" setup>
import { useClipboard } from '@vueuse/core'
import { ViewTypes } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { message } from 'ant-design-vue'
import { message, notification } from 'ant-design-vue'
import { onMounted, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiVisibilityOnIcon from '~icons/mdi/visibility'
@ -20,8 +19,9 @@ interface SharedViewType {
}
const { $api, meta } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard()
const toast = useToast()
const { dashboardUrl } = useDashboard()
const sharedViewList = ref<SharedViewType[]>()
@ -80,10 +80,14 @@ const copyLink = (view: SharedViewType) => {
const deleteLink = async (id: string) => {
try {
await $api.dbViewShare.delete(id)
toast.success('Deleted shared view successfully')
notification.success({
message: 'Deleted shared view successfully',
})
await loadSharedViewsList()
} catch (e) {
toast.error(await extractSdkResponseErrorMsg(e))
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
</script>

24
packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue

@ -14,12 +14,18 @@ const { $e } = useNuxtApp()
const isView = ref(false)
let showApiSnippet = $ref(false)
const showWebhookDrawer = ref(false)
function onApiSnippet() {
// get API snippet
showApiSnippet = true
$e('a:view:api-snippet')
}
function onWebhooks() {
showWebhookDrawer.value = true
}
function onOpenModal(type: ViewTypes, title = '') {
emits('openModal', { type, title })
}
@ -33,7 +39,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<template #title>
{{ $t('msg.info.onlyCreator') }}
</template>
<MdiShieldLockOutline class="text-pink-500" />
<mdi-shield-lock-outline class="text-pink-500" />
</a-tooltip>
</h3>
@ -50,7 +56,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" />
<MdiPlus class="group-hover:text-primary" />
<mdi-plus class="group-hover:text-primary" />
</div>
</a-tooltip>
</a-menu-item>
@ -68,7 +74,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" />
<MdiPlus class="group-hover:text-primary" />
<mdi-plus class="group-hover:text-primary" />
</div>
</a-tooltip>
</a-menu-item>
@ -86,7 +92,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" />
<MdiPlus class="group-hover:text-primary" />
<mdi-plus class="group-hover:text-primary" />
</div>
</a-tooltip>
</a-menu-item>
@ -97,14 +103,14 @@ function onOpenModal(type: ViewTypes, title = '') {
class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded !bg-primary text-white transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease"
@click="onApiSnippet"
>
<MdiXml />Get API Snippet
<mdi-xml />Get API Snippet
</button>
<button
class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded border transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease"
@click="onApiSnippet"
@click="onWebhooks"
>
<MdiHook />{{ $t('objects.webhooks') }}
<mdi-hook />{{ $t('objects.webhooks') }}
</button>
</div>
@ -135,10 +141,12 @@ function onOpenModal(type: ViewTypes, title = '') {
class="group flex items-center gap-2 w-full mx-3 px-4 py-2 rounded-l !bg-primary text-white transform translate-x-4 hover:(translate-x-0 shadow-lg !opacity-100) transition duration-150 ease"
@click.stop
>
<MdiCardsHeart class="text-red-500" />
<mdi-cards-heart class="text-red-500" />
{{ $t('activity.sponsorUs') }}
</a>
</template>
</general-flipping-card>
<WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
</a-menu>
</template>

18
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/LockMenu.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { computed, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
@ -13,14 +13,16 @@ enum LockType {
}
const { view, $api } = useSmartsheetStoreOrThrow()
const { $e } = useNuxtApp()
const toast = useToast()
function changeLockType(type: LockType) {
async function changeLockType(type: LockType) {
$e('a:grid:lockmenu', { lockType: type })
if (type === 'personal') {
return toast.info('Coming soon', { timeout: 3000 })
return notification.info({
message: 'Coming soon',
})
}
try {
;(view.value as any).lock_type = type
@ -28,9 +30,13 @@ function changeLockType(type: LockType) {
lock_type: type,
})
toast.success(`Successfully Switched to ${type} view`, { timeout: 3000 })
notification.success({
message: `Successfully Switched to ${type} view`,
})
} catch (e: any) {
toast.error(extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}

28
packages/nc-gui-v2/components/tabs/auth/ApiTokenManagement.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ApiTokenType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { useClipboard } from '@vueuse/core'
import KebabIcon from '~icons/ic/baseline-more-vert'
import MdiPlusIcon from '~icons/mdi/plus'
@ -10,9 +10,7 @@ import VisibilityOpenIcon from '~icons/material-symbols/visibility'
import VisibilityCloseIcon from '~icons/material-symbols/visibility-off'
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
import MdiContentCopyIcon from '~icons/mdi/content-copy'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
const toast = useToast()
import { extractSdkResponseErrorMsg } from '~/utils'
interface ApiToken extends ApiTokenType {
show?: boolean
@ -42,7 +40,9 @@ const copyToken = (token: string | undefined) => {
if (!token) return
copy(token)
toast.info('Copied to clipboard')
notification.info({
message: 'Copied to clipboard',
})
$e('c:api-token:copy')
}
@ -53,12 +53,15 @@ const generateToken = async () => {
await $api.apiToken.create(project.id, selectedTokenData)
showNewTokenModal = false
toast.success('Token generated successfully')
notification.success({
message: 'Token generated successfullyd',
})
selectedTokenData = {}
await loadApiTokens()
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:api-token:generate')
@ -70,12 +73,15 @@ const deleteToken = async () => {
await $api.apiToken.delete(project.id, selectedTokenData.token)
toast.success('Token deleted successfully')
notification.success({
message: 'Token deleted successfully',
})
await loadApiTokens()
showDeleteTokenModal = false
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:api-token:delete')

40
packages/nc-gui-v2/components/tabs/auth/UserManagement.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useClipboard, watchDebounced } from '@vueuse/core'
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import UsersModal from './user-management/UsersModal.vue'
import FeedbackForm from './user-management/FeedbackForm.vue'
import KebabIcon from '~icons/ic/baseline-more-vert'
@ -17,7 +17,6 @@ import MdiContentCopyIcon from '~icons/mdi/content-copy'
import MdiEmailSendIcon from '~icons/mdi/email-arrow-right-outline'
import RolesIcon from '~icons/mdi/drama-masks'
import type { User } from '~/lib/types'
const toast = useToast()
const { $api, $e } = useNuxtApp()
const { project } = useProject()
@ -53,8 +52,9 @@ const loadUsers = async (page = currentPage, limit = currentLimit) => {
totalRows = response.users.pageInfo.totalRows ?? 0
users = response.users.list as User[]
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -63,11 +63,14 @@ const inviteUser = async (user: User) => {
if (!project.value?.id) return
await $api.auth.projectUserAdd(project.value.id, user)
toast.success('Successfully added user to project')
notification.success({
message: 'Successfully added user to project',
})
await loadUsers()
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:user:add')
@ -78,13 +81,17 @@ const deleteUser = async () => {
if (!project.value?.id || !selectedUser?.id) return
await $api.auth.projectUserRemove(project.value.id, selectedUser.id)
toast.success('Successfully deleted user from project')
notification.success({
message: 'Successfully deleted user from project',
})
await loadUsers()
showUserDeleteModal = false
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
showUserDeleteModal = false
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
}
$e('a:user:delete')
@ -110,11 +117,14 @@ const resendInvite = async (user: User) => {
try {
await $api.auth.projectUserResendInvite(project.value.id, user.id)
toast.success('Invite email sent successfully')
notification.success({
message: 'Invite email sent successfully',
})
await loadUsers()
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:user:resend-invite')
@ -126,7 +136,9 @@ const copyInviteUrl = (user: User) => {
const getInviteUrl = (token: string) => `${dashboardUrl}/user/authentication/signup/${token}`
copy(getInviteUrl(user.invite_token))
toast.success('Invite url copied to clipboard')
notification.success({
message: 'Invite url copied to clipboard',
})
}
onMounted(async () => {

39
packages/nc-gui-v2/components/tabs/auth/user-management/ShareBase.vue

@ -1,11 +1,8 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { onMounted, useClipboard, useNuxtApp, useProject } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
const toast = useToast()
const { dashboardUrl } = $(useDashboard())
interface ShareBase {
uuid?: string
url?: string
@ -17,6 +14,8 @@ enum ShareBaseRole {
Viewer = 'viewer',
}
const { dashboardUrl } = $(useDashboard())
const { $api, $e } = useNuxtApp()
let base = $ref<null | ShareBase>(null)
@ -40,9 +39,9 @@ const loadBase = async () => {
role: res.roles,
}
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -57,9 +56,9 @@ const createShareBase = async (role = ShareBaseRole.Viewer) => {
base = res ?? {}
base!.role = role
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:shared-base:enable', { role })
@ -72,8 +71,9 @@ const disableSharedBase = async () => {
await $api.project.sharedBaseDisable(project.value.id)
base = null
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:shared-base:disable')
@ -91,9 +91,9 @@ const recreate = async () => {
base = { ...newBase, role: base?.role }
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:shared-base:recreate')
@ -104,7 +104,9 @@ const copyUrl = async () => {
await copy(url)
toast.success('Copied shareable base url to clipboard!')
notification.success({
message: 'Copied shareable base url to clipboard!',
})
$e('c:shared-base:copy-url')
}
@ -127,7 +129,10 @@ frameborder="0"
width="100%"
height="700"
style="background: transparent; border: 1px solid #ddd"></iframe>`)
toast.success('Copied embeddable html code!')
notification.success({
message: 'Copied embeddable html code!',
})
$e('c:shared-base:copy-embed-frame')
}

20
packages/nc-gui-v2/components/tabs/auth/user-management/UsersModal.vue

@ -1,6 +1,5 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { Form } from 'ant-design-vue'
import { Form, notification } from 'ant-design-vue'
import { useClipboard } from '@vueuse/core'
import ShareBase from './ShareBase.vue'
import SendIcon from '~icons/material-symbols/send-outline'
@ -9,9 +8,7 @@ import MidAccountIcon from '~icons/mdi/account-outline'
import ContentCopyIcon from '~icons/mdi/content-copy'
import type { User } from '~/lib/types'
import { ProjectRole } from '~/lib/enums'
import { projectRoleTagColors, projectRoles } from '~/utils/userUtils'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { isEmail } from '~/utils/validation'
import { extractSdkResponseErrorMsg, isEmail, projectRoleTagColors, projectRoles } from '~/utils'
interface Props {
show: boolean
@ -26,7 +23,6 @@ interface Users {
const { show, selectedUser } = defineProps<Props>()
const emit = defineEmits(['closed', 'reload'])
const toast = useToast()
const { project } = useProject()
const { $api, $e } = useNuxtApp()
@ -93,10 +89,14 @@ const saveUser = async () => {
})
usersData.invitationToken = res.invite_token
}
toast.success('Successfully updated the user details')
notification.success({
message: 'Successfully updated the user details',
})
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -108,7 +108,9 @@ const copyUrl = async () => {
if (!inviteUrl) return
copy(inviteUrl)
toast.success('Copied shareable base url to clipboard!')
notification.success({
message: 'Copied shareable base url to clipboard!',
})
$e('c:shared-base:copy-url')
}

470
packages/nc-gui-v2/components/template/Editor.vue

@ -1,24 +1,19 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import type { ColumnType, TableType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { Form } from 'ant-design-vue'
import { tableColumns } from './utils'
import { Form, notification } from 'ant-design-vue'
import { srcDestMappingColumns, tableColumns } from './utils'
import { computed, onMounted } from '#imports'
import MdiTableIcon from '~icons/mdi/table'
import MdiStringIcon from '~icons/mdi/alpha-a'
import MdiLongTextIcon from '~icons/mdi/text'
import MdiNumericIcon from '~icons/mdi/numeric'
import MdiPlusIcon from '~icons/mdi/plus'
import MdiKeyStarIcon from '~icons/mdi/key-star'
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { fieldRequiredValidator } from '~/utils/validation'
import { extractSdkResponseErrorMsg, fieldRequiredValidator, getUIDTIcon } from '~/utils'
import { MetaInj, ReloadViewDataHookInj } from '~/context'
interface Props {
quickImportType: 'csv' | 'excel' | 'json'
projectTemplate: Record<string, any>
importData: any[]
importData: Record<string, any>[]
importColumns: any[]
importOnly: boolean
maxRowsToParse: number
}
interface Option {
@ -26,10 +21,16 @@ interface Option {
value: string
}
const { quickImportType, projectTemplate, importData } = defineProps<Props>()
const { quickImportType, projectTemplate, importData, importColumns, importOnly, maxRowsToParse } = defineProps<Props>()
const emit = defineEmits(['import'])
const meta = inject(MetaInj)
const columns = computed(() => meta?.value?.columns || [])
const reloadHook = inject(ReloadViewDataHookInj)!
const useForm = Form.useForm
const { $api } = useNuxtApp()
@ -67,8 +68,6 @@ const data = reactive<{ title: string | null; name: string; tables: TableType[]
tables: [],
})
const toast = useToast()
const { addTab } = useTabs()
const { sqlUi, project, loadTables } = useProject()
@ -97,6 +96,8 @@ const validators = computed(() =>
}, {}),
)
const srcDestMapping = ref<Record<string, any>[]>([])
const { validate, validateInfos } = useForm(data, validators)
function filterOption(input: string, option: Option) {
@ -174,119 +175,380 @@ function remapColNames(batchData: any[], columns: ColumnType[]) {
)
}
async function importTemplate() {
// check if form is valid
try {
await validate()
} catch (errorInfo) {
isImporting.value = false
throw new Error('Please fill all the required values')
function missingRequiredColumnsValidation() {
const missingRequiredColumns = columns.value.filter(
(c: Record<string, any>) =>
(c.pk ? !c.ai && !c.cdf : !c.cdf && c.rqd) && !srcDestMapping.value.some((r) => r.destCn === c.title),
)
if (missingRequiredColumns.length) {
notification.error({
message: `Following columns are required : ${missingRequiredColumns.map((c) => c.title).join(', ')}`,
duration: 3,
})
return false
}
return true
}
function atLeastOneEnabledValidation() {
if (srcDestMapping.value.filter((v) => v.enabled === true).length === 0) {
notification.error({
message: 'At least one column has to be selected',
duration: 3,
})
return false
}
return true
}
function fieldsValidation(record: Record<string, any>) {
// if it is not selected, then pass validation
if (!record.enabled) {
return true
}
const tableName = meta?.value.title || ''
if (!record.destCn) {
notification.error({
message: `Cannot find the destination column for ${record.srcCn}`,
duration: 3,
})
return false
}
if (srcDestMapping.value.filter((v) => v.destCn === record.destCn).length > 1) {
notification.error({
message: 'Duplicate mapping found, please remove one of the mapping',
duration: 3,
})
return false
}
try {
isImporting.value = true
// tab info to be used to show the tab after successful import
const tab = {
id: '',
title: '',
const v = columns.value.find((c) => c.title === record.destCn) as Record<string, any>
// check if the input contains null value for a required column
if (v.pk ? !v.ai && !v.cdf : !v.cdf && v.rqd) {
if (
importData[tableName]
.slice(0, maxRowsToParse)
.some((r: Record<string, any>) => r[record.srcCn] === null || r[record.srcCn] === undefined || r[record.srcCn] === '')
) {
notification.error({
message: 'null value violates not-null constraint',
duration: 3,
})
}
}
// create tables
for (const table of data.tables) {
// enrich system fields if not provided
// e.g. id, created_at, updated_at
const systemColumns = sqlUi?.value.getNewTableColumns().filter((c: ColumnType) => c.column_name !== 'title')
for (const systemColumn of systemColumns) {
if (!table.columns?.some((c) => c.column_name?.toLowerCase() === systemColumn.column_name.toLowerCase())) {
table.columns?.push(systemColumn)
}
switch (v.uidt) {
case UITypes.Number:
if (
importData[tableName]
.slice(0, maxRowsToParse)
.some(
(r: Record<string, any>) => r[record.sourceCn] !== null && r[record.srcCn] !== undefined && isNaN(+r[record.srcCn]),
)
) {
notification.error({
message: 'Source data contains some invalid numbers',
duration: 3,
})
return false
}
// set pk & rqd if ID is provided
if (table.columns) {
for (const column of table.columns) {
if (column.column_name?.toLowerCase() === 'id' && !('pk' in column)) {
column.pk = true
column.rqd = true
break
break
case UITypes.Checkbox:
if (
importData[tableName].slice(0, maxRowsToParse).some((r: Record<string, any>) => {
if (r[record.srcCn] !== null && r[record.srcCn] !== undefined) {
let input = r[record.srcCn]
if (typeof input === 'string') {
input = input.replace(/["']/g, '').toLowerCase().trim()
return !(
input === 'false' ||
input === 'no' ||
input === 'n' ||
input === '0' ||
input === 'true' ||
input === 'yes' ||
input === 'y' ||
input === '1'
)
}
return input !== 1 && input !== 0 && input !== true && input !== false
}
}
return false
})
) {
notification.error({
message: 'Source data contains some invalid boolean values',
duration: 3,
})
return false
}
const tableMeta = await $api.dbTable.create(project?.value?.id as string, {
table_name: table.table_name,
// leave title empty to get a generated one based on table_name
title: '',
columns: table.columns,
break
}
return true
}
async function importTemplate() {
if (importOnly) {
// validate required columns
if (!missingRequiredColumnsValidation()) return
// validate at least one column needs to be selected
if (!atLeastOneEnabledValidation()) return
try {
isImporting.value = true
const tableName = meta?.value.title as string
const data = importData[tableName]
const projectName = project.value.title as string
const total = data.length
for (let i = 0, progress = 0; i < total; i += maxRowsToParse) {
const batchData = data.slice(i, i + maxRowsToParse).map((row: Record<string, any>) =>
srcDestMapping.value.reduce((res: Record<string, any>, col: Record<string, any>) => {
if (col.enabled && col.destCn) {
const v = columns.value.find((c: Record<string, any>) => c.title === col.destCn) as Record<string, any>
let input = row[col.srcCn]
// parse potential boolean values
if (v.uidt === UITypes.Checkbox) {
input = input.replace(/["']/g, '').toLowerCase().trim()
if (input === 'false' || input === 'no' || input === 'n') {
input = '0'
} else if (input === 'true' || input === 'yes' || input === 'y') {
input = '1'
}
} else if (v.uidt === UITypes.Number) {
if (input === '') {
input = null
}
} else if (v.uidt === UITypes.SingleSelect || v.uidt === UITypes.MultiSelect) {
if (input === '') {
input = null
}
}
res[col.destCn] = input
}
return res
}, {}),
)
await $api.dbTableRow.bulkCreate('noco', projectName, tableName, batchData)
importingTip.value = `Importing data to ${projectName}: ${progress}/${total} records`
progress += batchData.length
}
// reload table
reloadHook.trigger()
notification.success({
message: 'Successfully imported table data',
duration: 3,
})
table.title = tableMeta.title
} catch (e: any) {
notification.error({
message: e.message,
duration: 3,
})
} finally {
isImporting.value = false
}
} else {
// check if form is valid
try {
await validate()
} catch (errorInfo) {
isImporting.value = false
throw new Error('Please fill all the required values')
}
// open the first table after import
if (tab.id === '' && tab.title === '') {
tab.id = tableMeta.id as string
tab.title = tableMeta.title as string
try {
isImporting.value = true
// tab info to be used to show the tab after successful import
const tab = {
id: '',
title: '',
}
// set primary value
if (tableMeta?.columns?.[0]?.id) {
await $api.dbTableColumn.primaryColumnSet(tableMeta.columns[0].id as string)
// create tables
for (const table of data.tables) {
// enrich system fields if not provided
// e.g. id, created_at, updated_at
const systemColumns = sqlUi?.value.getNewTableColumns().filter((c: ColumnType) => c.column_name !== 'title')
for (const systemColumn of systemColumns) {
if (!table.columns?.some((c) => c.column_name?.toLowerCase() === systemColumn.column_name.toLowerCase())) {
table.columns?.push(systemColumn)
}
}
// set pk & rqd if ID is provided
if (table.columns) {
for (const column of table.columns) {
if (column.column_name?.toLowerCase() === 'id' && !('pk' in column)) {
column.pk = true
column.rqd = true
break
}
}
}
const tableMeta = await $api.dbTable.create(project?.value?.id as string, {
table_name: table.table_name,
// leave title empty to get a generated one based on table_name
title: '',
columns: table.columns,
})
table.title = tableMeta.title
// open the first table after import
if (tab.id === '' && tab.title === '') {
tab.id = tableMeta.id as string
tab.title = tableMeta.title as string
}
// set primary value
if (tableMeta?.columns?.[0]?.id) {
await $api.dbTableColumn.primaryColumnSet(tableMeta.columns[0].id as string)
}
}
}
// bulk insert data
if (importData) {
let total = 0
let progress = 0
const offset = 500
const projectName = project.value.title as string
await Promise.all(
data.tables.map((table: Record<string, any>) =>
(async (tableMeta) => {
const data = importData[tableMeta.ref_table_name]
if (data) {
total += data.length
for (let i = 0; i < data.length; i += offset) {
importingTip.value = `Importing data to ${projectName}: ${progress}/${total} records`
const batchData = remapColNames(data.slice(i, i + offset), tableMeta.columns)
await $api.dbTableRow.bulkCreate('noco', projectName, tableMeta.title, batchData)
progress += batchData.length
// bulk insert data
if (importData) {
let total = 0
let progress = 0
const offset = maxRowsToParse
const projectName = project.value.title as string
await Promise.all(
data.tables.map((table: Record<string, any>) =>
(async (tableMeta) => {
const data = importData[tableMeta.ref_table_name]
if (data) {
total += data.length
for (let i = 0; i < data.length; i += offset) {
importingTip.value = `Importing data to ${projectName}: ${progress}/${total} records`
const batchData = remapColNames(data.slice(i, i + offset), tableMeta.columns)
await $api.dbTableRow.bulkCreate('noco', projectName, tableMeta.title, batchData)
progress += batchData.length
}
}
}
})(table),
),
)
})(table),
),
)
}
// reload table list
await loadTables()
addTab({
...tab,
type: 'table',
})
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
isImporting.value = false
}
// reload table list
await loadTables()
addTab({
...tab,
type: 'table',
})
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
} finally {
isImporting.value = false
}
}
const isValid = computed(() => {
for (const [_, o] of Object.entries(validateInfos)) {
if (o?.validateStatus) {
if (o.validateStatus === 'error') {
if (importOnly) {
for (const record of srcDestMapping.value) {
if (!fieldsValidation(record)) {
return false
}
}
} else {
for (const [_, o] of Object.entries(validateInfos)) {
if (o?.validateStatus) {
if (o.validateStatus === 'error') {
return false
}
}
}
}
return true
})
function mapDefaultColumns() {
srcDestMapping.value = []
for (const col of importColumns[0]) {
const o = { srcCn: col.column_name, destCn: '', enabled: true }
if (columns.value) {
const tableColumn = columns.value.find((c: Record<string, any>) => c.title === col.column_name)
if (tableColumn) {
o.destCn = tableColumn.title as string
} else {
o.enabled = false
}
}
srcDestMapping.value.push(o)
}
}
defineExpose({
importTemplate,
isValid,
})
onMounted(() => {
if (importOnly) {
mapDefaultColumns()
}
})
</script>
<template>
<a-spin :spinning="isImporting" :tip="importingTip" size="large">
<a-card>
<a-card v-if="importOnly">
<a-form :model="data" name="import-only">
<p v-if="data.tables && quickImportType === 'excel'" class="text-center">
{{ data.tables.length }} sheet{{ data.tables.length > 1 ? 's' : '' }}
available for import
</p>
</a-form>
<a-collapse v-if="data.tables && data.tables.length" v-model:activeKey="expansionPanel" class="template-collapse" accordion>
<a-collapse-panel v-for="(table, tableIdx) in data.tables" :key="tableIdx">
<template #header>
<span class="font-weight-bold text-lg flex items-center gap-2">
<mdi-table class="text-primary" />
{{ table.ref_table_name }}
</span>
</template>
<a-table
v-if="srcDestMapping"
class="template-form"
row-class-name="template-form-row"
:data-source="srcDestMapping"
:columns="srcDestMappingColumns"
:pagination="false"
>
<template #headerCell="{ column }">
<span v-if="column.key === 'source_column' || column.key === 'destination_column'">
{{ column.name }}
</span>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'source_column'">
<span>{{ record.srcCn }}</span>
</template>
<template v-else-if="column.key === 'destination_column'">
<a-select v-model:value="record.destCn" class="w-52" show-search :filter-option="filterOption">
<a-select-option v-for="(col, i) of columns" :key="i" :value="col.title">
<div class="flex items-center">
<component :is="getUIDTIcon(col.uidt)" />
<span class="ml-2">{{ col.title }}</span>
</div>
</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'action'">
<a-checkbox v-model:checked="record.enabled" />
</template>
</template>
</a-table>
</a-collapse-panel>
</a-collapse>
</a-card>
<a-card v-else>
<a-form :model="data" name="template-editor-form" @keydown.enter="emit('import')">
<p v-if="data.tables && quickImportType === 'excel'" class="text-center">
{{ data.tables.length }} sheet{{ data.tables.length > 1 ? 's' : '' }}
@ -312,7 +574,7 @@ defineExpose({
/>
</a-form-item>
<span v-else class="font-weight-bold text-lg flex items-center gap-2" @click="setEditableTn(tableIdx, true)">
<MdiTableIcon class="text-primary" />
<mdi-table class="text-primary" />
{{ table.table_name }}
</span>
</template>
@ -322,7 +584,7 @@ defineExpose({
<!-- TODO: i18n -->
<span>Delete Table</span>
</template>
<MdiDeleteOutlineIcon v-if="data.tables.length > 1" class="text-lg mr-8" @click.stop="deleteTable(tableIdx)" />
<mdi-delete-outline v-if="data.tables.length > 1" class="text-lg mr-8" @click.stop="deleteTable(tableIdx)" />
</a-tooltip>
</template>
@ -390,7 +652,7 @@ defineExpose({
<span>Primary Value</span>
</template>
<div class="flex items-center float-right mr-4">
<MdiKeyStarIcon class="text-lg" />
<mdi-key-star class="text-lg" />
</div>
</a-tooltip>
<a-tooltip v-else>
@ -401,7 +663,7 @@ defineExpose({
<a-button type="text" @click="deleteTableColumn(tableIdx, record.key)">
<div class="flex items-center">
<MdiDeleteOutlineIcon class="text-lg" />
<mdi-delete-outline class="text-lg" />
</div>
</a-button>
</a-tooltip>
@ -417,7 +679,7 @@ defineExpose({
<a-button @click="addNewColumnRow(table, 'Number')">
<div class="flex items-center">
<MdiNumericIcon class="text-lg" />
<mdi-numeric class="text-lg" />
</div>
</a-button>
</a-tooltip>
@ -429,7 +691,7 @@ defineExpose({
</template>
<a-button @click="addNewColumnRow(table, 'SingleLineText')">
<div class="flex items-center">
<MdiStringIcon class="text-lg" />
<mdi-alpha-a class="text-lg" />
</div>
</a-button>
</a-tooltip>
@ -441,7 +703,7 @@ defineExpose({
</template>
<a-button @click="addNewColumnRow(table, 'LongText')">
<div class="flex items-center">
<MdiLongTextIcon class="text-lg" />
<mdi-text class="text-lg" />
</div>
</a-button>
</a-tooltip>
@ -453,7 +715,7 @@ defineExpose({
</template>
<a-button @click="addNewColumnRow(table, 'SingleLineText')">
<div class="flex items-center">
<MdiPlusIcon class="text-lg" />
<mdi-plus class="text-lg" />
Column
</div>
</a-button>

20
packages/nc-gui-v2/components/template/utils.ts

@ -23,3 +23,23 @@ export const tableColumns: (Omit<ColumnGroupType<any>, 'children'> & { dataIndex
align: 'right',
},
]
export const srcDestMappingColumns: (Omit<ColumnGroupType<any>, 'children'> & { dataIndex?: string; name: string })[] = [
{
name: 'Source column',
dataIndex: 'source_column',
key: 'source_column',
width: 400,
},
{
name: 'Destination column',
dataIndex: 'destination_column',
key: 'destination_column',
width: 400,
},
{
name: 'Action',
key: 'action',
align: 'right',
},
]

21
packages/nc-gui-v2/components/webhook/Editor.vue

@ -1,6 +1,5 @@
<script setup lang="ts">
import { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { Form, notification } from 'ant-design-vue'
import { MetaInj } from '~/context'
import { extractSdkResponseErrorMsg, fieldRequiredValidator } from '~/utils'
import { inject, reactive, useApi, useNuxtApp } from '#imports'
@ -16,8 +15,6 @@ const { $e } = useNuxtApp()
const { api, isLoading: loading } = useApi()
const toast = useToast()
const meta = inject(MetaInj)
const useForm = Form.useForm
@ -305,7 +302,9 @@ async function loadPluginList() {
hook.eventOperation = `${hook.event} ${hook.operation}`
}
} catch (e: any) {
toast.error(extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -314,7 +313,9 @@ async function saveHooks() {
try {
await validate()
} catch (_: any) {
toast.error('Invalid Form')
notification.error({
message: 'Invalid Form',
})
loading.value = false
@ -352,9 +353,13 @@ async function saveHooks() {
// });
// }
toast.success('Webhook details updated successfully')
notification.success({
message: 'Webhook details updated successfully',
})
} catch (e: any) {
toast.error(extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
} finally {
loading.value = false
}

17
packages/nc-gui-v2/components/webhook/List.vue

@ -1,14 +1,13 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { MetaInj } from '~/context'
import { inject, onMounted, ref, useNuxtApp } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
const emit = defineEmits(['edit'])
const { $api, $e } = useNuxtApp()
const toast = useToast()
const hooks = ref<Record<string, any>[]>([])
const meta = inject(MetaInj)
@ -21,7 +20,9 @@ async function loadHooksList() {
return hook
})
} catch (e: any) {
toast.error(e.message)
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -33,12 +34,16 @@ async function deleteHook(item: Record<string, any>, index: number) {
} else {
hooks.value.splice(index, 1)
}
toast.success('Hook deleted successfully')
notification.success({
message: 'Hook deleted successfully',
})
if (!hooks.value.length) {
hooks.value = []
}
} catch (e: any) {
toast.error(e.message)
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
$e('a:webhook:delete')

12
packages/nc-gui-v2/components/webhook/Test.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { onMounted } from '@vue/runtime-core'
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { MetaInj } from '~/context'
import { extractSdkResponseErrorMsg } from '~/utils'
@ -12,8 +12,6 @@ const { hook } = defineProps<Props>()
const { $api } = useNuxtApp()
const toast = useToast()
const meta = inject(MetaInj)
const sampleData = ref({
@ -41,9 +39,13 @@ async function testWebhook() {
payload: sampleData.value,
})
toast.success('Webhook tested successfully')
notification.success({
message: 'Webhook tested successfully',
})
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}

24
packages/nc-gui-v2/composables/useColumnCreateStore.ts

@ -1,9 +1,8 @@
import { createInjectionState } from '@vueuse/core'
import { Form } from 'ant-design-vue'
import { Form, notification } from 'ant-design-vue'
import type { ColumnType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { useToast } from 'vue-toastification'
import { useColumn } from './useColumn'
import { computed } from '#imports'
import { useNuxtApp } from '#app'
@ -25,7 +24,6 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
const { sqlUi } = useProject()
const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const toast = useToast()
const idType = null
@ -178,12 +176,21 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
try {
console.log(formState, validators)
if (!(await validate())) return
} catch (e) {
notification.error({
message: 'Form validation failed',
})
return
}
try {
formState.value.table_name = meta.value.table_name
// formState.value.title = formState.value.column_name
if (column?.value) {
await $api.dbTableColumn.update(column?.value?.id as string, formState.value)
toast.success('Column updated')
notification.success({
message: 'Column updated',
})
} else {
// todo : set additional meta for auto generated string id
if (formState.value.uidt === UITypes.ID) {
@ -201,12 +208,15 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
getMeta(formState.value.childId, true).then(() => {})
}
toast.success('Column created')
notification.success({
message: 'Column created',
})
}
onSuccess?.()
} catch (e: any) {
const error = await extractSdkResponseErrorMsg(e)
if (error) toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}

18
packages/nc-gui-v2/composables/useTable.ts

@ -1,7 +1,6 @@
import { Modal } from 'ant-design-vue'
import { Modal, notification } from 'ant-design-vue'
import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { useProject } from './useProject'
import { TabType } from '~/composables/useTabs'
import { extractSdkResponseErrorMsg } from '~/utils'
@ -15,7 +14,6 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
})
const { $e, $api } = useNuxtApp()
const toast = useToast()
const { getMeta, removeMeta } = useMetas()
const { loadTables } = useProject()
const { closeTab } = useTabs()
@ -78,13 +76,13 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
return `${i + 1}. ${c.title} is a LinkToAnotherRecord of ${(refMeta && refMeta.title) || c.title}`
}),
)
toast.info(
h('div', {
notification.info({
message: h('div', {
innerHTML: `<div style="padding:10px 4px">Unable to delete tables because of the following.
<br><br>${refColMsgs.join('<br>')}<br><br>
Delete them & try again</div>`,
}),
)
})
return
}
@ -99,10 +97,14 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
await loadTables()
removeMeta(table.id as string)
toast.info(`Deleted table ${table.title} successfully`)
notification.info({
message: `Deleted table ${table.title} successfully`,
})
$e('a:table:delete')
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
},
})

4
packages/nc-gui-v2/composables/useViewData.ts

@ -89,7 +89,7 @@ export function useViewData(
for (const row of formattedData.value) {
const id = extractPkFromRow(row.row, meta?.value?.columns as ColumnType[])
row.rowMeta.commentCount = aggCommentCount.value?.find((c) => c.row_id === id)?.count || 0
row.rowMeta.commentCount = aggCommentCount.value?.find((c: Record<string, any>) => c.row_id === id)?.count || 0
}
}
@ -262,7 +262,7 @@ export function useViewData(
let row = formattedData.value.length
while (row--) {
try {
const { row: rowObj, rowMeta } = formattedData.value[row]
const { row: rowObj, rowMeta } = formattedData.value[row] as Record<string, any>
if (!rowMeta.selected) {
continue
}

4
packages/nc-gui-v2/composables/useViews.ts

@ -3,7 +3,7 @@ import type { MaybeRef } from '@vueuse/core'
import { useNuxtApp } from '#app'
export function useViews(meta: MaybeRef<TableType | undefined>) {
let views = $ref<ViewType[]>([])
const views = $ref<ViewType[]>([])
const { $api } = useNuxtApp()
const loadViews = async () => {
@ -12,7 +12,7 @@ export function useViews(meta: MaybeRef<TableType | undefined>) {
if (_meta && _meta.id) {
const response = (await $api.dbView.list(_meta.id)).list
if (response) {
views = response.sort((a, b) => a.order! - b.order!)
// views = response.sort((a, b) => a.order! - b.order!)
}
}
}

15
packages/nc-gui-v2/package-lock.json generated

@ -24,7 +24,6 @@
"util": "^0.12.4",
"vue-dompurify-html": "^3.0.0",
"vue-i18n": "^9.1.10",
"vue-toastification": "^2.0.0-rc.5",
"vuedraggable": "^4.1.0",
"vuetify": "^3.0.0-alpha.13",
"xlsx": "^0.17.3"
@ -14629,14 +14628,6 @@
"vue": "^3.2.0"
}
},
"node_modules/vue-toastification": {
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz",
"integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==",
"peerDependencies": {
"vue": "^3.0.2"
}
},
"node_modules/vue-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",
@ -26117,12 +26108,6 @@
"@vue/devtools-api": "^6.0.0"
}
},
"vue-toastification": {
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz",
"integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==",
"requires": {}
},
"vue-types": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",

3
packages/nc-gui-v2/package.json

@ -26,11 +26,10 @@
"socket.io-client": "^4.5.1",
"sortablejs": "^1.15.0",
"unique-names-generator": "^4.7.1",
"vue-dompurify-html": "^3.0.0",
"url": "^0.11.0",
"util": "^0.12.4",
"vue-dompurify-html": "^3.0.0",
"vue-i18n": "^9.1.10",
"vue-toastification": "^2.0.0-rc.5",
"vuedraggable": "^4.1.0",
"vuetify": "^3.0.0-alpha.13",
"xlsx": "^0.17.3"

11
packages/nc-gui-v2/pages/index/index.vue

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { Modal } from 'ant-design-vue'
import { Modal, notification } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { navigateTo } from '#app'
import { computed, onMounted, ref, useApi, useNuxtApp, useSidebar } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
@ -18,8 +17,6 @@ const { api, isLoading } = useApi()
useSidebar({ hasSidebar: true, isOpen: true })
const toast = useToast()
const filterQuery = ref('')
const projects = ref<ProjectType[]>()
@ -45,10 +42,12 @@ const deleteProject = (project: ProjectType) => {
async onOk() {
try {
$e('c:project:delete')
await $api.project.delete(project.id as string)
await api.project.delete(project.id as string)
return projects.value?.splice(projects.value.indexOf(project), 1)
} catch (e: any) {
return toast.error(await extractSdkResponseErrorMsg(e))
return notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
},
})

17
packages/nc-gui-v2/pages/project/index/[id].vue

@ -3,10 +3,9 @@ import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { ref } from 'vue'
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { navigateTo, useRoute } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { projectTitleValidator } from '~/utils/validation'
import { extractSdkResponseErrorMsg, projectTitleValidator } from '~/utils'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
import { nextTick, reactive, useSidebar } from '#imports'
@ -14,8 +13,6 @@ const { api } = useApi()
useSidebar({ hasSidebar: false })
const toast = useToast()
const route = useRoute()
const nameValidationRules = [
@ -32,10 +29,12 @@ const formState = reactive({
const getProject = async () => {
try {
const result: ProjectType = await $api.project.read(route.params.id as string)
const result: ProjectType = await api.project.read(route.params.id as string)
formState.title = result.title as string
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}
@ -45,7 +44,9 @@ const renameProject = async () => {
navigateTo(`/nc/${route.params.id}`)
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}

18
packages/nc-gui-v2/pages/project/index/create-external.vue

@ -1,8 +1,7 @@
<script lang="ts" setup>
import { onMounted } from '@vue/runtime-core'
import { Form, Modal } from 'ant-design-vue'
import { Form, Modal, notification } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import { useToast } from 'vue-toastification'
import { computed, ref, useSidebar, watch } from '#imports'
import { navigateTo, useNuxtApp } from '#app'
import { ClientType } from '~/lib'
@ -27,8 +26,6 @@ const { $api, $e } = useNuxtApp()
useSidebar({ hasSidebar: false })
const toast = useToast()
const { t } = useI18n()
const formState = $ref<ProjectCreateForm>({
@ -145,8 +142,9 @@ const createProject = async () => {
$e('a:project:create:extdb')
await navigateTo(`/nc/${result.id}`)
} catch (e: any) {
// todo: toast
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
loading.value = false
}
@ -187,12 +185,16 @@ const testConnection = async () => {
})
} else {
testSuccess.value = false
toast.error(`${t('msg.error.dbConnectionFailed')} ${result.message}`)
notification.error({
message: `${t('msg.error.dbConnectionFailed')} ${result.message}`,
})
}
}
} catch (e: any) {
testSuccess.value = false
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}

8
packages/nc-gui-v2/pages/project/index/create.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { notification } from 'ant-design-vue'
import { nextTick, reactive, ref, useApi, useSidebar } from '#imports'
import { navigateTo, useNuxtApp } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
@ -14,8 +14,6 @@ const { api, isLoading } = useApi()
useSidebar({ hasSidebar: false })
const toast = useToast()
const nameValidationRules = [
{
required: true,
@ -37,7 +35,9 @@ const createProject = async () => {
await navigateTo(`/nc/${result.id}`)
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
}

10
packages/nc-gui-v2/pages/projects/index.vue

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { Modal } from 'ant-design-vue'
import { Modal, notification } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { navigateTo } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils'
import MaterialSymbolsFormatListBulletedRounded from '~icons/material-symbols/format-list-bulleted-rounded'
@ -33,7 +32,6 @@ const navDrawerOptions = [
const route = useRoute()
const { $api } = useNuxtApp()
const toast = useToast()
const response = await $api.project.list({})
const projects = $ref(response.list)
@ -50,8 +48,10 @@ const deleteProject = (project: ProjectType) => {
try {
await $api.project.delete(project.id as string)
projects.splice(projects.indexOf(project), 1)
} catch (e) {
toast.error(await extractSdkResponseErrorMsg(e))
} catch (e: any) {
notification.error({
message: await extractSdkResponseErrorMsg(e),
})
}
},
})

8
packages/nc-gui-v2/plugins/toastr.ts

@ -1,8 +0,0 @@
import { defineNuxtPlugin } from 'nuxt/app'
import Toast from 'vue-toastification'
// Import the CSS or use your own!
import 'vue-toastification/dist/index.css'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Toast, {})
})

1
packages/nc-gui-v2/utils/index.ts

@ -16,3 +16,4 @@ export * from './validation'
export * from './viewUtils'
export * from './currencyUtils'
export * from './dataUtils'
export * from './userUtils'

4
packages/nc-gui-v2/utils/parsers/ExcelTemplateAdapter.ts

@ -237,4 +237,8 @@ export default class ExcelTemplateAdapter extends TemplateGenerator {
getData() {
return this.data
}
getColumns() {
return this.project.tables.map((t: Record<string, any>) => t.columns)
}
}

Loading…
Cancel
Save