Browse Source

Merge branch 'develop' into fix/gui-v2-formula

pull/3049/head
Wing-Kam Wong 2 years ago
parent
commit
1d11d6fac6
  1. 2
      packages/nc-gui-v2/assets/style-v2.scss
  2. 35
      packages/nc-gui-v2/components.d.ts
  3. 4
      packages/nc-gui-v2/components/cell/MultiSelect.vue
  4. 2
      packages/nc-gui-v2/components/cell/attachment/Carousel.vue
  5. 30
      packages/nc-gui-v2/components/cell/attachment/index.vue
  6. 52
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  7. 2
      packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue
  8. 6
      packages/nc-gui-v2/components/dlg/AirtableImport.vue
  9. 2
      packages/nc-gui-v2/components/dlg/QuickImport.vue
  10. 18
      packages/nc-gui-v2/components/dlg/TableCreate.vue
  11. 6
      packages/nc-gui-v2/components/dlg/TableRename.vue
  12. 10
      packages/nc-gui-v2/components/general/MiniSidebar.vue
  13. 2
      packages/nc-gui-v2/components/smartsheet-column/CurrencyOptions.vue
  14. 2
      packages/nc-gui-v2/components/smartsheet-column/DurationOptions.vue
  15. 48
      packages/nc-gui-v2/components/smartsheet-column/EditOrAdd.vue
  16. 36
      packages/nc-gui-v2/components/smartsheet-column/LookupOptions.vue
  17. 2
      packages/nc-gui-v2/components/smartsheet-column/RatingOptions.vue
  18. 18
      packages/nc-gui-v2/components/smartsheet-column/RollupOptions.vue
  19. 38
      packages/nc-gui-v2/components/smartsheet-header/Menu.vue
  20. 2
      packages/nc-gui-v2/components/smartsheet-header/VirtualCell.vue
  21. 3
      packages/nc-gui-v2/components/smartsheet-header/VirtualCellIcon.vue
  22. 3
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue
  23. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilterMenu.vue
  24. 23
      packages/nc-gui-v2/components/smartsheet-toolbar/FieldListAutoCompleteDropdown.vue
  25. 5
      packages/nc-gui-v2/components/smartsheet-toolbar/MoreActions.vue
  26. 1
      packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue
  27. 9
      packages/nc-gui-v2/components/smartsheet-toolbar/SharedViewList.vue
  28. 1
      packages/nc-gui-v2/components/smartsheet-toolbar/SortListMenu.vue
  29. 10
      packages/nc-gui-v2/components/smartsheet/Cell.vue
  30. 6
      packages/nc-gui-v2/components/smartsheet/Gallery.vue
  31. 40
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  32. 13
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  33. 4
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  34. 3
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  35. 4
      packages/nc-gui-v2/components/webhook/Test.vue
  36. 7
      packages/nc-gui-v2/composables/useColumnCreateStore.ts
  37. 10
      packages/nc-gui-v2/layouts/base.vue
  38. 10
      packages/nc-gui-v2/pages/index/user/index.vue
  39. 4
      packages/nc-gui-v2/pages/index/user/index/index.vue
  40. 6
      packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue
  41. 4
      packages/nc-gui-v2/pages/project/index/[id].vue
  42. 6
      packages/nc-gui-v2/pages/project/index/create.vue
  43. 2
      packages/nc-gui-v2/pages/projects/index.vue
  44. 9
      packages/nocodb-sdk/src/lib/Api.ts
  45. 2
      packages/nocodb/src/lib/models/View.ts
  46. 17
      scripts/sdk/swagger.json

2
packages/nc-gui-v2/assets/style-v2.scss

@ -75,7 +75,7 @@ html {
// menu item styling // menu item styling
.nc-menu-item { .nc-menu-item {
@apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5)); @apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 relative after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
} }
.nc-sidebar-right-item { .nc-sidebar-right-item {

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

@ -42,12 +42,14 @@ declare module '@vue/runtime-core' {
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup'] AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination'] APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
ARadio: typeof import('ant-design-vue/es')['Radio'] ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARate: typeof import('ant-design-vue/es')['Rate'] ARate: typeof import('ant-design-vue/es')['Rate']
ARow: typeof import('ant-design-vue/es')['Row'] ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select'] ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
ASkeletonImage: typeof import('ant-design-vue/es')['SkeletonImage'] ASkeletonImage: typeof import('ant-design-vue/es')['SkeletonImage']
ASpin: typeof import('ant-design-vue/es')['Spin'] ASpin: typeof import('ant-design-vue/es')['Spin']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
@ -79,11 +81,44 @@ declare module '@vue/runtime-core' {
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default'] MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiAt: typeof import('~icons/mdi/at')['default'] MdiAt: typeof import('~icons/mdi/at')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default'] MdiCalculator: typeof import('~icons/mdi/calculator')['default']
MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default']
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default']
MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiCloseCircle: typeof import('~icons/mdi/close-circle')['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'] MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiDownload: typeof import('~icons/mdi/download')['default']
MdiDrag: typeof import('~icons/mdi/drag')['default']
MdiEmail: typeof import('~icons/mdi/email')['default']
MdiEyeOffOutline: typeof import('~icons/mdi/eye-off-outline')['default']
MdiFlag: typeof import('~icons/mdi/flag')['default']
MdiFolder: typeof import('~icons/mdi/folder')['default']
MdiFunction: typeof import('~icons/mdi/function')['default'] MdiFunction: typeof import('~icons/mdi/function')['default']
MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default']
MdiHeart: typeof import('~icons/mdi/heart')['default']
MdiHook: typeof import('~icons/mdi/hook')['default']
MdiInformation: typeof import('~icons/mdi/information')['default']
MdiLink: typeof import('~icons/mdi/link')['default']
MdiLinkVariantRemove: typeof import('~icons/mdi/link-variant-remove')['default']
MdiLogout: typeof import('~icons/mdi/logout')['default'] MdiLogout: typeof import('~icons/mdi/logout')['default']
MdiMagnify: typeof import('~icons/mdi/magnify')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']
MdiNotebookCheckOutline: typeof import('~icons/mdi/notebook-check-outline')['default']
MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default']
MdiOperator: typeof import('~icons/mdi/operator')['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'] MdiReload: typeof import('~icons/mdi/reload')['default']
MdiSearch: typeof import('~icons/mdi/search')['default']
MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default'] MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default'] MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiStar: typeof import('~icons/mdi/star')['default'] MdiStar: typeof import('~icons/mdi/star')['default']

4
packages/nc-gui-v2/components/cell/MultiSelect.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject } from '#imports' import { computed, inject } from '#imports'
import { ColumnInj, EditModeInj } from '~/context' import { ColumnInj } from '~/context'
interface Props { interface Props {
modelValue: string | null modelValue: string | null
@ -11,8 +11,6 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const column = inject(ColumnInj) const column = inject(ColumnInj)
const isForm = inject<boolean>('isForm', false)
const editEnabled = inject(EditModeInj, ref(false))
const options = computed(() => column?.value?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || []) const options = computed(() => column?.value?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || [])

2
packages/nc-gui-v2/components/cell/attachment/Carousel.vue

@ -2,7 +2,7 @@
import { onKeyDown } from '@vueuse/core' import { onKeyDown } from '@vueuse/core'
import { useAttachmentCell } from './utils' import { useAttachmentCell } from './utils'
import { isImage } from '~/utils' import { isImage } from '~/utils'
import { computed, onClickOutside, ref, watch } from '#imports' import { computed, onClickOutside, ref } from '#imports'
import MaterialSymbolsArrowCircleRightRounded from '~icons/material-symbols/arrow-circle-right-rounded' import MaterialSymbolsArrowCircleRightRounded from '~icons/material-symbols/arrow-circle-right-rounded'
import MaterialSymbolsArrowCircleLeftRounded from '~icons/material-symbols/arrow-circle-left-rounded' import MaterialSymbolsArrowCircleLeftRounded from '~icons/material-symbols/arrow-circle-left-rounded'
import MdiCloseCircle from '~icons/mdi/close-circle' import MdiCloseCircle from '~icons/mdi/close-circle'

30
packages/nc-gui-v2/components/cell/attachment/index.vue

@ -6,21 +6,17 @@ import { useSortable } from './sort'
import Carousel from './Carousel.vue' import Carousel from './Carousel.vue'
import { onMounted, ref, useDropZone, watch } from '#imports' import { onMounted, ref, useDropZone, watch } from '#imports'
import { isImage, openLink } from '~/utils' import { isImage, openLink } from '~/utils'
import MaterialSymbolsAttachFile from '~icons/material-symbols/attach-file'
import MaterialArrowExpandIcon from '~icons/mdi/arrow-expand'
import MaterialSymbolsFileCopyOutline from '~icons/material-symbols/file-copy-outline'
import MdiReload from '~icons/mdi/reload'
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file'
interface Props { interface Props {
modelValue: string | Record<string, any>[] | null modelValue: string | Record<string, any>[] | null
rowIndex: number
} }
interface Emits { interface Emits {
(event: 'update:modelValue', value: string | Record<string, any>): void (event: 'update:modelValue', value: string | Record<string, any>): void
} }
const { modelValue } = defineProps<Props>() const { modelValue, rowIndex } = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
@ -60,7 +56,7 @@ onKeyDown('Escape', () => {
/** if possible, on mounted we try to fetch the relevant `td` cell to use as a dropzone */ /** if possible, on mounted we try to fetch the relevant `td` cell to use as a dropzone */
onMounted(() => { onMounted(() => {
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
dropZoneRef.value = document.querySelector(`td[data-col="${column.value.id}"]`) as HTMLTableDataCellElement dropZoneRef.value = document.querySelector(`td[data-key="${rowIndex}${column.value.id}"]`) as HTMLTableDataCellElement
} }
}) })
</script> </script>
@ -73,7 +69,7 @@ onMounted(() => {
<general-overlay <general-overlay
v-model="isOverDropZone" v-model="isOverDropZone"
inline inline
:target="`td[data-col='${column.id}']`" :target="`td[data-key='${rowIndex}${column.id}']`"
class="text-white text-lg ring ring-pink-500 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl" class="text-white text-lg ring ring-pink-500 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl"
> >
<MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here <MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here
@ -100,12 +96,15 @@ onMounted(() => {
</div> </div>
<template v-if="visibleItems.length"> <template v-if="visibleItems.length">
<div ref="sortableRef" :class="{ dragging }" class="flex flex-wrap gap-2 p-1 scrollbar-thin-dull"> <div
ref="sortableRef"
:class="{ dragging }"
class="flex justify-center items-center flex-wrap gap-2 p-1 scrollbar-thin-dull max-h-[150px] overflow-scroll"
>
<div <div
v-for="(item, i) of visibleItems" v-for="(item, i) of visibleItems"
:id="item.url" :id="item.url"
:key="item.url || item.title" :key="item.url || item.title"
style="flex: 1 1 50px"
:class="isImage(item.title, item.mimetype) ? '' : 'border-1 rounded'" :class="isImage(item.title, item.mimetype) ? '' : 'border-1 rounded'"
class="nc-attachment flex items-center justify-center min-h-[50px]" class="nc-attachment flex items-center justify-center min-h-[50px]"
> >
@ -120,7 +119,7 @@ onMounted(() => {
placeholder placeholder
:alt="item.title || `#${i}`" :alt="item.title || `#${i}`"
:src="item.url || item.data" :src="item.url || item.data"
class="ring-1 ring-gray-300 rounded" class="ring-1 ring-gray-300 rounded max-h-[50px] max-w-[50px]"
@click="selectedImage = item" @click="selectedImage = item"
/> />
@ -137,10 +136,7 @@ onMounted(() => {
<a-tooltip v-else placement="bottom"> <a-tooltip v-else placement="bottom">
<template #title> View attachments </template> <template #title> View attachments </template>
<MaterialArrowExpandIcon <MdiArrowExpand class="select-none transform group-hover:(text-pink-500 scale-120)" @click.stop="modalVisible = true" />
class="select-none transform group-hover:(text-pink-500 scale-120)"
@click.stop="modalVisible = true"
/>
</a-tooltip> </a-tooltip>
</div> </div>
</template> </template>
@ -152,6 +148,10 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
.nc-cell { .nc-cell {
.nc-attachment-cell { .nc-attachment-cell {
.nc-attachment {
@apply w-[50px] h-[50px] min-h-[50px] min-w-[50px];
}
.ghost, .ghost,
.ghost > * { .ghost > * {
@apply !pointer-events-none; @apply !pointer-events-none;

52
packages/nc-gui-v2/components/dashboard/TreeView.vue

@ -1,27 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import { useToast } from 'vue-toastification'
import { useProject, useTable, useTabs, watchEffect } from '#imports'
import { useNuxtApp, useRoute } from '#app' import { useNuxtApp, useRoute } from '#app'
import { computed, useProject, useTable, useTabs, watchEffect } from '#imports'
import { TabType } from '~/composables'
import MdiTable from '~icons/mdi/table' import MdiTable from '~icons/mdi/table'
import MdiView from '~icons/mdi/eye-circle-outline' import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large' import MdiTableLarge from '~icons/mdi/table-large'
import MdiMenuDown from '~icons/mdi/chevron-down' import MdiMenuDown from '~icons/mdi/chevron-down'
import MdiPlus from '~icons/mdi/plus-circle-outline'
import MdiDrag from '~icons/mdi/drag-vertical'
import MdiMenuIcon from '~icons/mdi/dots-vertical' import MdiMenuIcon from '~icons/mdi/dots-vertical'
import MdiDrag from '~icons/mdi/drag-vertical'
import MdiPlus from '~icons/mdi/plus-circle-outline'
const { addTab } = useTabs() const { addTab } = useTabs()
const toast = useToast()
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const route = useRoute() const route = useRoute()
const { tables, loadTables } = useProject(route.params.projectId as string) const { tables, loadTables } = useProject(route.params.projectId as string)
const { activeTab } = useTabs()
const { deleteTable } = useTable() const { deleteTable } = useTable()
const tablesById = $computed<Record<string, TableType>>(() => const tablesById = $computed<Record<string, TableType>>(() =>
@ -32,13 +30,11 @@ const tablesById = $computed<Record<string, TableType>>(() =>
) )
const showTableList = ref(true) const showTableList = ref(true)
const tableCreateDlg = ref(false) const tableCreateDlg = ref(false)
let key = $ref(0)
const menuRef = $ref<HTMLLIElement>() const menuRef = $ref<HTMLLIElement>()
let key = $ref(0)
let sortable: Sortable let sortable: Sortable
// todo: replace with vuedraggable // todo: replace with vuedraggable
@ -106,13 +102,11 @@ const icon = (table: TableType) => {
} }
const filterQuery = $ref('') const filterQuery = $ref('')
const filteredTables = $computed(() => { const filteredTables = $computed(() => {
return tables?.value?.filter((table) => !filterQuery || table?.title.toLowerCase()?.includes(filterQuery.toLowerCase())) return tables?.value?.filter((table) => !filterQuery || table?.title.toLowerCase()?.includes(filterQuery.toLowerCase()))
}) })
const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({}) const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({})
const setMenuContext = (type: 'table' | 'main', value?: any) => { const setMenuContext = (type: 'table' | 'main', value?: any) => {
contextMenuTarget.type = type contextMenuTarget.type = type
contextMenuTarget.value = value contextMenuTarget.value = value
@ -120,24 +114,24 @@ const setMenuContext = (type: 'table' | 'main', value?: any) => {
} }
const renameTableDlg = ref(false) const renameTableDlg = ref(false)
const renameTableMeta = ref() const renameTableMeta = ref()
const showRenameTableDlg = (table: TableType, rightClick = false) => { const showRenameTableDlg = (table: TableType, rightClick = false) => {
$e(rightClick ? 'c:table:rename:navdraw:right-click' : 'c:table:rename:navdraw:options') $e(rightClick ? 'c:table:rename:navdraw:right-click' : 'c:table:rename:navdraw:options')
renameTableMeta.value = table renameTableMeta.value = table
renameTableDlg.value = true renameTableDlg.value = true
} }
const reloadTables = async () => { const reloadTables = async () => {
$e('a:table:refresh:navdraw') $e('a:table:refresh:navdraw')
await loadTables() await loadTables()
} }
const addTableTab = (table: TableType) => { const addTableTab = (table: TableType) => {
$e('a:table:open') $e('a:table:open')
addTab({ title: table.title, id: table.id, type: table.type as any }) addTab({ title: table.title, id: table.id, type: table.type as any })
} }
const activeTable = computed(() => {
return [TabType.TABLE, TabType.VIEW].includes(activeTab.value?.type) ? activeTab.value.title : null
})
</script> </script>
<template> <template>
@ -186,11 +180,10 @@ const addTableTab = (table: TableType) => {
:key="table.id" :key="table.id"
v-t="['a:table:open']" v-t="['a:table:open']"
:class="[ :class="[
{ hidden: !filteredTables?.includes(table) }, { hidden: !filteredTables?.includes(table), active: activeTable === table.title },
`nc-project-tree-tbl nc-project-tree-tbl-${table.title}`, `nc-project-tree-tbl nc-project-tree-tbl-${table.title}`,
route.params.title && route.params.title.includes(table.title) ? 'bg-blue-500/15' : '',
]" ]"
class="pl-5 pr-3 py-2 text-sm cursor-pointer group" class="nc-tree-item pl-5 pr-3 py-2 text-sm cursor-pointer group"
:data-order="table.order" :data-order="table.order"
:data-id="table.id" :data-id="table.id"
@click="addTableTab(table)" @click="addTableTab(table)"
@ -254,7 +247,7 @@ const addTableTab = (table: TableType) => {
</div> </div>
</template> </template>
<style lang="scss" scoped> <style scoped>
.nc-treeview-container { .nc-treeview-container {
@apply h-[calc(100vh_-_var(--header-height))]; @apply h-[calc(100vh_-_var(--header-height))];
} }
@ -299,4 +292,23 @@ const addTableTab = (table: TableType) => {
@apply !bg-primary/25 text-primary; @apply !bg-primary/25 text-primary;
} }
} }
.nc-tree-item {
@apply relative cursor-pointer after:(pointer-events-none content-[''] absolute top-0 left-0 w-full h-full right-0 !bg-current transition transition-opactity duration-100 opacity-0);
}
.nc-tree-item svg {
@apply text-gray-500;
}
.nc-tree-item.active {
@apply !text-primary after:(!opacity-5);
svg {
@apply !text-primary;
}
}
.nc-tree-item:hover {
@apply !text-grey after:(!opacity-2);
}
</style> </style>

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

@ -32,7 +32,7 @@ async function loadTableList() {
isLoading = true isLoading = true
// TODO includeM2M // TODO includeM2M
tables = await $api.project.modelVisibilityList(project.value?.id, { tables = await $api.project.modelVisibilityList(project.value?.id, {
includeM2M: true, includeM2M: false,
}) })
} catch (e) { } catch (e) {
console.error(e) console.error(e)

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

@ -25,9 +25,7 @@ const { $state } = useNuxtApp()
const toast = useToast() const toast = useToast()
const { sqlUi, project, loadTables } = useProject() const { project, loadTables } = useProject()
const loading = ref(false)
const showGoToDashboardButton = ref(false) const showGoToDashboardButton = ref(false)
@ -78,7 +76,7 @@ const dialogShow = computed({
const useForm = Form.useForm const useForm = Form.useForm
const { resetFields, validate, validateInfos } = useForm(syncSource, validators) const { validateInfos } = useForm(syncSource, validators)
const disableImportButton = computed(() => { const disableImportButton = computed(() => {
return !syncSource.value.details.apiKey || !syncSource.value.details.syncSourceUrlOrId return !syncSource.value.details.apiKey || !syncSource.value.details.syncSourceUrlOrId

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

@ -68,7 +68,7 @@ const validators = computed(() => {
} }
}) })
const { resetFields, validate, validateInfos } = useForm(importState, validators) const { validate, validateInfos } = useForm(importState, validators)
const importMeta = computed(() => { const importMeta = computed(() => {
if (IsImportTypeExcel.value) { if (IsImportTypeExcel.value) {

18
packages/nc-gui-v2/components/dlg/TableCreate.vue

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Form } from 'ant-design-vue' import { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { onMounted, useProject, useTable, useTabs } from '#imports' import { onMounted, useProject, useTable, useTabs } from '#imports'
import { validateTableName } from '~/utils/validation' import { validateTableName } from '~/utils/validation'
import { TabType } from '~/composables' import { TabType } from '~/composables'
@ -15,12 +14,6 @@ const emit = defineEmits(['update:modelValue'])
const dialogShow = useVModel(props, 'modelValue', emit) const dialogShow = useVModel(props, 'modelValue', emit)
const toast = useToast()
const valid = ref(false)
const isIdToggleAllowed = ref(false)
const isAdvanceOptVisible = ref(false) const isAdvanceOptVisible = ref(false)
const { addTab } = useTabs() const { addTab } = useTabs()
@ -38,18 +31,9 @@ const { table, createTable, generateUniqueTitle, tables, project } = useTable(as
dialogShow.value = false dialogShow.value = false
}) })
const prefix = computed(() => project?.value?.prefix || '')
const validateDuplicateAlias = (v: string) => { const validateDuplicateAlias = (v: string) => {
return (tables?.value || []).every((t) => t.title !== (v || '')) || 'Duplicate table alias' 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'
}
const inputEl = ref<HTMLInputElement>() const inputEl = ref<HTMLInputElement>()
const useForm = Form.useForm const useForm = Form.useForm
@ -60,7 +44,7 @@ const validators = computed(() => {
table_name: [validateTableName], table_name: [validateTableName],
} }
}) })
const { resetFields, validate, validateInfos } = useForm(table, validators) const { validateInfos } = useForm(table, validators)
onMounted(() => { onMounted(() => {
generateUniqueTitle() generateUniqueTitle()

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

@ -28,9 +28,7 @@ const dialogShow = computed({
const { updateTab } = useTabs() const { updateTab } = useTabs()
const { loadTables } = useProject() const { loadTables } = useProject()
const { project, tables } = useProject() const { tables } = useProject()
const prefix = computed(() => project?.value?.prefix || '')
const inputEl = $ref<any>() const inputEl = $ref<any>()
let loading = $ref(false) let loading = $ref(false)
@ -60,7 +58,7 @@ const validators = computed(() => {
], ],
} }
}) })
const { resetFields, validate, validateInfos } = useForm(formState, validators) const { validateInfos } = useForm(formState, validators)
watchEffect(() => { watchEffect(() => {
if (tableMeta?.title) formState.title = tableMeta?.title if (tableMeta?.title) formState.title = tableMeta?.title

10
packages/nc-gui-v2/components/general/MiniSidebar.vue

@ -1,12 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { breakpointsTailwind } from '@vueuse/core'
import { navigateTo } from '#app' import { navigateTo } from '#app'
import { computed, useBreakpoints, useGlobal, useProject, useRoute, useSidebar } from '#imports' import { computed, useGlobal, useProject, useRoute, useSidebar } from '#imports'
/** get current breakpoints (for enabling sidebar) */ const { signOut, signedIn, user } = useGlobal()
const breakpoints = useBreakpoints(breakpointsTailwind)
const { signOut, signedIn, isLoading, user } = useGlobal()
const { isOpen } = useSidebar({ isOpen: true }) const { isOpen } = useSidebar({ isOpen: true })
@ -33,7 +29,7 @@ const logout = () => {
theme="light" theme="light"
> >
<a-dropdown placement="bottom" :trigger="['click']"> <a-dropdown placement="bottom" :trigger="['click']">
<div class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105"> <div class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 nc-noco-brand-icon">
<img width="35" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" /> <img width="35" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" />
</div> </div>

2
packages/nc-gui-v2/components/smartsheet-column/CurrencyOptions.vue

@ -7,7 +7,7 @@ interface Option {
value: string value: string
} }
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow() const { formState, validateInfos, setAdditionalValidations } = useColumnCreateStoreOrThrow()
const validators = { const validators = {
'meta.currency_locale': [ 'meta.currency_locale': [

2
packages/nc-gui-v2/components/smartsheet-column/DurationOptions.vue

@ -2,7 +2,7 @@
import { useColumnCreateStoreOrThrow } from '#imports' import { useColumnCreateStoreOrThrow } from '#imports'
import { durationOptions } from '@/utils' import { durationOptions } from '@/utils'
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow() const { formState } = useColumnCreateStoreOrThrow()
const durationOptionList = const durationOptionList =
durationOptions.map((o) => ({ durationOptions.map((o) => ({

48
packages/nc-gui-v2/components/smartsheet-column/EditOrAdd.vue

@ -8,7 +8,7 @@ import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
import MdiIdentifierIcon from '~icons/mdi/identifier' import MdiIdentifierIcon from '~icons/mdi/identifier'
interface Props { interface Props {
editColumnDropdown: boolean editColumnDropdown?: boolean
} }
const { editColumnDropdown } = defineProps<Props>() const { editColumnDropdown } = defineProps<Props>()
@ -21,20 +21,13 @@ const { getMeta } = useMetas()
const formulaOptionsRef = ref() const formulaOptionsRef = ref()
const { const { formState, validateInfos, onUidtOrIdTypeChange, onAlter, addOrUpdate, generateNewColumnMeta, isEdit } =
formState, useColumnCreateStoreOrThrow()
resetFields,
validate,
validateInfos,
onUidtOrIdTypeChange,
onAlter,
addOrUpdate,
generateNewColumnMeta,
isEdit,
} = useColumnCreateStoreOrThrow()
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber] const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const onlyNameUpdateOnEditColumns = [UITypes.LinkToAnotherRecord, UITypes.Lookup, UITypes.Rollup]
const uiTypesOptions = computed<typeof uiTypes>(() => { const uiTypesOptions = computed<typeof uiTypes>(() => {
return [ return [
...uiTypes.filter((t) => !isEdit.value || !t.virtual), ...uiTypes.filter((t) => !isEdit.value || !t.virtual),
@ -64,6 +57,11 @@ function onCancel() {
} }
} }
async function onSubmit() {
await addOrUpdate(reloadMetaAndData)
advancedOptions.value = false
}
// create column meta if it's a new column // create column meta if it's a new column
watchEffect(() => { watchEffect(() => {
if (!isEdit.value) { if (!isEdit.value) {
@ -81,6 +79,7 @@ watchEffect(() => {
antInput.value.select() antInput.value.select()
}, 300) }, 300)
} }
advancedOptions.value = false
}) })
watch( watch(
@ -112,7 +111,10 @@ if (!formState.value?.column_name) {
@input="onAlter(8)" @input="onAlter(8)"
/> />
</a-form-item> </a-form-item>
<a-form-item :label="$t('labels.columnType')"> <a-form-item
v-if="!(editColumnDropdown && !!onlyNameUpdateOnEditColumns.find((col) => col === formState.uidt))"
:label="$t('labels.columnType')"
>
<a-select <a-select
v-model:value="formState.uidt" v-model:value="formState.uidt"
show-search show-search
@ -134,10 +136,12 @@ if (!formState.value?.column_name) {
<SmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" /> <SmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" />
<SmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" /> <SmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" />
<SmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" /> <SmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" />
<SmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" /> <SmartsheetColumnLookupOptions v-if="!editColumnDropdown && formState.uidt === UITypes.Lookup" />
<SmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" /> <SmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" />
<SmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" /> <SmartsheetColumnRollupOptions v-if="!editColumnDropdown && formState.uidt === UITypes.Rollup" />
<SmartsheetColumnLinkedToAnotherRecordOptions v-if="formState.uidt === UITypes.LinkToAnotherRecord" /> <SmartsheetColumnLinkedToAnotherRecordOptions
v-if="!editColumnDropdown && formState.uidt === UITypes.LinkToAnotherRecord"
/>
<SmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" /> <SmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
<SmartsheetColumnPercentOptions v-if="formState.uidt === UITypes.Percent" /> <SmartsheetColumnPercentOptions v-if="formState.uidt === UITypes.Percent" />
@ -168,17 +172,7 @@ if (!formState.value?.column_name) {
<!-- Cancel --> <!-- Cancel -->
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</a-button> </a-button>
<a-button <a-button html-type="submit" type="primary" size="small" @click="onSubmit">
html-type="submit"
type="primary"
size="small"
@click="
() => {
addOrUpdate(reloadMetaAndData)
advancedOptions = false
}
"
>
<!-- Save --> <!-- Save -->
{{ $t('general.save') }} {{ $t('general.save') }}
</a-button> </a-button>

36
packages/nc-gui-v2/components/smartsheet-column/LookupOptions.vue

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk' import { UITypes, isSystemColumn } from 'nocodb-sdk'
import { useColumnCreateStoreOrThrow } from '#imports' import { useColumnCreateStoreOrThrow } from '#imports'
import { MetaInj } from '~/context' import { MetaInj } from '~/context'
@ -27,16 +28,16 @@ const refTables = $computed(() => {
return [] return []
} }
// todo: type issues with ColumnType so we have to cast to any return meta.columns
return ( .filter((c: ColumnType) => c.uidt === UITypes.LinkToAnotherRecord && !c.system)
meta.columns .map<TableType & { col: LinkToAnotherRecordType; column: ColumnType }>((c: ColumnType) => ({
?.filter((c: any) => c.uidt === UITypes.LinkToAnotherRecord && c.colOptions?.type !== 'bt' && !c.system)
.map((c) => ({
col: c.colOptions, col: c.colOptions,
column: c, column: c,
...tables.find((t) => t.id === (c.colOptions as any)?.fk_related_model_id), ...tables.find((t) => t.id === (c.colOptions as LinkToAnotherRecordType).fk_related_model_id),
})) }))
.filter((table: any) => table.col?.fk_related_model_id === table.id && !table.mm) ?? [] .filter(
(table: TableType & { col: LinkToAnotherRecordType; column: ColumnType }) =>
table.col.fk_related_model_id === table.id && !table.mm,
) )
}) })
@ -46,7 +47,7 @@ const columns = $computed(() => {
return [] return []
} }
return metas[selectedTable.id].columns.filter((c: any) => !isSystemColumn(c)) return metas[selectedTable.id].columns.filter((c) => !isSystemColumn(c))
}) })
</script> </script>
@ -54,17 +55,22 @@ const columns = $computed(() => {
<div class="p-4 w-full flex flex-col border-2 mb-2 mt-4"> <div class="p-4 w-full flex flex-col border-2 mb-2 mt-4">
<div class="w-full flex flex-row space-x-2"> <div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id"> <a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-select v-model:value="formState.fk_relation_column_id" size="small" @change="onDataTypeChange"> <a-select
<a-select-option v-for="(table, index) of refTables" :key="index" :value="table.col.fk_column_id"> v-model:value="formState.fk_relation_column_id"
<div class="flex flex-row items-center space-x-0.5 h-full"> size="small"
<div class="font-weight-bold text-[0.7rem]">{{ table.column.title }}</div> dropdown-class-name="!w-64"
@change="onDataTypeChange"
<div class="text-[0.5rem]">({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})</div> >
<a-select-option v-for="(table, index) in refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row space-x-0.5 h-full pb-0.5 items-center justify-between">
<div class="font-semibold text-xs">{{ table.column.title }}</div>
<div class="text-[0.65rem] text-gray-600">
{{ relationNames[table.col.type] }} {{ table.title || table.table_name }}
</div>
</div> </div>
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item class="flex w-1/2" :label="$t('labels.childColumn')" v-bind="validateInfos.fk_lookup_column_id"> <a-form-item class="flex w-1/2" :label="$t('labels.childColumn')" v-bind="validateInfos.fk_lookup_column_id">
<a-select <a-select
v-model:value="formState.fk_lookup_column_id" v-model:value="formState.fk_lookup_column_id"

2
packages/nc-gui-v2/components/smartsheet-column/RatingOptions.vue

@ -3,7 +3,7 @@ import { Sketch } from '@ckpack/vue-color'
import { useColumnCreateStoreOrThrow } from '#imports' import { useColumnCreateStoreOrThrow } from '#imports'
import { enumColor, getMdiIcon } from '@/utils' import { enumColor, getMdiIcon } from '@/utils'
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow() const { formState } = useColumnCreateStoreOrThrow()
// cater existing v1 cases // cater existing v1 cases
const iconList = [ const iconList = [

18
packages/nc-gui-v2/components/smartsheet-column/RollupOptions.vue

@ -4,7 +4,6 @@ import { inject, useColumnCreateStoreOrThrow, useMetas, useProject } from '#impo
import { MetaInj } from '~/context' import { MetaInj } from '~/context'
const { formState, validateInfos, onDataTypeChange, setAdditionalValidations } = $(useColumnCreateStoreOrThrow()) const { formState, validateInfos, onDataTypeChange, setAdditionalValidations } = $(useColumnCreateStoreOrThrow())
const { tables } = $(useProject()) const { tables } = $(useProject())
const meta = $(inject(MetaInj)!) const meta = $(inject(MetaInj)!)
@ -69,11 +68,18 @@ const columns = $computed(() => {
<div class="p-4 w-full flex flex-col border-2 mb-2 mt-4"> <div class="p-4 w-full flex flex-col border-2 mb-2 mt-4">
<div class="w-full flex flex-row space-x-2"> <div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id"> <a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-select v-model:value="formState.fk_relation_column_id" size="small" @change="onDataTypeChange"> <a-select
<a-select-option v-for="(table, index) of refTables" :key="index" :value="table.col.fk_column_id"> v-model:value="formState.fk_relation_column_id"
<div class="flex flex-row items-center space-x-0.5"> size="small"
<div class="font-weight-bold text-xs">{{ table.column.title }}</div> dropdown-class-name="!w-64"
<div class="text-[0.45rem]">({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})</div> @change="onDataTypeChange"
>
<a-select-option v-for="(table, index) in refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row space-x-0.5 h-full pb-0.5 items-center justify-between">
<div class="font-semibold text-xs">{{ table.column.title }}</div>
<div class="text-[0.65rem] text-gray-600">
({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})
</div>
</div> </div>
</a-select-option> </a-select-option>
</a-select> </a-select>

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

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onClickOutside } from '@vueuse/core'
import { Modal } from 'ant-design-vue' import { Modal } from 'ant-design-vue'
import { inject } from 'vue' import { inject } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -55,38 +56,61 @@ function onVisibleChange() {
// by clicking cancel button // by clicking cancel button
editColumnDropdown.value = true editColumnDropdown.value = true
} }
const editOrAddCard = ref()
onClickOutside(editOrAddCard, () => {
editColumnDropdown.value = false
})
</script> </script>
<template> <template>
<a-dropdown v-model:visible="editColumnDropdown" :trigger="['click']" @visible-change="onVisibleChange"> <a-dropdown v-model:visible="editColumnDropdown" :trigger="['click']" @visible-change="onVisibleChange">
<span /> <span />
<template #overlay> <template #overlay>
<SmartsheetColumnEditOrAdd :edit-column-dropdown="editColumnDropdown" @click.stop @cancel="editColumnDropdown = false" /> <SmartsheetColumnEditOrAdd
ref="editOrAddCard"
:edit-column-dropdown="editColumnDropdown"
@click.stop
@cancel="editColumnDropdown = false"
/>
</template> </template>
</a-dropdown> </a-dropdown>
<a-dropdown :trigger="['hover']"> <a-dropdown :trigger="['hover']">
<MdiMenuDownIcon class="text-grey nc-ui-dt-dropdown" /> <MdiMenuDownIcon class="text-grey nc-ui-dt-dropdown" />
<template #overlay> <template #overlay>
<div class="shadow bg-white"> <a-menu class="shadow bg-white">
<div class="nc-column-edit nc-menu-item" @click="editColumnDropdown = true"> <a-menu-item @click="editColumnDropdown = true">
<div class="nc-column-edit nc-header-menu-item">
<MdiEditIcon class="text-primary" /> <MdiEditIcon class="text-primary" />
<!-- Edit --> <!-- Edit -->
{{ $t('general.edit') }} {{ $t('general.edit') }}
</div> </div>
<div v-if="!virtual" v-t="['a:column:set-primary']" class="nc-menu-item" @click="setAsPrimaryValue"> </a-menu-item>
<a-menu-item v-if="!virtual" v-t="['a:column:set-primary']" @click="setAsPrimaryValue">
<div class="nc-column-edit nc-header-menu-item">
<MdiStarIcon class="text-primary" /> <MdiStarIcon class="text-primary" />
<!-- todo : tooltip --> <!-- todo : tooltip -->
<!-- Set as Primary value --> <!-- Set as Primary value -->
{{ $t('activity.setPrimary') }} {{ $t('activity.setPrimary') }}
<!-- <span class="caption font-weight-bold">Primary value will be shown in place of primary key</span> -->
</div> </div>
<div class="nc-column-delete nc-menu-item" @click="deleteColumn"> <!-- <span class="caption font-weight-bold">Primary value will be shown in place of primary key</span> -->
</a-menu-item>
<a-menu-item @click="deleteColumn">
<div class="nc-column-delete nc-header-menu-item">
<MdiDeleteIcon class="text-error" /> <MdiDeleteIcon class="text-error" />
<!-- Delete --> <!-- Delete -->
{{ $t('general.delete') }} {{ $t('general.delete') }}
</div> </div>
</div> </a-menu-item>
</a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
</template> </template>
<style scoped>
.nc-header-menu-item {
@apply text-xs flex items-center px-1 py-2 gap-1;
}
</style>

2
packages/nc-gui-v2/components/smartsheet-header/VirtualCell.vue

@ -16,7 +16,7 @@ provide(ColumnInj, column)
const { metas } = useMetas() const { metas } = useMetas()
const meta = inject(MetaInj) const meta = inject(MetaInj)
const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualCell(column) const { isLookup, isBt, isRollup, isMm, isHm, isFormula } = useVirtualCell(column)
const colOptions = $computed(() => column.value?.colOptions) const colOptions = $computed(() => column.value?.colOptions)
const tableTile = $computed(() => meta?.value?.title) const tableTile = $computed(() => meta?.value?.title)

3
packages/nc-gui-v2/components/smartsheet-header/VirtualCellIcon.vue

@ -11,6 +11,7 @@ import FormulaIcon from '~icons/mdi/math-integral'
import RollupIcon from '~icons/mdi/movie-roll' import RollupIcon from '~icons/mdi/movie-roll'
import CountIcon from '~icons/mdi/counter' import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings' import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before'
const props = defineProps<{ columnMeta?: ColumnType }>() const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta') const columnMeta = toRef(props, 'columnMeta')
@ -34,7 +35,7 @@ const icon = computed(() => {
case UITypes.Formula: case UITypes.Formula:
return FormulaIcon return FormulaIcon
case UITypes.Lookup: case UITypes.Lookup:
return GenericIcon return TableColumnPlusBefore
case UITypes.Rollup: case UITypes.Rollup:
return RollupIcon return RollupIcon
case UITypes.Count: case UITypes.Count:

3
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue

@ -5,7 +5,7 @@ import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
import { inject, useViewFilters } from '#imports' import { inject, useViewFilters } from '#imports'
import { comparisonOpList } from '~/utils/filterUtils' import { comparisonOpList } from '~/utils/filterUtils'
import { ActiveViewInj, IsLockedInj, MetaInj, ReloadViewDataHookInj } from '~/context' import { ActiveViewInj, MetaInj, ReloadViewDataHookInj } from '~/context'
import MdiDeleteIcon from '~icons/mdi/close-box' import MdiDeleteIcon from '~icons/mdi/close-box'
import MdiAddIcon from '~icons/mdi/plus' import MdiAddIcon from '~icons/mdi/plus'
@ -16,7 +16,6 @@ const emit = defineEmits(['update:filtersLength'])
const meta = inject(MetaInj) const meta = inject(MetaInj)
const activeView = inject(ActiveViewInj) const activeView = inject(ActiveViewInj)
const reloadDataHook = inject(ReloadViewDataHookInj) const reloadDataHook = inject(ReloadViewDataHookInj)
const isLocked = inject(IsLockedInj)
// todo: replace with inject or get from state // todo: replace with inject or get from state
const shared = ref(false) const shared = ref(false)

2
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilterMenu.vue

@ -1,12 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { watchEffect } from '@vue/runtime-core' import { watchEffect } from '@vue/runtime-core'
import type ColumnFilter from './ColumnFilter.vue' import type ColumnFilter from './ColumnFilter.vue'
import { useState } from '#app'
import { ActiveViewInj, IsLockedInj } from '~/context' import { ActiveViewInj, IsLockedInj } from '~/context'
import MdiFilterIcon from '~icons/mdi/filter-outline' import MdiFilterIcon from '~icons/mdi/filter-outline'
import MdiMenuDownIcon from '~icons/mdi/menu-down' import MdiMenuDownIcon from '~icons/mdi/menu-down'
const autoApplyFilter = useState('autoApplyFilter', () => false)
const isLocked = inject(IsLockedInj) const isLocked = inject(IsLockedInj)
const activeView = inject(ActiveViewInj) const activeView = inject(ActiveViewInj)

23
packages/nc-gui-v2/components/smartsheet-toolbar/FieldListAutoCompleteDropdown.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SelectProps } from 'ant-design-vue' import type { SelectProps } from 'ant-design-vue'
import { isVirtualCol } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import { computed } from 'vue' import { computed } from 'vue'
import { MetaInj } from '~/context' import { MetaInj } from '~/context'
import VirtualCellIcon from '~/components/smartsheet-header/VirtualCellIcon.vue' import VirtualCellIcon from '~/components/smartsheet-header/VirtualCellIcon.vue'
@ -8,9 +9,10 @@ import CellIcon from '~/components/smartsheet-header/CellIcon.vue'
interface Props { interface Props {
modelValue?: string modelValue?: string
isSort?: boolean
} }
const { modelValue } = defineProps<Props>() const { modelValue, isSort } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
@ -48,7 +50,19 @@ const localValue = computed({
}, },
} */ } */
const options = computed<SelectProps['options']>(() => const options = computed<SelectProps['options']>(() =>
meta?.value?.columns?.map((c) => ({ meta?.value?.columns
?.filter((c: ColumnType) => {
/** ignore hasmany and manytomany relations if it's using within sort menu */
if (isSort) {
return !(
c.uidt === UITypes.LinkToAnotherRecord && (c.colOptions as LinkToAnotherRecordType).type !== RelationTypes.BELONGS_TO
)
/** ignore vutual fields which are system fields ( mm relation ) */
} else {
return !c.colOptions || !c.system
}
})
.map((c: ColumnType) => ({
value: c.id, value: c.id,
label: c.title, label: c.title,
icon: h(isVirtualCol(c) ? VirtualCellIcon : CellIcon, { icon: h(isVirtualCol(c) ? VirtualCellIcon : CellIcon, {
@ -75,7 +89,8 @@ const filterOption = (input: string, option: any) => {
> >
<a-select-option v-for="option in options" :key="option.value" :value="option.value"> <a-select-option v-for="option in options" :key="option.value" :value="option.value">
<div class="flex gap-2 text-xs items-center align-center h-full"> <div class="flex gap-2 text-xs items-center align-center h-full">
<component :is="option.icon" class="min-w-5 !mx-0" /> <span class="min-w-0"> {{ option.label }}</span> <component :is="option.icon" class="min-w-5 !mx-0" />
<span class="min-w-0"> {{ option.label }}</span>
</div> </div>
</a-select-option> </a-select-option>
</a-select> </a-select>

5
packages/nc-gui-v2/components/smartsheet-toolbar/MoreActions.vue

@ -18,11 +18,6 @@ const sharedViewListDlg = ref(false)
// todo : replace with inject // todo : replace with inject
const publicViewId = null const publicViewId = null
// TODO:: identify based on meta
const isView = ref(false)
const { isUIAllowed } = useUIPermission()
const { project } = useProject() const { project } = useProject()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()

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

@ -10,7 +10,6 @@ import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiOpenInNewIcon from '~icons/mdi/open-in-new' import MdiOpenInNewIcon from '~icons/mdi/open-in-new'
import MdiCopyIcon from '~icons/mdi/content-copy' import MdiCopyIcon from '~icons/mdi/content-copy'
const { isUIAllowed } = useUIPermission()
const { view, $api } = useSmartsheetStoreOrThrow() const { view, $api } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard() const { copy } = useClipboard()

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

@ -3,7 +3,6 @@ import { useClipboard } from '@vueuse/core'
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { useRoute } from '#app'
import { onMounted, useSmartsheetStoreOrThrow } from '#imports' import { onMounted, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiVisibilityOnIcon from '~icons/mdi/visibility' import MdiVisibilityOnIcon from '~icons/mdi/visibility'
@ -20,18 +19,14 @@ interface SharedViewType {
showPassword?: boolean showPassword?: boolean
} }
const { view, $api, meta } = useSmartsheetStoreOrThrow() const { $api, meta } = useSmartsheetStoreOrThrow()
const { copy } = useClipboard() const { copy } = useClipboard()
const toast = useToast() const toast = useToast()
const route = useRoute()
const { dashboardUrl } = useDashboard() const { dashboardUrl } = useDashboard()
let isLoading = $ref(false)
// let activeSharedView = $ref(null)
const sharedViewList = ref<SharedViewType[]>() const sharedViewList = ref<SharedViewType[]>()
const loadSharedViewsList = async () => { const loadSharedViewsList = async () => {
isLoading = true
const list = await $api.dbViewShare.list(meta.value?.id as string) const list = await $api.dbViewShare.list(meta.value?.id as string)
console.log(unref(sharedViewList)) console.log(unref(sharedViewList))
@ -49,8 +44,6 @@ const loadSharedViewsList = async () => {
// } else { // } else {
// activeSharedView = null // activeSharedView = null
// } // }
isLoading = false
} }
onMounted(loadSharedViewsList) onMounted(loadSharedViewsList)

1
packages/nc-gui-v2/components/smartsheet-toolbar/SortListMenu.vue

@ -59,6 +59,7 @@ watch(
v-model="sort.fk_column_id" v-model="sort.fk_column_id"
class="caption nc-sort-field-select" class="caption nc-sort-field-select"
:columns="columns" :columns="columns"
is-sort
@click.stop @click.stop
@update:model-value="saveOrUpdate(sort, i)" @update:model-value="saveOrUpdate(sort, i)"
/> />

10
packages/nc-gui-v2/components/smartsheet/Cell.vue

@ -10,14 +10,13 @@ interface Props {
column: ColumnType column: ColumnType
modelValue: any modelValue: any
editEnabled: boolean editEnabled: boolean
} rowIndex: number
interface Emits {
(event: 'update:modelValue', value: any): void
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'save', 'navigate', 'update:editEnabled']) const emit = defineEmits(['update:modelValue', 'save', 'navigate', 'update:editEnabled'])
const column = toRef(props, 'column') const column = toRef(props, 'column')
provide(ColumnInj, column) provide(ColumnInj, column)
@ -25,6 +24,7 @@ provide(ColumnInj, column)
provide(EditModeInj, useVModel(props, 'editEnabled', emit)) provide(EditModeInj, useVModel(props, 'editEnabled', emit))
let changed = $ref(false) let changed = $ref(false)
const syncValue = useDebounceFn(function () { const syncValue = useDebounceFn(function () {
changed = false changed = false
emit('save') emit('save')
@ -114,7 +114,7 @@ const syncAndNavigate = (dir: NavigateDir) => {
> >
<CellTextArea v-if="isTextArea" v-model="vModel" /> <CellTextArea v-if="isTextArea" v-model="vModel" />
<CellCheckbox v-else-if="isBoolean" v-model="vModel" /> <CellCheckbox v-else-if="isBoolean" v-model="vModel" />
<CellAttachment v-else-if="isAttachment" v-model="vModel" /> <CellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" />
<CellSingleSelect v-else-if="isSingleSelect" v-model="vModel" /> <CellSingleSelect v-else-if="isSingleSelect" v-model="vModel" />
<CellMultiSelect v-else-if="isMultiSelect" v-model="vModel" /> <CellMultiSelect v-else-if="isMultiSelect" v-model="vModel" />
<CellDatePicker v-else-if="isDate" v-model="vModel" /> <CellDatePicker v-else-if="isDate" v-model="vModel" />

6
packages/nc-gui-v2/components/smartsheet/Gallery.vue

@ -11,12 +11,6 @@ interface Attachment {
const meta = inject(MetaInj) const meta = inject(MetaInj)
const view = inject(ActiveViewInj) const view = inject(ActiveViewInj)
// todo: get from parent ( inject or use prop )
const isPublicView = false
const selected = reactive<{ row?: number | null; col?: number | null }>({})
const editEnabled = ref(false)
const { loadData, paginationData, formattedData: data, loadGalleryData, galleryData, changePage } = useViewData(meta, view as any) const { loadData, paginationData, formattedData: data, loadGalleryData, galleryData, changePage } = useViewData(meta, view as any)
provide(IsFormInj, false) provide(IsFormInj, false)

40
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onClickOutside, useEventListener } from '@vueuse/core' import { onClickOutside, useEventListener } from '@vueuse/core'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { isVirtualCol } from 'nocodb-sdk' import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { import {
inject, inject,
@ -228,6 +228,12 @@ useEventListener(document, 'keydown', onKeyDown)
/** On clicking outside of table reset active cell */ /** On clicking outside of table reset active cell */
const smartTable = ref(null) const smartTable = ref(null)
onClickOutside(smartTable, () => { onClickOutside(smartTable, () => {
if (selected.col === null) return
const activeCol = fields.value[selected.col]
if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return
selected.row = null selected.row = null
selected.col = null selected.col = null
}) })
@ -255,7 +261,7 @@ const onNavigate = (dir: NavigateDir) => {
<a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']"> <a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']">
<table ref="smartTable" class="xc-row-table nc-grid backgroundColorDefault" @contextmenu.prevent="contextMenu = true"> <table ref="smartTable" class="xc-row-table nc-grid backgroundColorDefault" @contextmenu.prevent="contextMenu = true">
<thead> <thead>
<tr class="group"> <tr>
<th> <th>
<div class="flex align-center w-[80px]"> <div class="flex align-center w-[80px]">
<div class="group-hover:hidden" :class="{ hidden: selectedAllRecords }">#</div> <div class="group-hover:hidden" :class="{ hidden: selectedAllRecords }">#</div>
@ -264,6 +270,7 @@ const onNavigate = (dir: NavigateDir) => {
class="group-hover:flex w-full align-center" class="group-hover:flex w-full align-center"
> >
<a-checkbox v-model:checked="selectedAllRecords" /> <a-checkbox v-model:checked="selectedAllRecords" />
<span class="flex-1" /> <span class="flex-1" />
</div> </div>
</div> </div>
@ -279,6 +286,7 @@ const onNavigate = (dir: NavigateDir) => {
@xcresized="resizingCol = null" @xcresized="resizingCol = null"
> >
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" /> <SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" />
<SmartsheetHeaderCell v-else :column="col" /> <SmartsheetHeaderCell v-else :column="col" />
</th> </th>
<!-- v-if="!isLocked && !isVirtual && !isPublicView && _isUIAllowed('add-column')" --> <!-- v-if="!isLocked && !isVirtual && !isPublicView && _isUIAllowed('add-column')" -->
@ -287,6 +295,7 @@ const onNavigate = (dir: NavigateDir) => {
<div class="h-full w-[60px] flex align-center justify-center"> <div class="h-full w-[60px] flex align-center justify-center">
<MdiPlus class="text-sm" /> <MdiPlus class="text-sm" />
</div> </div>
<template #overlay> <template #overlay>
<SmartsheetColumnEditOrAdd @click.stop @cancel="addColumnDropdown = false" /> <SmartsheetColumnEditOrAdd @click.stop @cancel="addColumnDropdown = false" />
</template> </template>
@ -295,7 +304,7 @@ const onNavigate = (dir: NavigateDir) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(row, rowIndex) of data" :key="rowIndex" class="nc-grid-row"> <tr v-for="(row, rowIndex) of data" :key="rowIndex" class="nc-grid-row group">
<td key="row-index" class="caption nc-grid-cell group"> <td key="row-index" class="caption nc-grid-cell group">
<div class="flex items-center w-[80px]"> <div class="flex items-center w-[80px]">
<div class="group-hover:hidden" :class="{ hidden: row.rowMeta.selected }">{{ rowIndex + 1 }}</div> <div class="group-hover:hidden" :class="{ hidden: row.rowMeta.selected }">{{ rowIndex + 1 }}</div>
@ -304,6 +313,7 @@ const onNavigate = (dir: NavigateDir) => {
class="group-hover:flex w-full items-center justify-between p-1" class="group-hover:flex w-full items-center justify-between p-1"
> >
<a-checkbox v-model:checked="row.rowMeta.selected" /> <a-checkbox v-model:checked="row.rowMeta.selected" />
<span class="flex-1" />
<div class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:bg-primary/10"> <div class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:bg-primary/10">
<MdiArrowExpand class="select-none transform hover:(text-pink-500 scale-120)" /> <MdiArrowExpand class="select-none transform hover:(text-pink-500 scale-120)" />
</div> </div>
@ -313,10 +323,11 @@ const onNavigate = (dir: NavigateDir) => {
<td <td
v-for="(columnObj, colIndex) of fields" v-for="(columnObj, colIndex) of fields"
:key="rowIndex + columnObj.title" :key="rowIndex + columnObj.title"
class="cell pointer nc-grid-cell" class="cell relative cursor-pointer nc-grid-cell"
:class="{ :class="{
active: !isPublicView && selected.col === colIndex && selected.row === rowIndex, active: !isPublicView && selected.col === colIndex && selected.row === rowIndex,
}" }"
:data-key="rowIndex + columnObj.id"
:data-col="columnObj.id" :data-col="columnObj.id"
:data-title="columnObj.title" :data-title="columnObj.title"
@click="selectCell(rowIndex, colIndex)" @click="selectCell(rowIndex, colIndex)"
@ -338,6 +349,7 @@ const onNavigate = (dir: NavigateDir) => {
v-model="row.row[columnObj.title]" v-model="row.row[columnObj.title]"
:column="columnObj" :column="columnObj"
:edit-enabled="editEnabled && selected.col === colIndex && selected.row === rowIndex" :edit-enabled="editEnabled && selected.col === colIndex && selected.row === rowIndex"
:row-index="rowIndex"
@update:edit-enabled="editEnabled = false" @update:edit-enabled="editEnabled = false"
@save="updateOrSaveRow(row, columnObj.title)" @save="updateOrSaveRow(row, columnObj.title)"
@navigate="onNavigate" @navigate="onNavigate"
@ -366,14 +378,18 @@ const onNavigate = (dir: NavigateDir) => {
</tbody> </tbody>
</table> </table>
<template #overlay> <template #overlay>
<div class="bg-white shadow" @click="contextMenu = false"> <a-menu class="bg-white shadow" @click="contextMenu = false">
<div v-if="contextMenuTarget" class="nc-menu-item" @click="deleteRow(contextMenuTarget.row)">Delete row</div> <a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)"
<div class="nc-menu-item" @click="deleteSelectedRows">Delete all selected rows</div> ><span class="text-xs">Delete row</span></a-menu-item
<div v-if="contextMenuTarget" class="nc-menu-item" @click="clearCell(contextMenuTarget)">Clear cell</div> >
<div v-if="contextMenuTarget" class="nc-menu-item" @click="addEmptyRow(contextMenuTarget.row + 1)"> <a-menu-item @click="deleteSelectedRows"><span class="text-xs">Delete all selected rows</span></a-menu-item>
Insert new row <a-menu-item v-if="contextMenuTarget" @click="clearCell(contextMenuTarget)"
</div> ><span class="text-xs">Clear cell</span>
</div> </a-menu-item>
<a-menu-item v-if="contextMenuTarget" @click="addEmptyRow(contextMenuTarget.row + 1)">
<span class="text-xs">Insert new row</span>
</a-menu-item>
</a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
</div> </div>

13
packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'nocodb-sdk' import type { ViewType, ViewTypes } from 'nocodb-sdk'
import type { SortableEvent } from 'sortablejs' import type { SortableEvent } from 'sortablejs'
import type { Menu as AntMenu } from 'ant-design-vue' import type { Menu as AntMenu } from 'ant-design-vue'
import { notification } from 'ant-design-vue' import { notification } from 'ant-design-vue'
@ -45,7 +45,7 @@ let isMarked = $ref<string | false>(false)
/** Watch currently active view, so we can mark it in the menu */ /** Watch currently active view, so we can mark it in the menu */
watch(activeView, (nextActiveView) => { watch(activeView, (nextActiveView) => {
const _nextActiveView = nextActiveView as GridType | FormType | KanbanType const _nextActiveView = nextActiveView as ViewType
if (_nextActiveView && _nextActiveView.id) { if (_nextActiveView && _nextActiveView.id) {
selected.value = [_nextActiveView.id] selected.value = [_nextActiveView.id]
@ -66,7 +66,7 @@ function validate(value?: string) {
return 'View name is required' return 'View name is required'
} }
if (views.value.every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== value)) { if (views.value.every((v1) => v1.title !== value)) {
return 'View name should be unique' return 'View name should be unique'
} }
@ -134,14 +134,13 @@ const initSortable = (el: HTMLElement) => {
onMounted(() => menuRef && initSortable(menuRef.$el)) onMounted(() => menuRef && initSortable(menuRef.$el))
// todo: fix view type, alias is missing for some reason?
/** Navigate to view by changing url param */ /** Navigate to view by changing url param */
function changeView(view: { id: string; alias?: string; title?: string; type: ViewTypes }) { function changeView(view: { id: string; alias?: string; title?: string; type: ViewTypes }) {
router.push({ params: { viewTitle: (view.alias ?? view.title) || '' } }) router.push({ params: { viewTitle: view.title || '' } })
} }
/** Rename a view */ /** Rename a view */
async function onRename(view: Record<string, any>) { async function onRename(view: ViewType) {
const valid = validate(view.title) const valid = validate(view.title)
if (valid !== true) { if (valid !== true) {
@ -153,7 +152,7 @@ async function onRename(view: Record<string, any>) {
try { try {
// todo typing issues, order and id do not exist on all members of ViewTypes (Kanban, Gallery, Form, Grid) // todo typing issues, order and id do not exist on all members of ViewTypes (Kanban, Gallery, Form, Grid)
await api.dbView.update(view.id, { await api.dbView.update(view.id!, {
title: view.title, title: view.title,
order: view.order, order: view.order,
}) })

4
packages/nc-gui-v2/components/smartsheet/sidebar/index.vue

@ -3,7 +3,7 @@ import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'noc
import MenuTop from './MenuTop.vue' import MenuTop from './MenuTop.vue'
import MenuBottom from './MenuBottom.vue' import MenuBottom from './MenuBottom.vue'
import Toolbar from './toolbar/index.vue' import Toolbar from './toolbar/index.vue'
import { computed, inject, provide, ref, useApi, useRoute, useViews, watch } from '#imports' import { computed, inject, provide, ref, useRoute, useViews, watch } from '#imports'
import { ActiveViewInj, MetaInj, RightSidebarInj, ViewListInj } from '~/context' import { ActiveViewInj, MetaInj, RightSidebarInj, ViewListInj } from '~/context'
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -12,8 +12,6 @@ const activeView = inject(ActiveViewInj, ref())
const { views, loadViews } = useViews(meta) const { views, loadViews } = useViews(meta)
const { api } = useApi()
const route = useRoute() const route = useRoute()
provide(ViewListInj, views) provide(ViewListInj, views)

3
packages/nc-gui-v2/components/tabs/Smartsheet.vue

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, TableType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import SmartsheetGrid from '../smartsheet/Grid.vue' import SmartsheetGrid from '../smartsheet/Grid.vue'
import { computed, inject, provide, useMetas, useProvideSmartsheetStore, watch, watchEffect } from '#imports' import { computed, inject, provide, useMetas, useProvideSmartsheetStore, watch, watchEffect } from '#imports'

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

@ -10,7 +10,7 @@ interface Props {
const { hook } = defineProps<Props>() const { hook } = defineProps<Props>()
const { $state, $api, $e } = useNuxtApp() const { $api } = useNuxtApp()
const toast = useToast() const toast = useToast()
@ -23,7 +23,7 @@ const activeKey = ref(0)
watch( watch(
() => hook?.operation, () => hook?.operation,
async (v) => { async () => {
await loadSampleData() await loadSampleData()
}, },
) )

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

@ -24,7 +24,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
(meta: Ref<TableType>, column?: Ref<ColumnType>) => { (meta: Ref<TableType>, column?: Ref<ColumnType>) => {
const { sqlUi } = useProject() const { sqlUi } = useProject()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const toast = useToast() const toast = useToast()
const idType = null const idType = null
@ -196,6 +196,11 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
} }
await $api.dbTableColumn.create(meta.value.id as string, formState.value) await $api.dbTableColumn.create(meta.value.id as string, formState.value)
/** if LTAR column then force reload related table meta */
if (formState.value.uidt === UITypes.LinkToAnotherRecord && meta.value.id !== formState.value.childId) {
getMeta(formState.value.childId, true).then(() => {})
}
toast.success('Column created') toast.success('Column created')
} }
onSuccess() onSuccess()

10
packages/nc-gui-v2/layouts/base.vue

@ -1,15 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { breakpointsTailwind } from '@vueuse/core'
import { navigateTo } from '#app' import { navigateTo } from '#app'
import { computed, useBreakpoints, useGlobal, useProject, useRoute } from '#imports' import { computed, useGlobal, useRoute } from '#imports'
/** get current breakpoints (for enabling sidebar) */
const breakpoints = useBreakpoints(breakpointsTailwind)
const { signOut, signedIn, isLoading, user } = useGlobal() const { signOut, signedIn, isLoading, user } = useGlobal()
const { project } = useProject()
const route = useRoute() const route = useRoute()
const email = computed(() => user.value?.email ?? '---') const email = computed(() => user.value?.email ?? '---')
@ -83,7 +77,7 @@ const logout = () => {
</template> </template>
</a-layout-header> </a-layout-header>
<div class="w-full" style="height: calc(100% - var(--header-height))"> <div class="w-full overflow-hidden" style="height: calc(100% - var(--header-height))">
<slot /> <slot />
</div> </div>
</a-layout> </a-layout>

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

@ -1,13 +1,3 @@
<script setup lang="ts">
import { navigateTo, useNuxtApp, useRoute } from '#app'
import MdiAccountCog from '~icons/mdi/account-cog'
import MdiFolderOutline from '~icons/mdi/folder-outline'
const { $api } = useNuxtApp()
const route = useRoute()
</script>
<template> <template>
<NuxtLayout> <NuxtLayout>
<NuxtPage /> <NuxtPage />

4
packages/nc-gui-v2/pages/index/user/index/index.vue

@ -6,7 +6,7 @@ import { reactive, ref, useApi } from '#imports'
import MaterialSymbolsWarning from '~icons/material-symbols/warning' import MaterialSymbolsWarning from '~icons/material-symbols/warning'
import MdiKeyChange from '~icons/mdi/key-change' import MdiKeyChange from '~icons/mdi/key-change'
const { api, isLoading } = useApi() const { api } = useApi()
const { t } = useI18n() const { t } = useI18n()
@ -34,7 +34,7 @@ const formRules = {
{ required: true, message: t('msg.error.signUpRules.passwdRequired') }, { required: true, message: t('msg.error.signUpRules.passwdRequired') },
// Passwords match // Passwords match
{ {
validator: (_: unknown, v: string) => { validator: (_: unknown, _v: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (form.password === form.passwordRepeat) return resolve(true) if (form.password === form.passwordRepeat) return resolve(true)
reject(new Error(t('msg.error.signUpRules.passwdMismatch'))) reject(new Error(t('msg.error.signUpRules.passwdMismatch')))

6
packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue

@ -21,12 +21,6 @@ const currentMenu = ref<string[]>(['addORImport'])
provide(TabMetaInj, activeTab) provide(TabMetaInj, activeTab)
function onEdit(targetKey: number, action: string) {
if (action !== 'add') {
closeTab(targetKey)
}
}
function openQuickImportDialog(type: string) { function openQuickImportDialog(type: string) {
quickImportDialog.value = true quickImportDialog.value = true
importType.value = type importType.value = type

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

@ -4,13 +4,13 @@ import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk' import type { ProjectType } from 'nocodb-sdk'
import { ref } from 'vue' import { ref } from 'vue'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { navigateTo, useNuxtApp, useRoute } from '#app' import { navigateTo, useRoute } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { projectTitleValidator } from '~/utils/validation' import { projectTitleValidator } from '~/utils/validation'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline' import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
import { nextTick, reactive, useSidebar } from '#imports' import { nextTick, reactive, useSidebar } from '#imports'
const { api, isLoading } = useApi() const { api } = useApi()
useSidebar({ hasSidebar: false }) useSidebar({ hasSidebar: false })

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

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUpdated } from '@vue/runtime-core' import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue' import type { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { nextTick, reactive, ref, useApi, useSidebar } from '#imports' import { nextTick, reactive, ref, useApi, useSidebar } from '#imports'
@ -8,10 +8,6 @@ import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { projectTitleValidator } from '~/utils/validation' import { projectTitleValidator } from '~/utils/validation'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline' import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
const name = ref('')
const valid = ref(false)
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { api, isLoading } = useApi() const { api, isLoading } = useApi()

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

@ -56,8 +56,6 @@ const deleteProject = (project: ProjectType) => {
}, },
}) })
} }
const visible = ref(true)
</script> </script>
<template> <template>

9
packages/nocodb-sdk/src/lib/Api.ts

@ -122,7 +122,7 @@ export interface TableType {
export interface ViewType { export interface ViewType {
id?: string; id?: string;
title?: string; title: string;
deleted?: boolean; deleted?: boolean;
order?: number; order?: number;
fk_model_id?: string; fk_model_id?: string;
@ -163,6 +163,7 @@ export interface TableReqType {
pinned?: boolean; pinned?: boolean;
deleted?: boolean; deleted?: boolean;
order?: number; order?: number;
mm?: boolean;
columns?: ColumnType[]; columns?: ColumnType[];
} }
@ -234,6 +235,8 @@ export interface ColumnType {
deleted?: boolean; deleted?: boolean;
visible?: boolean; visible?: boolean;
order?: number; order?: number;
system?: number | boolean;
meta?: any;
colOptions?: colOptions?:
| LinkToAnotherRecordType | LinkToAnotherRecordType
| FormulaType | FormulaType
@ -1515,7 +1518,7 @@ export class Api<
*/ */
reorder: ( reorder: (
tableId: string, tableId: string,
data: { order?: string }, data: { order?: number },
params: RequestParams = {} params: RequestParams = {}
) => ) =>
this.request<void, any>({ this.request<void, any>({
@ -1629,7 +1632,7 @@ export class Api<
update: ( update: (
viewId: string, viewId: string,
data: { data: {
order?: string; order?: number;
title?: string; title?: string;
show_system_fields?: boolean; show_system_fields?: boolean;
lock_type?: 'collaborative' | 'locked' | 'personal'; lock_type?: 'collaborative' | 'locked' | 'personal';

2
packages/nocodb/src/lib/models/View.ts

@ -23,7 +23,7 @@ import extractProps from '../meta/helpers/extractProps';
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
export default class View implements ViewType { export default class View implements ViewType {
id?: string; id?: string;
title?: string; title: string;
uuid?: string; uuid?: string;
password?: string; password?: string;
show: boolean; show: boolean;

17
scripts/sdk/swagger.json

@ -1334,7 +1334,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"order": { "order": {
"type": "string" "type": "number"
} }
} }
} }
@ -1506,7 +1506,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"order": { "order": {
"type": "string" "type": "number"
}, },
"title": { "title": {
"type": "string" "type": "string"
@ -6011,7 +6011,8 @@
"type": "string" "type": "string"
}, },
"title": { "title": {
"type": "string" "type": "string",
"required": true
}, },
"deleted": { "deleted": {
"type": "boolean" "type": "boolean"
@ -6233,6 +6234,9 @@
"order": { "order": {
"type": "number" "type": "number"
}, },
"mm": {
"type": "boolean"
},
"columns": { "columns": {
"type": "array", "type": "array",
"items": { "items": {
@ -6579,6 +6583,13 @@
"order": { "order": {
"type": "number" "type": "number"
}, },
"system": {
"type": [
"number",
"boolean"
]
},
"meta": {},
"colOptions": { "colOptions": {
"oneOf": [ "oneOf": [
{ {

Loading…
Cancel
Save