Browse Source

refactor: rename project and base

- Rename `Project`  => `Base`
- Rename `Base` => `Source`
- Remove `db` from data/meta api endpoints
- Add backward compatibility for old apis
- Migrations for renaming table and columns

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/6545/head
Pranav C 1 year ago
parent
commit
e790abdbaf
  1. 16
      packages/nc-gui/assets/nc-icons/project.svg
  2. 12
      packages/nc-gui/assets/nc-icons/record.svg
  3. 14
      packages/nc-gui/assets/nc-icons/table.svg
  4. 2
      packages/nc-gui/assets/style.scss
  5. 2
      packages/nc-gui/components/account/UsersModal.vue
  6. 6
      packages/nc-gui/components/cell/Checkbox.vue
  7. 8
      packages/nc-gui/components/cell/DateTimePicker.vue
  8. 10
      packages/nc-gui/components/cell/MultiSelect.vue
  9. 8
      packages/nc-gui/components/cell/SingleSelect.vue
  10. 3
      packages/nc-gui/components/cell/Text.vue
  11. 5
      packages/nc-gui/components/cell/TextArea.vue
  12. 6
      packages/nc-gui/components/cell/TimePicker.vue
  13. 6
      packages/nc-gui/components/cell/attachment/utils.ts
  14. 2
      packages/nc-gui/components/dashboard/Sidebar.vue
  15. 6
      packages/nc-gui/components/dashboard/Sidebar/TopSection.vue
  16. 98
      packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue
  17. 30
      packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue
  18. 4
      packages/nc-gui/components/dashboard/TreeView/CreateViewBtn.vue
  19. 329
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  20. 14
      packages/nc-gui/components/dashboard/TreeView/ProjectWrapper.vue
  21. 54
      packages/nc-gui/components/dashboard/TreeView/TableList.vue
  22. 62
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  23. 18
      packages/nc-gui/components/dashboard/TreeView/ViewsList.vue
  24. 8
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  25. 60
      packages/nc-gui/components/dashboard/TreeView/index.vue
  26. 10
      packages/nc-gui/components/dashboard/settings/AuditTab.vue
  27. 16
      packages/nc-gui/components/dashboard/settings/BaseAudit.vue
  28. 122
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  29. 4
      packages/nc-gui/components/dashboard/settings/Erd.vue
  30. 20
      packages/nc-gui/components/dashboard/settings/Metadata.vue
  31. 26
      packages/nc-gui/components/dashboard/settings/Misc.vue
  32. 16
      packages/nc-gui/components/dashboard/settings/Modal.vue
  33. 27
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  34. 14
      packages/nc-gui/components/dashboard/settings/UIAclTabs.vue
  35. 53
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  36. 42
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  37. 22
      packages/nc-gui/components/dlg/AirtableImport.vue
  38. 24
      packages/nc-gui/components/dlg/ProjectDelete.vue
  39. 18
      packages/nc-gui/components/dlg/ProjectDuplicate.vue
  40. 12
      packages/nc-gui/components/dlg/QuickImport.vue
  41. 32
      packages/nc-gui/components/dlg/TableCreate.vue
  42. 20
      packages/nc-gui/components/dlg/TableDelete.vue
  43. 2
      packages/nc-gui/components/dlg/TableDuplicate.vue
  44. 28
      packages/nc-gui/components/dlg/TableRename.vue
  45. 6
      packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue
  46. 4
      packages/nc-gui/components/dlg/share-and-collaborate/ManageUsers.vue
  47. 38
      packages/nc-gui/components/dlg/share-and-collaborate/ShareBase.vue
  48. 2
      packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue
  49. 6
      packages/nc-gui/components/dlg/share-and-collaborate/ShareProject.vue
  50. 20
      packages/nc-gui/components/dlg/share-and-collaborate/View.vue
  51. 16
      packages/nc-gui/components/erd/View.vue
  52. 4
      packages/nc-gui/components/general/AddBaseButton.vue
  53. 10
      packages/nc-gui/components/general/HelpAndSupport.vue
  54. 18
      packages/nc-gui/components/general/MiniSidebar.vue
  55. 10
      packages/nc-gui/components/general/ProjectIcon.vue
  56. 6
      packages/nc-gui/components/general/ShareProject.vue
  57. 1
      packages/nc-gui/components/general/TableIcon.vue
  58. 27
      packages/nc-gui/components/general/UserIcon.vue
  59. 4
      packages/nc-gui/components/general/language/Menu.vue
  60. 4
      packages/nc-gui/components/notification/Item/ProjectEvent.vue
  61. 4
      packages/nc-gui/components/notification/Item/ProjectInvite.vue
  62. 2
      packages/nc-gui/components/notification/Item/SharedViewEvent.vue
  63. 2
      packages/nc-gui/components/notification/Item/TableEvent.vue
  64. 2
      packages/nc-gui/components/notification/Item/ViewEvent.vue
  65. 4
      packages/nc-gui/components/profile/overview/contributionActivity.vue
  66. 16
      packages/nc-gui/components/project/AccessSettings.vue
  67. 50
      packages/nc-gui/components/project/AllTables.vue
  68. 28
      packages/nc-gui/components/project/ImportModal.vue
  69. 20
      packages/nc-gui/components/project/View.vue
  70. 4
      packages/nc-gui/components/shared-view/Grid.vue
  71. 10
      packages/nc-gui/components/smartsheet/ApiSnippet.vue
  72. 6
      packages/nc-gui/components/smartsheet/Cell.vue
  73. 4
      packages/nc-gui/components/smartsheet/Gallery.vue
  74. 4
      packages/nc-gui/components/smartsheet/Kanban.vue
  75. 6
      packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
  76. 8
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  77. 10
      packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue
  78. 8
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  79. 8
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  80. 4
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  81. 10
      packages/nc-gui/components/smartsheet/details/Api.vue
  82. 2
      packages/nc-gui/components/smartsheet/details/Erd.vue
  83. 23
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  84. 115
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  85. 14
      packages/nc-gui/components/smartsheet/grid/Table.vue
  86. 6
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  87. 2
      packages/nc-gui/components/smartsheet/header/DeleteColumnModal.vue
  88. 2
      packages/nc-gui/components/smartsheet/toolbar/Erd.vue
  89. 12
      packages/nc-gui/components/smartsheet/toolbar/ExportSubActions.vue
  90. 6
      packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue
  91. 6
      packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue
  92. 4
      packages/nc-gui/components/smartsheet/toolbar/QrScannerButton.vue
  93. 2
      packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue
  94. 8
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  95. 16
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  96. 8
      packages/nc-gui/components/smartsheet/toolbar/ViewInfo.vue
  97. 4
      packages/nc-gui/components/tabs/Smartsheet.vue
  98. 18
      packages/nc-gui/components/tabs/auth/ApiTokenManagement.vue
  99. 34
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  100. 50
      packages/nc-gui/components/tabs/auth/user-management/ShareBase.vue
  101. Some files were not shown because too many files have changed in this diff Show More

16
packages/nc-gui/assets/nc-icons/project.svg

@ -1,9 +1,9 @@
<svg width="16" height="16" viewBox="0 0 1073 1073" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_1749_80944" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="94" y="40" width="885" height="993">
<path d="M978.723 40H94V1033H978.723V40Z" fill="white"/>
</mask>
<g mask="url(#mask0_1749_80944)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M638.951 291.265L936.342 462.949C966.129 480.145 980.256 502.958 978.723 525.482V774.266C980.256 796.789 966.129 819.602 936.342 836.798L638.951 1008.48C582.292 1041.19 490.431 1041.19 433.773 1008.48L136.381 836.798C106.595 819.602 92.4675 796.789 93.9999 774.266L93.9999 525.482C92.4675 502.957 106.595 480.145 136.381 462.949L433.773 291.265C490.431 258.556 582.292 258.556 638.951 291.265Z" fill="#142966"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M638.951 65.0055L936.342 236.69C966.129 253.886 980.256 276.699 978.723 299.222V548.006C980.256 570.529 966.129 593.343 936.342 610.538L638.951 782.223C582.292 814.931 490.431 814.931 433.773 782.223L136.381 610.538C106.595 593.343 92.4675 570.529 93.9999 548.006L93.9999 299.222C92.4675 276.699 106.595 253.886 136.381 236.69L433.773 65.0055C490.431 32.2968 582.292 32.2968 638.951 65.0055Z" fill="#36BFFF"/>
</g>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99294 14.8095L14.548 12.28C14.8177 12.1572 14.9569 11.9975 14.9659 11.8367H1C1.00896 11.9975 1.14826 12.1572 1.4179 12.28L6.97294 14.8095C7.53075 15.0635 8.43514 15.0635 8.99294 14.8095Z" fill="#142966"/>
<path d="M14.9999 9.27893H1.00513V11.8367H14.9999V9.27893Z" fill="#142966"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99294 12.2518L14.548 9.72223C14.8177 9.59946 14.9569 9.4398 14.9659 9.27893H1C1.00896 9.4398 1.14826 9.59946 1.4179 9.72223L6.97294 12.2518C7.53075 12.5058 8.43514 12.5058 8.99294 12.2518Z" fill="#142966"/>
<path d="M14.9999 6.72107H1.00513V9.27885H14.9999V6.72107Z" fill="#142966"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99294 10.9729L14.548 8.44332C14.8177 8.32054 14.9569 8.16088 14.9659 8H1C1.00896 8.16088 1.14826 8.32054 1.4179 8.44332L6.97294 10.9729C7.53075 11.2269 8.43514 11.2269 8.99294 10.9729Z" fill="#36BFFF"/>
<path d="M14.9997 4.16309H1.00491V8.00309H14.9997V4.16309Z" fill="#36BFFF"/>
<path d="M14.5484 4.63991L8.99337 7.16947C8.43561 7.42348 7.53121 7.42348 6.9734 7.16947L1.41836 4.63991C0.860546 4.3859 0.860546 3.97408 1.41836 3.72007L6.9734 1.19051C7.53121 0.936498 8.43561 0.936498 8.99337 1.19051L14.5484 3.72007C15.1063 3.97408 15.1063 4.3859 14.5484 4.63991Z" fill="#36BFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

12
packages/nc-gui/assets/nc-icons/record.svg

@ -1,12 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1409_68546)">
<path d="M12.3571 5.96842L4.64282 10.4219" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="1.15184" width="9.06208" height="9.06208" rx="1.335" transform="matrix(0.866044 -0.499967 0.866044 0.499967 -0.345705 8.77119)" stroke="#1F293A" stroke-width="1.33"/>
<path d="M4 6.33984L11.7143 10.7933" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1409_68546">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 712 B

14
packages/nc-gui/assets/nc-icons/table.svg

@ -1,12 +1,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1613_80692)">
<path d="M11.8571 5.96903L4.14285 10.4225" stroke="#374151" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="1.15184" width="9.06208" height="9.06208" rx="1.335" transform="matrix(0.866044 -0.499967 0.866044 0.499967 -0.845705 8.77156)" stroke="#374151" stroke-width="1.33"/>
<path d="M3.5 6.34009L11.2143 10.7935" stroke="#374151" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1409_68546)">
<path d="M12.3571 5.96842L4.64282 10.4219" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="1.15184" width="9.06208" height="9.06208" rx="1.335" transform="matrix(0.866044 -0.499967 0.866044 0.499967 -0.345705 8.77119)" stroke="#1F293A" stroke-width="1.33"/>
<path d="M4 6.33984L11.7143 10.7933" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1613_80692">
<rect width="16" height="16" fill="white"/>
<clipPath id="clip0_1409_68546">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 712 B

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

@ -193,7 +193,7 @@ a {
}
}
.nc-project-menu-item {
.nc-base-menu-item {
@apply cursor-pointer flex items-center gap-2 py-2 after:(content-[''] absolute top-0 left-0 bottom-0 right-0 w-full h-full bg-current opacity-0 transition transition-opacity duration-100) hover:(after:(opacity-5));
// &:hover {

2
packages/nc-gui/components/account/UsersModal.vue

@ -79,7 +79,7 @@ const copyUrl = async () => {
try {
await copy(inviteUrl.value)
// Copied shareable base url to clipboard!
// Copied shareable source url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
} catch (e: any) {
message.error(e.message)

6
packages/nc-gui/components/cell/Checkbox.vue

@ -8,7 +8,7 @@ import {
getMdiIcon,
inject,
parseProp,
useProject,
useBase,
useSelectedCellKeyupListener,
} from '#imports'
@ -28,7 +28,7 @@ const emits = defineEmits<Emits>()
const active = inject(ActiveCellInj, ref(false))
const { isMssql } = useProject()
const { isMssql } = useBase()
const column = inject(ColumnInj)
@ -53,7 +53,7 @@ const checkboxMeta = computed(() => {
const vModel = computed<boolean | number>({
get: () => !!props.modelValue && props.modelValue !== '0' && props.modelValue !== 0 && props.modelValue !== 'false',
set: (val: any) => emits('update:modelValue', isMssql(column?.value?.base_id) ? +val : val),
set: (val: any) => emits('update:modelValue', isMssql(column?.value?.source_id) ? +val : val),
})
function onClick(force?: boolean, event?: MouseEvent) {

8
packages/nc-gui/components/cell/DateTimePicker.vue

@ -13,7 +13,7 @@ import {
parseProp,
ref,
timeFormats,
useProject,
useBase,
useSelectedCellKeyupListener,
watch,
} from '#imports'
@ -28,7 +28,7 @@ const { modelValue, isPk, isUpdatedFromCopyNPaste } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const { isMssql, isXcdbBase } = useProject()
const { isMssql, isXcdbBase } = useBase()
const { showNull } = useGlobal()
@ -67,7 +67,7 @@ const localState = computed({
return undefined
}
const isXcDB = isXcdbBase(column.value.base_id)
const isXcDB = isXcdbBase(column.value.source_id)
// cater copy and paste
// when copying a datetime cell, the copied value would be local time
@ -83,7 +83,7 @@ const localState = computed({
return /^\d+$/.test(modelValue) ? dayjs(+modelValue) : dayjs(modelValue)
}
if (isMssql(column.value.base_id)) {
if (isMssql(column.value.source_id)) {
// e.g. 2023-04-29T11:41:53.000Z
return dayjs(modelValue)
}

10
packages/nc-gui/components/cell/MultiSelect.vue

@ -25,7 +25,7 @@ import {
ref,
useEventListener,
useMetas,
useProject,
useBase,
useRoles,
useSelectedCellKeyupListener,
watch,
@ -81,7 +81,7 @@ const { getMeta } = useMetas()
const { isUIAllowed } = useRoles()
const { isPg, isMysql } = useProject()
const { isPg, isMysql } = useBase()
// a variable to keep newly created options value
// temporary until it's add the option to column meta
@ -133,7 +133,7 @@ const vModel = computed({
const selectedTitles = computed(() =>
modelValue
? typeof modelValue === 'string'
? isMysql(column.value.base_id)
? isMysql(column.value.source_id)
? modelValue.split(',').sort((a, b) => {
const opa = options.value.find((el) => el.title === a)
const opb = options.value.find((el) => el.title === b)
@ -247,7 +247,7 @@ async function addIfMissingAndSave() {
// todo: refactor and avoid repetition
if (updatedColMeta.cdf) {
// Postgres returns default value wrapped with single quotes & casted with type so we have to get value between single quotes to keep it unified for all databases
if (isPg(column.value.base_id)) {
if (isPg(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.substring(
updatedColMeta.cdf.indexOf(`'`) + 1,
updatedColMeta.cdf.lastIndexOf(`'`),
@ -255,7 +255,7 @@ async function addIfMissingAndSave() {
}
// Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped
if (!isMysql(column.value.base_id)) {
if (!isMysql(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'")
}
}

8
packages/nc-gui/components/cell/SingleSelect.vue

@ -21,7 +21,7 @@ import {
isDrawerOrModalExist,
ref,
useEventListener,
useProject,
useBase,
useRoles,
useSelectedCellKeyupListener,
watch,
@ -71,7 +71,7 @@ const { getMeta } = useMetas()
const { isUIAllowed } = useRoles()
const { isPg, isMysql } = useProject()
const { isPg, isMysql } = useBase()
// a variable to keep newly created option value
// temporary until it's add the option to column meta
@ -175,7 +175,7 @@ async function addIfMissingAndSave() {
// todo: refactor and avoid repetition
if (updatedColMeta.cdf) {
// Postgres returns default value wrapped with single quotes & casted with type so we have to get value between single quotes to keep it unified for all databases
if (isPg(column.value.base_id)) {
if (isPg(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.substring(
updatedColMeta.cdf.indexOf(`'`) + 1,
updatedColMeta.cdf.lastIndexOf(`'`),
@ -183,7 +183,7 @@ async function addIfMissingAndSave() {
}
// Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped
if (!isMysql(column.value.base_id)) {
if (!isMysql(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'")
}
}

3
packages/nc-gui/components/cell/Text.vue

@ -34,9 +34,6 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
v-model="vModel"
class="h-full w-full outline-none p-2 bg-transparent"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
:class="{
'px-1': isExpandedFormOpen,
}"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop

5
packages/nc-gui/components/cell/TextArea.vue

@ -86,7 +86,6 @@ onClickOutside(inputWrapperRef, (e) => {
:class="{
'p-2': editEnabled,
'py-1 h-full': isForm,
'px-1': isExpandedFormOpen,
}"
:style="{
minHeight: `${height}px`,
@ -113,8 +112,8 @@ onClickOutside(inputWrapperRef, (e) => {
<span v-else>{{ vModel }}</span>
<div
v-if="active && !isExpandedFormOpen"
class="!absolute right-0 bottom-0 h-6 w-5 group cursor-pointer flex justify-end gap-1 items-center rounded border-none p-1 hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
v-if="active"
class="!absolute right-0 bottom-0 h-6 w-5 group cursor-pointer flex justify-end gap-1 items-center active:(ring ring-accent ring-opacity-100) rounded border-none p-1 hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
:class="{ 'right-2 bottom-2': editEnabled }"
data-testid="attachment-cell-file-picker-button"
@click.stop="isVisible = !isVisible"

6
packages/nc-gui/components/cell/TimePicker.vue

@ -6,7 +6,7 @@ import {
ReadonlyInj,
inject,
onClickOutside,
useProject,
useBase,
useSelectedCellKeyupListener,
watch,
} from '#imports'
@ -20,7 +20,7 @@ const { modelValue, isPk } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const { isMysql } = useProject()
const { isMysql } = useBase()
const { showNull } = useGlobal()
@ -36,7 +36,7 @@ const column = inject(ColumnInj)!
const isTimeInvalid = ref(false)
const dateFormat = isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'
const dateFormat = isMysql(column.value.source_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'
const { t } = useI18n()

6
packages/nc-gui/components/cell/attachment/utils.ts

@ -20,7 +20,7 @@ import {
useFileDialog,
useI18n,
useInjectionState,
useProject,
useBase,
watch,
} from '#imports'
import MdiPdfBox from '~icons/mdi/pdf-box'
@ -55,7 +55,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
/** for image carousel */
const selectedImage = ref()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { api, isLoading } = useApi()
@ -191,7 +191,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
try {
const data = await api.storage.upload(
{
path: [NOCO, project.value.title, meta.value?.title, column.value?.title].join('/'),
path: [NOCO, base.value.title, meta.value?.title, column.value?.title].join('/'),
},
{
files,

2
packages/nc-gui/components/dashboard/Sidebar.vue

@ -3,7 +3,7 @@ const workspaceStore = useWorkspace()
const { isWorkspaceLoading } = storeToRefs(workspaceStore)
const { isSharedBase } = storeToRefs(useProject())
const { isSharedBase } = storeToRefs(useBase())
const { isMobileMode } = useGlobal()

6
packages/nc-gui/components/dashboard/Sidebar/TopSection.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
const workspaceStore = useWorkspace()
const projectStore = useProject()
const baseStore = useBase()
const { isUIAllowed } = useRoles()
@ -10,7 +10,7 @@ const { isWorkspaceLoading, isWorkspaceSettingsPageOpened } = storeToRefs(worksp
const { navigateToWorkspaceSettings } = workspaceStore
const { isSharedBase } = storeToRefs(projectStore)
const { isSharedBase } = storeToRefs(baseStore)
const isCreateProjectOpen = ref(false)
@ -70,7 +70,7 @@ const navigateToSettings = () => {
modal
type="text"
class="nc-sidebar-top-button !hover:bg-gray-200 !xs:hidden"
data-testid="nc-sidebar-create-project-btn"
data-testid="nc-sidebar-create-base-btn"
>
<div class="gap-x-2 flex flex-row w-full items-center !font-normal">
<GeneralIcon icon="plus" />

98
packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { ProjectType } from 'nocodb-sdk'
import type { BaseType } from 'nocodb-sdk'
import { storeToRefs } from 'pinia'
import { toRef } from '@vue/reactivity'
import { resolveComponent } from '@vue/runtime-core'
@ -8,11 +8,11 @@ import { ProjectRoleInj, useDialog, useRoles } from '#imports'
const props = withDefaults(
defineProps<{
project: ProjectType
baseIndex?: number
base: BaseType
sourceIndex?: number
}>(),
{
baseIndex: 0,
sourceIndex: 0,
},
)
@ -22,18 +22,18 @@ const emit = defineEmits<{
const { isUIAllowed } = useRoles()
const project = toRef(props, 'project')
const base = toRef(props, 'base')
const { $e } = useNuxtApp()
const projectStore = useProject()
const baseStore = useBase()
const { isSharedBase } = storeToRefs(projectStore)
const { isSharedBase } = storeToRefs(baseStore)
const projectRole = inject(ProjectRoleInj)
const baseRole = inject(ProjectRoleInj)
function openSchemaMagicDialog(baseId?: string) {
if (!baseId) return
function openSchemaMagicDialog(sourceId?: string) {
if (!sourceId) return
$e('c:table:create:navdraw')
@ -41,7 +41,7 @@ function openSchemaMagicDialog(baseId?: string) {
const { close } = useDialog(resolveComponent('DlgSchemaMagic'), {
'modelValue': isOpen,
'baseId': baseId,
'sourceId': sourceId,
'onUpdate:modelValue': closeDialog,
})
@ -52,8 +52,8 @@ function openSchemaMagicDialog(baseId?: string) {
}
}
function openQuickImportDialog(type: string, baseId?: string) {
if (!baseId) return
function openQuickImportDialog(type: string, sourceId?: string) {
if (!sourceId) return
$e(`a:actions:import-${type}`)
@ -62,7 +62,7 @@ function openQuickImportDialog(type: string, baseId?: string) {
const { close } = useDialog(resolveComponent('DlgQuickImport'), {
'modelValue': isOpen,
'importType': type,
'baseId': baseId,
'sourceId': sourceId,
'onUpdate:modelValue': closeDialog,
})
@ -73,8 +73,8 @@ function openQuickImportDialog(type: string, baseId?: string) {
}
}
function openAirtableImportDialog(baseId?: string) {
if (!baseId) return
function openAirtableImportDialog(sourceId?: string) {
if (!sourceId) return
$e('a:actions:import-airtable')
@ -82,7 +82,7 @@ function openAirtableImportDialog(baseId?: string) {
const { close } = useDialog(resolveComponent('DlgAirtableImport'), {
'modelValue': isOpen,
'baseId': baseId,
'sourceId': sourceId,
'onUpdate:modelValue': closeDialog,
})
@ -93,8 +93,8 @@ function openAirtableImportDialog(baseId?: string) {
}
}
function openTableCreateMagicDialog(baseId?: string) {
if (!baseId) return
function openTableCreateMagicDialog(sourceId?: string) {
if (!sourceId) return
$e('c:table:create:navdraw')
@ -102,7 +102,7 @@ function openTableCreateMagicDialog(baseId?: string) {
const { close } = useDialog(resolveComponent('DlgTableMagic'), {
'modelValue': isOpen,
'baseId': baseId,
'sourceId': sourceId,
'onUpdate:modelValue': closeDialog,
})
@ -116,7 +116,7 @@ function openTableCreateMagicDialog(baseId?: string) {
<template>
<div
v-if="isUIAllowed('tableCreate', { roles: projectRole })"
v-if="isUIAllowed('tableCreate', { roles: baseRole })"
class="group flex items-center gap-2 pl-2 pr-4.75 py-1 text-primary/70 hover:(text-primary/100) cursor-pointer select-none"
@click="emit('openTableCreateDialog')"
>
@ -139,14 +139,14 @@ function openTableCreateMagicDialog(baseId?: string) {
<GeneralIcon icon="magic" class="ml-1 text-orange-400" />
</div>
</template>
<a-menu-item key="table-magic" @click="openTableCreateMagicDialog(project.bases[baseIndex].id)">
<div class="color-transition nc-project-menu-item group">
<a-menu-item key="table-magic" @click="openTableCreateMagicDialog(base.sources[sourceIndex].id)">
<div class="color-transition nc-base-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create table
</div>
</a-menu-item>
<a-menu-item key="schema-magic" @click="openSchemaMagicDialog(project.bases[baseIndex].id)">
<div class="color-transition nc-project-menu-item group">
<a-menu-item key="schema-magic" @click="openSchemaMagicDialog(base.sources[sourceIndex].id)">
<div class="color-transition nc-base-menu-item group">
<GeneralIcon icon="magic1" class="group-hover:text-accent" />
Create schema
</div>
@ -158,44 +158,44 @@ function openTableCreateMagicDialog(baseId?: string) {
<!-- Quick Import From -->
<a-menu-item-group :title="$t('title.quickImportFrom')" class="!px-0 !mx-0">
<a-menu-item
v-if="isUIAllowed('airtableImport', { roles: projectRole })"
v-if="isUIAllowed('airtableImport', { roles: baseRole })"
key="quick-import-airtable"
@click="openAirtableImportDialog(project.bases[baseIndex].id)"
@click="openAirtableImportDialog(base.sources[sourceIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<div class="color-transition nc-base-menu-item group">
<GeneralIcon icon="airtable" class="group-hover:text-accent" />
Airtable
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('csvImport', { roles: projectRole })"
v-if="isUIAllowed('csvImport', { roles: baseRole })"
key="quick-import-csv"
@click="openQuickImportDialog('csv', project.bases[baseIndex].id)"
@click="openQuickImportDialog('csv', base.sources[sourceIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<div class="color-transition nc-base-menu-item group">
<GeneralIcon icon="csv" class="group-hover:text-accent" />
CSV file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('jsonImport', { roles: projectRole })"
v-if="isUIAllowed('jsonImport', { roles: baseRole })"
key="quick-import-json"
@click="openQuickImportDialog('json', project.bases[baseIndex].id)"
@click="openQuickImportDialog('json', base.sources[sourceIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<div class="color-transition nc-base-menu-item group">
<GeneralIcon icon="json" class="group-hover:text-accent" />
JSON file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport', { roles: projectRole })"
v-if="isUIAllowed('excelImport', { roles: baseRole })"
key="quick-import-excel"
@click="openQuickImportDialog('excel', project.bases[baseIndex].id)"
@click="openQuickImportDialog('excel', base.sources[sourceIndex].id)"
>
<div class="color-transition nc-project-menu-item group">
<div class="color-transition nc-base-menu-item group">
<GeneralIcon icon="excel" class="group-hover:text-accent" />
Microsoft Excel
</div>
@ -205,26 +205,26 @@ function openTableCreateMagicDialog(baseId?: string) {
<a-menu-divider class="my-0" />
<!-- <a-menu-item-group title="Connect to new datasource" class="!px-0 !mx-0">
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MYSQL, project.id)">
<div class="color-transition nc-project-menu-item group">
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MYSQL, base.id)">
<div class="color-transition nc-base-menu-item group">
<LogosMysqlIcon class="group-hover:text-accent" />
MySQL
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.PG, project.id)">
<div class="color-transition nc-project-menu-item group">
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.PG, base.id)">
<div class="color-transition nc-base-menu-item group">
<LogosPostgresql class="group-hover:text-accent" />
Postgres
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SQLITE, project.id)">
<div class="color-transition nc-project-menu-item group">
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.SQLITE, base.id)">
<div class="color-transition nc-base-menu-item group">
<VscodeIconsFileTypeSqlite class="group-hover:text-accent" />
SQLite
</div>
</a-menu-item>
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MSSQL, project.id)">
<div class="color-transition nc-project-menu-item group">
<a-menu-item key="connect-new-source" @click="toggleDialog(true, 'dataSources', ClientType.MSSQL, base.id)">
<div class="color-transition nc-base-menu-item group">
<SimpleIconsMicrosoftsqlserver class="group-hover:text-accent" />
MSSQL
</div>
@ -232,9 +232,9 @@ function openTableCreateMagicDialog(baseId?: string) {
<a-menu-item
v-if="appInfo.ee"
key="connect-new-source"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE, project.id)"
@click="toggleDialog(true, 'dataSources', ClientType.SNOWFLAKE, base.id)"
>
<div class="color-transition nc-project-menu-item group">
<div class="color-transition nc-base-menu-item group">
<LogosSnowflakeIcon class="group-hover:text-accent" />
Snowflake
</div>
@ -243,12 +243,12 @@ function openTableCreateMagicDialog(baseId?: string) {
<a-menu-divider class="my-0" /> -->
<a-menu-item v-if="isUIAllowed('importRequest', { roles: projectRole })" key="add-new-table" class="py-1 rounded-b">
<a-menu-item v-if="isUIAllowed('importRequest', { roles: baseRole })" key="add-new-table" class="py-1 rounded-b">
<a
v-e="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-base-menu-item group after:(!rounded-b)"
>
<GeneralIcon icon="openInNew" class="group-hover:text-accent" />
<!-- Request a data source you need? -->

30
packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue

@ -1,21 +1,21 @@
<script lang="ts" setup>
import type { BaseType, ProjectType } from 'nocodb-sdk'
import type { SourceType, BaseType } from 'nocodb-sdk'
const props = defineProps<{
source: SourceType
base: BaseType
project: ProjectType
}>()
const base = toRef(props, 'base')
const source = toRef(props, 'source')
const { isUIAllowed } = useRoles()
const projectRole = inject(ProjectRoleInj)
const baseRole = inject(ProjectRoleInj)
const { $e } = useNuxtApp()
function openAirtableImportDialog(baseId?: string) {
if (!baseId) return
function openAirtableImportDialog(sourceId?: string) {
if (!sourceId) return
$e('a:actions:import-airtable')
@ -23,7 +23,7 @@ function openAirtableImportDialog(baseId?: string) {
const { close } = useDialog(resolveComponent('DlgAirtableImport'), {
'modelValue': isOpen,
'baseId': baseId,
'sourceId': sourceId,
'onUpdate:modelValue': closeDialog,
})
@ -35,7 +35,7 @@ function openAirtableImportDialog(baseId?: string) {
}
function openQuickImportDialog(type: string) {
if (!base.value?.id) return
if (!source.value?.id) return
$e(`a:actions:import-${type}`)
@ -44,7 +44,7 @@ function openQuickImportDialog(type: string) {
const { close } = useDialog(resolveComponent('DlgQuickImport'), {
'modelValue': isOpen,
'importType': type,
'baseId': base.value.id,
'sourceId': source.value.id,
'onUpdate:modelValue': closeDialog,
})
@ -58,7 +58,7 @@ function openQuickImportDialog(type: string) {
<template>
<!-- Quick Import From -->
<NcSubMenu class="py-0" data-testid="nc-sidebar-project-import">
<NcSubMenu class="py-0" data-testid="nc-sidebar-base-import">
<template #title>
<GeneralIcon icon="download" />
@ -68,17 +68,17 @@ function openQuickImportDialog(type: string) {
<template #expandIcon></template>
<NcMenuItem
v-if="isUIAllowed('airtableImport', { roles: projectRole })"
v-if="isUIAllowed('airtableImport', { roles: baseRole })"
key="quick-import-airtable"
v-e="['c:import:airtable']"
@click="openAirtableImportDialog(base.id)"
@click="openAirtableImportDialog(source.id)"
>
<GeneralIcon icon="airtable" class="max-w-3.75 group-hover:text-black" />
<div class="ml-0.5">{{ $t('labels.airtable') }}</div>
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('csvImport', { roles: projectRole })"
v-if="isUIAllowed('csvImport', { roles: baseRole })"
key="quick-import-csv"
v-e="['c:import:csv']"
@click="openQuickImportDialog('csv')"
@ -88,7 +88,7 @@ function openQuickImportDialog(type: string) {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('jsonImport', { roles: projectRole })"
v-if="isUIAllowed('jsonImport', { roles: baseRole })"
key="quick-import-json"
v-e="['c:import:json']"
@click="openQuickImportDialog('json')"
@ -98,7 +98,7 @@ function openQuickImportDialog(type: string) {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('excelImport', { roles: projectRole })"
v-if="isUIAllowed('excelImport', { roles: baseRole })"
key="quick-import-excel"
v-e="['c:import:excel']"
@click="openQuickImportDialog('excel')"

4
packages/nc-gui/components/dashboard/TreeView/CreateViewBtn.vue

@ -9,7 +9,7 @@ const viewsStore = useViewsStore()
const { loadViews, navigateToView } = viewsStore
const table = inject(SidebarTableInj)!
const project = inject(ProjectInj)!
const base = inject(ProjectInj)!
const isViewListLoading = ref(false)
const toBeCreateType = ref<ViewTypes>()
@ -67,7 +67,7 @@ async function onOpenModal({
navigateToView({
view,
tableId: table.value.id!,
projectId: project.value.id!,
baseId: base.value.id!,
})
$e('a:view:create', { view: view.type })

329
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -2,7 +2,7 @@
import { nextTick } from '@vue/runtime-core'
import { message } from 'ant-design-vue'
import { stringifyRolesObj } from 'nocodb-sdk'
import type { BaseType, ProjectType, TableType } from 'nocodb-sdk'
import type { SourceType, BaseType, TableType } from 'nocodb-sdk'
import { LoadingOutlined } from '@ant-design/icons-vue'
import { useTitle } from '@vueuse/core'
import {
@ -13,7 +13,7 @@ import {
extractSdkResponseErrorMsg,
openLink,
storeToRefs,
useProjects,
useBases,
} from '#imports'
import type { NcProject } from '#imports'
import { useNuxtApp } from '#app'
@ -29,19 +29,19 @@ const indicator = h(LoadingOutlined, {
const router = useRouter()
const route = router.currentRoute
const { isSharedBase } = storeToRefs(useProject())
const { projectUrl } = useProject()
const { isSharedBase } = storeToRefs(useBase())
const { projectUrl } = useBase()
const { setMenuContext, openRenameTableDialog, duplicateTable, contextMenuTarget } = inject(TreeViewInj)!
const project = inject(ProjectInj)!
const base = inject(ProjectInj)!
const projectsStore = useProjects()
const basesStore = useBases()
const { isMobileMode } = useGlobal()
const { loadProject, loadProjects, createProject: _createProject, updateProject, getProjectMetaInfo } = projectsStore
const { projects } = storeToRefs(projectsStore)
const { loadProject, loadProjects, createProject: _createProject, updateProject, getProjectMetaInfo } = basesStore
const { bases } = storeToRefs(basesStore)
const { loadProjectTables } = useTablesStore()
const { activeTable } = storeToRefs(useTablesStore())
@ -64,9 +64,11 @@ const { t } = useI18n()
const input = ref<HTMLInputElement>()
const projectRole = inject(ProjectRoleInj)
const baseRole = inject(ProjectRoleInj)
const { activeProjectId } = storeToRefs(useProjects())
const { activeProjectId } = storeToRefs(useBases())
const { baseUrl } = useBase()
const toggleDialog = inject(ToggleDialogInj, () => {})
@ -82,9 +84,9 @@ const keys = ref<Record<string, number>>({})
const isTableDeleteDialogVisible = ref(false)
const isProjectDeleteDialogVisible = ref(false)
// If only project is open, i.e in case of docs, project view is open and not the page view
const projectViewOpen = computed(() => {
const routeNameSplit = String(route.value?.name).split('projectId-index-index')
// If only base is open, i.e in case of docs, base view is open and not the page view
const baseViewOpen = computed(() => {
const routeNameSplit = String(route.value?.name).split('baseId-index-index')
if (routeNameSplit.length <= 1) return false
const routeNameAfterProjectView = routeNameSplit[routeNameSplit.length - 1]
@ -97,7 +99,7 @@ const showBaseOption = computed(() => {
const enableEditMode = () => {
editMode.value = true
tempTitle.value = project.value.title!
tempTitle.value = base.value.title!
nextTick(() => {
input.value?.focus()
input.value?.select()
@ -109,15 +111,15 @@ const updateProjectTitle = async () => {
if (!tempTitle.value) return
try {
await updateProject(project.value.id!, {
await updateProject(base.value.id!, {
title: tempTitle.value,
})
editMode.value = false
tempTitle.value = ''
$e('a:project:rename')
$e('a:base:rename')
useTitle(`${project.value?.title}`)
useTitle(`${base.value?.title}`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -129,7 +131,7 @@ const copyProjectInfo = async () => {
try {
if (
await copy(
Object.entries(await getProjectMetaInfo(project.value.id!)!)
Object.entries(await getProjectMetaInfo(base.value.id!)!)
.map(([k, v]) => `${k}: **${v}**`)
.join('\n'),
)
@ -147,34 +149,34 @@ defineExpose({
enableEditMode,
})
const setIcon = async (icon: string, project: ProjectType) => {
const setIcon = async (icon: string, base: BaseType) => {
try {
const meta = {
...((project.meta as object) || {}),
...((base.meta as object) || {}),
icon,
}
projectsStore.updateProject(project.id!, { meta: JSON.stringify(meta) })
basesStore.updateProject(base.id!, { meta: JSON.stringify(meta) })
$e('a:project:icon:navdraw', { icon })
$e('a:base:icon:navdraw', { icon })
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
function openTableCreateDialog(baseIndex?: number | undefined) {
function openTableCreateDialog(sourceIndex?: number | undefined) {
const isOpen = ref(true)
let baseId = project.value!.bases?.[0].id
if (typeof baseIndex === 'number') {
baseId = project.value!.bases?.[baseIndex].id
let sourceId = base.value!.sources?.[0].id
if (typeof sourceIndex === 'number') {
sourceId = base.value!.sources?.[sourceIndex].id
}
if (!baseId || !project.value?.id) return
if (!sourceId || !base.value?.id) return
const { close } = useDialog(resolveComponent('DlgTableCreate'), {
'modelValue': isOpen,
baseId, // || bases.value[0].id,
'projectId': project.value!.id,
sourceId, // || sources.value[0].id,
'baseId': base.value!.id,
'onCreate': closeDialog,
'onUpdate:modelValue': () => closeDialog(),
})
@ -184,10 +186,10 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
if (!table) return
project.value.isExpanded = true
base.value.isExpanded = true
if (!activeKey.value || !activeKey.value.includes(`collapse-${baseId}`)) {
activeKey.value.push(`collapse-${baseId}`)
if (!activeKey.value || !activeKey.value.includes(`collapse-${sourceId}`)) {
activeKey.value.push(`collapse-${sourceId}`)
}
// TODO: Better way to know when the table node dom is available
@ -208,27 +210,27 @@ const addNewProjectChildEntity = async () => {
isAddNewProjectChildEntityLoading.value = true
const isProjectPopulated = projectsStore.isProjectPopulated(project.value.id!)
if (!isProjectPopulated && project.value.type === NcProjectType.DB) {
const isProjectPopulated = basesStore.isProjectPopulated(base.value.id!)
if (!isProjectPopulated && base.value.type === NcProjectType.DB) {
// We do not wait for tables api, so that add new table is seamless.
// Only con would be while saving table duplicate table name FE validation might not work
// If the table list api takes time to load before the table name validation
loadProjectTables(project.value.id!)
loadProjectTables(base.value.id!)
}
try {
openTableCreateDialog()
if (!project.value.isExpanded && project.value.type !== NcProjectType.DB) {
project.value.isExpanded = true
if (!base.value.isExpanded && base.value.type !== NcProjectType.DB) {
base.value.isExpanded = true
}
} finally {
isAddNewProjectChildEntityLoading.value = false
}
}
const onProjectClick = async (project: NcProject, ignoreNavigation?: boolean, toggleIsExpanded?: boolean) => {
if (!project) {
const onProjectClick = async (base: NcProject, ignoreNavigation?: boolean, toggleIsExpanded?: boolean) => {
if (!base) {
return
}
@ -238,19 +240,19 @@ const onProjectClick = async (project: NcProject, ignoreNavigation?: boolean, to
toggleIsExpanded = isMobileMode.value || toggleIsExpanded
if (toggleIsExpanded) {
project.isExpanded = !project.isExpanded
base.isExpanded = !base.isExpanded
} else {
project.isExpanded = true
base.isExpanded = true
}
const isProjectPopulated = projectsStore.isProjectPopulated(project.id!)
const isProjectPopulated = basesStore.isProjectPopulated(base.id!)
if (!isProjectPopulated) project.isLoading = true
if (!isProjectPopulated) base.isLoading = true
if (!ignoreNavigation) {
await navigateTo(
projectUrl({
id: project.id!,
baseUrl({
id: base.id!,
type: 'database',
isSharedBase: isSharedBase.value,
}),
@ -258,32 +260,32 @@ const onProjectClick = async (project: NcProject, ignoreNavigation?: boolean, to
}
if (!isProjectPopulated) {
await loadProjectTables(project.id!)
await loadProjectTables(base.id!)
}
if (!isProjectPopulated) {
const updatedProject = projects.value.get(project.id!)!
const updatedProject = bases.value.get(base.id!)!
updatedProject.isLoading = false
}
}
function openErdView(base: BaseType) {
activeBaseId.value = base.id
function openErdView(source: SourceType) {
activeBaseId.value = source.id
isErdModalOpen.value = !isErdModalOpen.value
}
async function openProjectErdView(_project: ProjectType) {
async function openProjectErdView(_project: BaseType) {
if (!_project.id) return
if (!projectsStore.isProjectPopulated(_project.id)) {
if (!basesStore.isProjectPopulated(_project.id)) {
await loadProject(_project.id)
}
const project = projects.value.get(_project.id)
const base = bases.value.get(_project.id)
const base = project?.bases?.[0]
if (!base) return
openErdView(base)
const source = base?.sources?.[0]
if (!source) return
openErdView(source)
}
const reloadTables = async () => {
@ -293,11 +295,11 @@ const reloadTables = async () => {
}
const contextMenuBase = computed(() => {
if (contextMenuTarget.type === 'base') {
if (contextMenuTarget.type === 'source') {
return contextMenuTarget.value
} else if (contextMenuTarget.type === 'table') {
const base = project.value?.bases?.find((b) => b.id === contextMenuTarget.value.base_id)
if (base) return base
const source = base.value?.sources?.find((b) => b.id === contextMenuTarget.value.source_id)
if (source) return source
}
return null
})
@ -307,11 +309,11 @@ watch(
async () => {
if (!activeTable.value) return
const baseId = activeTable.value.base_id
if (!baseId) return
const sourceId = activeTable.value.source_id
if (!sourceId) return
if (!activeKey.value.includes(`collapse-${baseId}`)) {
activeKey.value.push(`collapse-${baseId}`)
if (!activeKey.value.includes(`collapse-${sourceId}`)) {
activeKey.value.push(`collapse-${sourceId}`)
}
},
{
@ -332,13 +334,13 @@ onKeyStroke('Escape', () => {
const isDuplicateDlgOpen = ref(false)
const selectedProjectToDuplicate = ref()
const duplicateProject = (project: ProjectType) => {
selectedProjectToDuplicate.value = project
const duplicateProject = (base: BaseType) => {
selectedProjectToDuplicate.value = base
isDuplicateDlgOpen.value = true
}
const { $poller } = useNuxtApp()
const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string }) => {
const DlgProjectDuplicateOnOk = async (jobData: { id: string; base_id: string }) => {
await loadProjects('workspace')
$poller.subscribe(
@ -358,23 +360,23 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
if (data.status === JobStatus.COMPLETED) {
await loadProjects('workspace')
const project = projects.value.get(jobData.project_id)
const base = bases.value.get(jobData.base_id)
// open project after duplication
if (project) {
// open base after duplication
if (base) {
await navigateToProject({
projectId: project.id,
type: project.type,
baseId: base.id,
type: base.type,
})
}
} else if (data.status === JobStatus.FAILED) {
message.error('Failed to duplicate project')
message.error('Failed to duplicate base')
await loadProjects('workspace')
}
}
},
)
$e('a:project:duplicate')
$e('a:base:duplicate')
}
const tableDelete = () => {
@ -391,54 +393,54 @@ const projectDelete = () => {
<template>
<NcDropdown :trigger="['contextmenu']" overlay-class-name="nc-dropdown-tree-view-context-menu">
<div
class="mx-1 nc-project-sub-menu rounded-md"
:class="{ active: project.isExpanded }"
:data-testid="`nc-sidebar-project-${project.title}`"
:data-project-id="project.id"
class="mx-1 nc-base-sub-menu rounded-md"
:class="{ active: base.isExpanded }"
:data-testid="`nc-sidebar-base-${base.title}`"
:data-base-id="base.id"
>
<div class="flex items-center gap-0.75 py-0.25 cursor-pointer" @contextmenu="setMenuContext('project', project)">
<div class="flex items-center gap-0.75 py-0.25 cursor-pointer" @contextmenu="setMenuContext('base', base)">
<div
ref="projectNodeRefs"
ref="baseNodeRefs"
:class="{
'bg-primary-selected active': activeProjectId === project.id && projectViewOpen && !isMobileMode,
'hover:bg-gray-200': !(activeProjectId === project.id && projectViewOpen),
'bg-primary-selected active': activeProjectId === base.id && baseViewOpen && !isMobileMode,
'hover:bg-gray-200': !(activeProjectId === base.id && baseViewOpen),
}"
:data-testid="`nc-sidebar-project-title-${project.title}`"
class="nc-sidebar-node project-title-node h-7.25 flex-grow rounded-md group flex items-center w-full pr-1"
:data-testid="`nc-sidebar-base-title-${base.title}`"
class="nc-sidebar-node base-title-node h-7.25 flex-grow rounded-md group flex items-center w-full pr-1"
>
<NcButton
v-e="['c:base:expand']"
type="text"
size="xxsmall"
class="nc-sidebar-node-btn nc-sidebar-expand ml-0.75 !xs:visible"
@click="onProjectClick(project, true, true)"
@click="onProjectClick(base, true, true)"
>
<GeneralIcon
icon="triangleFill"
class="group-hover:visible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.75 rotate-90 !xs:visible"
:class="{ '!rotate-180': project.isExpanded, '!visible': isOptionsOpen }"
:class="{ '!rotate-180': base.isExpanded, '!visible': isOptionsOpen }"
/>
</NcButton>
<div class="flex items-center mr-1" @click="onProjectClick(project)">
<div class="flex items-center mr-1" @click="onProjectClick(base)">
<div class="flex items-center select-none w-6 h-full">
<a-spin
v-if="project.isLoading"
v-if="base.isLoading"
class="!ml-1.25 !flex !flex-row !items-center !my-0.5 w-8"
:indicator="indicator"
/>
<LazyGeneralEmojiPicker
v-else
:key="project.meta?.icon"
:key="base.meta?.icon"
:emoji="base.meta?.icon"
v-e="['c:base:emojiSelect']"
:emoji="project.meta?.icon"
:readonly="true"
size="small"
@emoji-selected="setIcon($event, project)"
@emoji-selected="setIcon($event, base)"
>
<template #default>
<GeneralProjectIcon :type="project.type" />
<GeneralProjectIcon :type="base.type" />
</template>
</LazyGeneralEmojiPicker>
</div>
@ -449,7 +451,7 @@ const projectDelete = () => {
ref="input"
v-model="tempTitle"
class="flex-grow leading-1 outline-0 ring-none capitalize !text-inherit !bg-transparent w-4/5"
:class="{ 'text-black font-semibold': activeProjectId === project.id && projectViewOpen && !isMobileMode }"
:class="{ 'text-black font-semibold': activeProjectId === base.id && baseViewOpen && !isMobileMode }"
@click.stop
@keyup.enter="updateProjectTitle"
@keyup.esc="updateProjectTitle"
@ -459,12 +461,12 @@ const projectDelete = () => {
v-else
class="nc-sidebar-node-title capitalize text-ellipsis overflow-hidden select-none"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
:class="{ 'text-black font-semibold': activeProjectId === project.id && projectViewOpen }"
@click="onProjectClick(project)"
:class="{ 'text-black font-semibold': activeProjectId === base.id && baseViewOpen }"
@click="onProjectClick(base)"
>
{{ project.title }}
{{ base.title }}
</span>
<div :class="{ 'flex flex-grow h-full': !editMode }" @click="onProjectClick(project)"></div>
<div :class="{ 'flex flex-grow h-full': !editMode }" @click="onProjectClick(base)"></div>
<NcDropdown v-model:visible="isOptionsOpen" :trigger="['click']">
<NcButton
@ -485,12 +487,12 @@ const projectDelete = () => {
maxHeight: '70vh',
overflow: 'overlay',
}"
:data-testid="`nc-sidebar-project-${project.title}-options`"
:data-testid="`nc-sidebar-base-${base.title}-options`"
@click="isOptionsOpen = false"
>
<template v-if="!isSharedBase">
<NcMenuItem
v-if="isUIAllowed('projectRename')"
v-if="isUIAllowed('baseRename')"
v-e="['c:base:rename']"
data-testid="nc-sidebar-project-rename"
@click="enableEditMode"
@ -500,23 +502,23 @@ const projectDelete = () => {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('projectDuplicate', { roles: [stringifyRolesObj(orgRoles), projectRole].join() })"
v-if="isUIAllowed('baseDuplicate', { roles: [stringifyRolesObj(orgRoles), baseRole].join() })"
v-e="['c:base:duplicate']"
data-testid="nc-sidebar-project-duplicate"
@click="duplicateProject(project)"
data-testid="nc-sidebar-base-duplicate"
@click="duplicateProject(base)"
>
<GeneralIcon icon="duplicate" class="text-gray-700" />
{{ $t('general.duplicate') }}
</NcMenuItem>
<NcDivider v-if="['projectDuplicate', 'projectRename'].some((permission) => isUIAllowed(permission))" />
<NcDivider v-if="['baseDuplicate', 'baseRename'].some((permission) => isUIAllowed(permission))" />
<!-- Copy Project Info -->
<NcMenuItem
v-if="!isEeUI"
key="copy"
data-testid="nc-sidebar-base-copy-base-info"
v-e="['c:base:copy-proj-info']"
data-testid="nc-sidebar-project-copy-project-info"
@click.stop="copyProjectInfo"
>
<GeneralIcon icon="copy" class="group-hover:text-black" />
@ -527,8 +529,8 @@ const projectDelete = () => {
<NcMenuItem
key="erd"
v-e="['c:base:erd']"
data-testid="nc-sidebar-project-relations"
@click="openProjectErdView(project)"
data-testid="nc-sidebar-base-relations"
@click="openProjectErdView(base)"
>
<GeneralIcon icon="erd" />
{{ $t('title.relations') }}
@ -539,11 +541,11 @@ const projectDelete = () => {
v-if="isUIAllowed('apiDocs')"
key="api"
v-e="['c:base:api-docs']"
data-testid="nc-sidebar-project-rest-apis"
data-testid="nc-sidebar-base-rest-apis"
@click.stop="
() => {
$e('c:base:api-docs')
openLink(`/api/v1/db/meta/projects/${project.id}/swagger`, appInfo.ncSiteUrl)
openLink(`/api/v1/meta/bases/${base.id}/swagger`, appInfo.ncSiteUrl)
}
"
>
@ -552,27 +554,27 @@ const projectDelete = () => {
</NcMenuItem>
</template>
<template v-if="project.bases && project.bases[0] && showBaseOption">
<template v-if="base.sources && base.sources[0] && showBaseOption">
<NcDivider />
<DashboardTreeViewBaseOptions v-model:project="project" :base="project.bases[0]" />
<DashboardTreeViewBaseOptions v-model:base="base" :source="base.sources[0]" />
</template>
<NcDivider v-if="['projectMiscSettings', 'projectDelete'].some((permission) => isUIAllowed(permission))" />
<NcDivider v-if="['baseMiscSettings', 'baseDelete'].some((permission) => isUIAllowed(permission))" />
<NcMenuItem
v-if="isUIAllowed('projectMiscSettings')"
v-if="isUIAllowed('baseMiscSettings')"
key="teamAndSettings"
v-e="['c:base:settings']"
data-testid="nc-sidebar-project-settings"
class="nc-sidebar-project-project-settings"
@click="toggleDialog(true, 'teamAndAuth', undefined, project.id)"
data-testid="nc-sidebar-base-settings"
class="nc-sidebar-base-base-settings"
@click="toggleDialog(true, 'teamAndAuth', undefined, base.id)"
>
<GeneralIcon icon="settings" class="group-hover:text-black" />
{{ $t('activity.settings') }}
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('projectDelete', { roles: [stringifyRolesObj(orgRoles), projectRole].join() })"
data-testid="nc-sidebar-project-delete"
v-if="isUIAllowed('baseDelete', { roles: [stringifyRolesObj(orgRoles), baseRole].join() })"
data-testid="nc-sidebar-base-delete"
class="!text-red-500 !hover:bg-red-50"
@click="projectDelete"
>
@ -584,12 +586,12 @@ const projectDelete = () => {
</NcDropdown>
<NcButton
v-if="isUIAllowed('tableCreate', { roles: projectRole })"
v-if="isUIAllowed('tableCreate', { roles: baseRole })"
v-e="['c:base:create-table']"
class="nc-sidebar-node-btn"
size="xxsmall"
type="text"
data-testid="nc-sidebar-add-project-entity"
data-testid="nc-sidebar-add-base-entity"
:class="{ '!text-black !visible': isAddNewProjectChildEntityLoading, '!visible': isOptionsOpen }"
:loading="isAddNewProjectChildEntityLoading"
@click.stop="addNewProjectChildEntity"
@ -600,28 +602,28 @@ const projectDelete = () => {
</div>
<div
v-if="project.id && !project.isLoading"
v-if="base.id && !base.isLoading"
key="g1"
class="overflow-x-hidden transition-max-height"
:class="{ 'max-h-0': !project.isExpanded }"
:class="{ 'max-h-0': !base.isExpanded }"
>
<template v-if="project && project?.bases">
<template v-if="base && base?.sources">
<div class="flex-1 overflow-y-auto overflow-x-hidden flex flex-col" :class="{ 'mb-[20px]': isSharedBase }">
<div v-if="project?.bases?.[0]?.enabled" class="flex-1">
<div v-if="base?.sources?.[0]?.enabled" class="flex-1">
<div class="transition-height duration-200">
<DashboardTreeViewTableList :project="project" :base-index="0" />
<DashboardTreeViewTableList :base="base" :source-index="0" />
</div>
</div>
<div v-if="project?.bases?.slice(1).filter((el) => el.enabled)?.length" class="transition-height duration-200">
<div v-if="base?.sources?.slice(1).filter((el) => el.enabled)?.length" class="transition-height duration-200">
<div class="border-none sortable-list">
<div v-for="(base, baseIndex) of project.bases" :key="`base-${base.id}`">
<template v-if="baseIndex === 0"></template>
<div v-for="(source, sourceIndex) of base.sources" :key="`source-${source.id}`">
<template v-if="sourceIndex === 0"></template>
<a-collapse
v-else-if="base && base.enabled"
v-else-if="source && source.enabled"
v-model:activeKey="activeKey"
class="!mx-0 !px-0 nc-sidebar-source-node"
v-e="['c:source:toggle-expand']"
class="!mx-0 !px-0 nc-sidebar-base-node"
:class="[{ hidden: searchActive && !!filterQuery }]"
expand-icon-position="left"
:bordered="false"
@ -633,34 +635,34 @@ const projectDelete = () => {
>
<GeneralIcon
icon="triangleFill"
class="nc-sidebar-base-node-btns -mt-0.75 invisible xs:visible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.5 text-gray-500 rotate-90"
class="nc-sidebar-source-node-btns -mt-0.75 invisible xs:visible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.5 text-gray-500 rotate-90"
:class="{ '!rotate-180': isActive }"
/>
</div>
</template>
<a-collapse-panel :key="`collapse-${base.id}`">
<a-collapse-panel :key="`collapse-${source.id}`">
<template #header>
<div class="nc-sidebar-node min-w-20 w-full flex flex-row group py-0.25">
<div
v-if="baseIndex === 0"
class="base-context flex items-center gap-2 text-gray-800 nc-sidebar-node-title"
@contextmenu="setMenuContext('base', base)"
v-if="sourceIndex === 0"
class="source-context flex items-center gap-2 text-gray-800 nc-sidebar-node-title"
@contextmenu="setMenuContext('source', source)"
>
<GeneralBaseLogo :base-type="base.type" class="min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)" />
<GeneralBaseLogo :source-type="source.type" class="min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)" />
{{ $t('general.default') }}
</div>
<div
v-else
class="base-context flex flex-grow items-center gap-1.75 text-gray-800 min-w-1/20 max-w-full"
@contextmenu="setMenuContext('base', base)"
class="source-context flex flex-grow items-center gap-1.75 text-gray-800 min-w-1/20 max-w-full"
@contextmenu="setMenuContext('source', source)"
>
<GeneralBaseLogo :base-type="base.type" class="min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)" />
<GeneralBaseLogo :source-type="source.type" class="min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)" />
<div
:data-testid="`nc-sidebar-project-${base.alias}`"
:data-testid="`nc-sidebar-base-${source.alias}`"
class="nc-sidebar-node-title flex capitalize text-ellipsis overflow-hidden select-none"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ base.alias || '' }}
{{ source.alias || '' }}
</div>
<a-tooltip class="xs:(hidden)">
<template #title>{{ $t('objects.externalDb') }}</template>
@ -671,17 +673,17 @@ const projectDelete = () => {
</div>
<div class="flex flex-row items-center gap-x-0.25 w-12.25">
<NcDropdown
:visible="isBasesOptionsOpen[base!.id!]"
:visible="isBasesOptionsOpen[source!.id!]"
:trigger="['click']"
@update:visible="isBasesOptionsOpen[base!.id!] = $event"
@update:visible="isBasesOptionsOpen[source!.id!] = $event"
>
<NcButton
v-e="['c:source:options']"
class="nc-sidebar-node-btn"
:class="{ '!text-black !opacity-100': isBasesOptionsOpen[base!.id!] }"
:class="{ '!text-black !opacity-100': isBasesOptionsOpen[source!.id!] }"
type="text"
size="xxsmall"
@click.stop="isBasesOptionsOpen[base!.id!] = !isBasesOptionsOpen[base!.id!]"
@click.stop="isBasesOptionsOpen[source!.id!] = !isBasesOptionsOpen[source!.id!]"
>
<GeneralIcon icon="threeDotHorizontal" class="text-xl w-4.75" />
</NcButton>
@ -692,26 +694,26 @@ const projectDelete = () => {
maxHeight: '70vh',
overflow: 'overlay',
}"
@click="isBasesOptionsOpen[base!.id!] = false"
@click="isBasesOptionsOpen[source!.id!] = false"
>
<!-- ERD View -->
<NcMenuItem key="erd" v-e="['c:source:erd']" @click="openErdView(base)">
<NcMenuItem key="erd" v-e="['c:source:erd']" @click="openErdView(source)">
<GeneralIcon icon="erd" />
{{ $t('title.relations') }}
</NcMenuItem>
<DashboardTreeViewBaseOptions v-if="showBaseOption" v-model:project="project" :base="base" />
<DashboardTreeViewBaseOptions v-if="showBaseOption" v-model:base="base" :source="source" />
</NcMenu>
</template>
</NcDropdown>
<NcButton
v-if="isUIAllowed('tableCreate', { roles: projectRole })"
v-if="isUIAllowed('tableCreate', { roles: baseRole })"
v-e="['c:source:add-table']"
type="text"
size="xxsmall"
class="nc-sidebar-node-btn"
@click.stop="openTableCreateDialog(baseIndex)"
@click.stop="openTableCreateDialog(sourceIndex)"
>
<GeneralIcon icon="plus" class="text-xl leading-5" style="-webkit-text-stroke: 0.15px" />
</NcButton>
@ -720,10 +722,10 @@ const projectDelete = () => {
</template>
<div
ref="menuRefs"
:key="`sortable-${base.id}-${base.id && base.id in keys ? keys[base.id] : '0'}`"
:nc-base="base.id"
:key="`sortable-${source.id}-${source.id && source.id in keys ? keys[source.id] : '0'}`"
:nc-source="source.id"
>
<DashboardTreeViewTableList :project="project" :base-index="baseIndex" />
<DashboardTreeViewTableList :base="base" :source-index="sourceIndex" />
</div>
</a-collapse-panel>
</a-collapse>
@ -736,14 +738,13 @@ const projectDelete = () => {
</div>
<template v-if="!isSharedBase" #overlay>
<NcMenu class="!py-0 rounded text-sm">
<template v-if="contextMenuTarget.type === 'project' && project.type === 'database'"></template>
<template v-if="contextMenuTarget.type === 'base' && base.type === 'database'"></template>
<template v-else-if="contextMenuTarget.type === 'base'"></template>
<template v-else-if="contextMenuTarget.type === 'source'"></template>
<template v-else-if="contextMenuTarget.type === 'table'">
v-e="['c:table:rename']"
<NcMenuItem v-if="isUIAllowed('tableRename')" @click="openRenameTableDialog(contextMenuTarget.value, true)">
<div class="nc-project-option-item">
<NcMenuItem v-if="isUIAllowed('tableRename')" v-e="['c:table:rename']" @click="openRenameTableDialog(contextMenuTarget.value, true)">
<div class="nc-base-option-item">
<GeneralIcon icon="edit" class="text-gray-700" />
{{ $t('general.rename') }}
</div>
@ -754,14 +755,14 @@ const projectDelete = () => {
v-e="['c:table:duplicate']"
@click="duplicateTable(contextMenuTarget.value)"
>
<div class="nc-project-option-item">
<div class="nc-base-option-item">
<GeneralIcon icon="duplicate" class="text-gray-700" />
{{ $t('general.duplicate') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem v-if="isUIAllowed('table-delete')" class="!hover:bg-red-50" @click="tableDelete">
<div class="nc-project-option-item text-red-600">
<div class="nc-base-option-item text-red-600">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}
</div>
@ -770,7 +771,7 @@ const projectDelete = () => {
<template v-else>
<NcMenuItem @click="reloadTables">
<div class="nc-project-option-item">
<div class="nc-base-option-item">
{{ $t('general.reload') }}
</div>
</NcMenuItem>
@ -779,21 +780,21 @@ const projectDelete = () => {
</template>
</NcDropdown>
<DlgTableDelete
v-if="contextMenuTarget.value?.id && project?.id"
v-if="contextMenuTarget.value?.id && base?.id"
v-model:visible="isTableDeleteDialogVisible"
:table-id="contextMenuTarget.value?.id"
:project-id="project?.id"
:base-id="base?.id"
/>
<DlgProjectDelete v-model:visible="isProjectDeleteDialogVisible" :project-id="project?.id" />
<DlgProjectDelete v-model:visible="isProjectDeleteDialogVisible" :base-id="base?.id" />
<DlgProjectDuplicate
v-if="selectedProjectToDuplicate"
v-model="isDuplicateDlgOpen"
:project="selectedProjectToDuplicate"
:base="selectedProjectToDuplicate"
:on-ok="DlgProjectDuplicateOnOk"
/>
<GeneralModal v-model:visible="isErdModalOpen" size="large">
<div class="h-[80vh]">
<LazyDashboardSettingsErd :base-id="activeBaseId" />
<LazyDashboardSettingsErd :source-id="activeBaseId" />
</div>
</GeneralModal>
</template>
@ -811,7 +812,7 @@ const projectDelete = () => {
@apply !px-0 !pb-0 !pt-0.25;
}
:deep(.ant-collapse-header:hover .nc-sidebar-base-node-btns) {
:deep(.ant-collapse-header:hover .nc-sidebar-source-node-btns) {
@apply visible;
}
</style>

14
packages/nc-gui/components/dashboard/TreeView/ProjectWrapper.vue

@ -1,17 +1,17 @@
<script lang="ts" setup>
import type { ProjectType } from 'nocodb-sdk'
import type { BaseType } from 'nocodb-sdk'
import { ProjectInj, ProjectRoleInj } from '#imports'
const props = defineProps<{
projectRole: string | string[]
project: ProjectType
baseRole: string | string[]
base: BaseType
}>()
const projectRole = toRef(props, 'projectRole')
const project = toRef(props, 'project')
const baseRole = toRef(props, 'baseRole')
const base = toRef(props, 'base')
provide(ProjectRoleInj, projectRole)
provide(ProjectInj, project)
provide(ProjectRoleInj, baseRole)
provide(ProjectInj, base)
</script>
<template>

54
packages/nc-gui/components/dashboard/TreeView/TableList.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ProjectType, TableType } from 'nocodb-sdk'
import type { BaseType, TableType } from 'nocodb-sdk'
import { storeToRefs } from 'pinia'
import Sortable from 'sortablejs'
import TableNode from './TableNode.vue'
@ -8,23 +8,23 @@ import { toRef } from '#imports'
const props = withDefaults(
defineProps<{
project: ProjectType
baseIndex?: number
base: BaseType
sourceIndex?: number
}>(),
{
baseIndex: 0,
sourceIndex: 0,
},
)
const project = toRef(props, 'project')
const baseIndex = toRef(props, 'baseIndex')
const base = toRef(props, 'base')
const sourceIndex = toRef(props, 'sourceIndex')
const base = computed(() => project.value?.bases?.[baseIndex.value])
const source = computed(() => base.value?.sources?.[sourceIndex.value])
const { isMobileMode } = useGlobal()
const { projectTables } = storeToRefs(useTablesStore())
const tables = computed(() => projectTables.value.get(project.value.id!) ?? [])
const { baseTables } = storeToRefs(useTablesStore())
const tables = computed(() => baseTables.value.get(base.value.id!) ?? [])
const { $api } = useNuxtApp()
@ -44,14 +44,14 @@ const sortables: Record<string, Sortable> = {}
// todo: replace with vuedraggable
const initSortable = (el: Element) => {
const base_id = el.getAttribute('nc-base')
if (!base_id) return
const source_id = el.getAttribute('nc-source')
if (!source_id) return
if (isMobileMode.value) return
if (sortables[base_id]) sortables[base_id].destroy()
if (sortables[source_id]) sortables[source_id].destroy()
Sortable.create(el as HTMLLIElement, {
onEnd: async (evt) => {
const offset = tables.value.findIndex((table) => table.base_id === base_id)
const offset = tables.value.findIndex((table) => table.source_id === source_id)
const { newIndex = 0, oldIndex = 0 } = evt
@ -87,10 +87,10 @@ const initSortable = (el: Element) => {
tables.value?.splice(newIndex + offset, 0, ...tables.value?.splice(oldIndex + offset, 1))
// force re-render the list
if (keys.value[base_id]) {
keys.value[base_id] = keys.value[base_id] + 1
if (keys.value[source_id]) {
keys.value[source_id] = keys.value[source_id] + 1
} else {
keys.value[base_id] = 1
keys.value[source_id] = 1
}
// update the item order
@ -106,7 +106,7 @@ const initSortable = (el: Element) => {
id: dragEl.dataset.id,
title: dragEl.dataset.title,
type: dragEl.dataset.type,
baseId: dragEl.dataset.baseId,
sourceId: dragEl.dataset.sourceId,
}),
)
},
@ -125,28 +125,28 @@ watchEffect(() => {
})
const availableTables = computed(() => {
return tables.value.filter((table) => table.base_id === project.value?.bases?.[baseIndex.value].id)
return tables.value.filter((table) => table.source_id === base.value?.sources?.[sourceIndex.value].id)
})
</script>
<template>
<div class="border-none sortable-list">
<template v-if="project">
<template v-if="base">
<div
v-if="availableTables.length === 0"
class="py-0.5 text-gray-500"
:class="{
'ml-13.55': baseIndex === 0,
'ml-19.25': baseIndex !== 0,
'ml-13.55': sourceIndex === 0,
'ml-19.25': sourceIndex !== 0,
}"
>
{{ $t('general.empty') }}
</div>
<div
v-if="project.bases?.[baseIndex] && project!.bases[baseIndex].enabled"
v-if="base.sources?.[sourceIndex] && base!.sources[sourceIndex].enabled"
ref="menuRefs"
:key="`sortable-${base?.id}-${base?.id && base?.id in keys ? keys[base?.id] : '0'}`"
:nc-base="base?.id"
:key="`sortable-${source?.id}-${source?.id && source?.id in keys ? keys[source?.id] : '0'}`"
:nc-source="source?.id"
>
<TableNode
v-for="table of availableTables"
@ -155,10 +155,10 @@ const availableTables = computed(() => {
:data-order="table.order"
:data-id="table.id"
:table="table"
:project="project"
:base-index="baseIndex"
:base="base"
:source-index="sourceIndex"
:data-title="table.title"
:data-base-id="base?.id"
:data-source-id="source?.id"
:data-type="table.type"
>
</TableNode>

62
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { ProjectType, TableType } from 'nocodb-sdk'
import type { BaseType, TableType } from 'nocodb-sdk'
import { toRef } from '@vue/reactivity'
import { message } from 'ant-design-vue'
import { storeToRefs } from 'pinia'
@ -9,19 +9,19 @@ import { ProjectRoleInj, TreeViewInj, useRoles, useTabs } from '#imports'
const props = withDefaults(
defineProps<{
project: ProjectType
base: BaseType
table: TableType
baseIndex: number
sourceIndex: number
}>(),
{ baseIndex: 0 },
{ sourceIndex: 0 },
)
const project = toRef(props, 'project')
const base = toRef(props, 'base')
const table = toRef(props, 'table')
const baseIndex = toRef(props, 'baseIndex')
const sourceIndex = toRef(props, 'sourceIndex')
const { openTable: _openTable } = useTableNew({
projectId: project.value.id!,
baseId: base.value.id!,
})
const route = useRoute()
@ -36,10 +36,10 @@ const { updateTab } = tabStore
const { $e, $api } = useNuxtApp()
useTableNew({
projectId: project.value.id!,
baseId: base.value.id!,
})
const projectRole = inject(ProjectRoleInj)
const baseRole = inject(ProjectRoleInj)
provide(SidebarTableInj, table)
const { setMenuContext, openRenameTableDialog, duplicateTable } = inject(TreeViewInj)!
@ -49,8 +49,8 @@ const { activeView } = storeToRefs(useViewsStore())
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
// todo: temp
const { projectTables } = storeToRefs(useTablesStore())
const tables = computed(() => projectTables.value.get(project.value.id!) ?? [])
const { baseTables } = storeToRefs(useTablesStore())
const tables = computed(() => baseTables.value.get(base.value.id!) ?? [])
const openedTableId = computed(() => route.params.viewId)
@ -78,11 +78,11 @@ const setIcon = async (icon: string, table: TableType) => {
// Todo: temp
const { isSharedBase } = useProject()
// const isMultiBase = computed(() => project.bases && project.bases.length > 1)
const { isSharedBase } = useBase()
// const isMultiBase = computed(() => base.sources && base.sources.length > 1)
const canUserEditEmote = computed(() => {
return isUIAllowed('tableIconEdit', { roles: projectRole?.value })
return isUIAllowed('tableIconEdit', { roles: baseRole?.value })
})
const isExpanded = ref(false)
@ -146,15 +146,15 @@ const isTableOpened = computed(() => {
:data-order="table.order"
:data-id="table.id"
:data-table-id="table.id"
:class="[`nc-project-tree-tbl nc-project-tree-tbl-${table.title}`]"
:class="[`nc-base-tree-tbl nc-base-tree-tbl-${table.title}`]"
:data-active="openedTableId === table.id"
>
<GeneralTooltip
class="nc-tree-item-inner nc-sidebar-node pl-11 pr-0.75 mb-0.25 rounded-md h-7.1 w-full group cursor-pointer hover:bg-gray-200"
:class="{
'hover:bg-gray-200': openedTableId !== table.id,
'pl-12 xs:(pl-14)': baseIndex !== 0,
'pl-6.5': baseIndex === 0,
'pl-12 xs:(pl-14)': sourceIndex !== 0,
'pl-6.5': sourceIndex === 0,
'!bg-primary-selected': isTableOpened,
}"
modifier-key="Alt"
@ -178,7 +178,7 @@ const isTableOpened = computed(() => {
>
<GeneralIcon
icon="triangleFill"
class="nc-sidebar-base-node-btns group-hover:visible invisible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.5 !text-gray-600 rotate-90"
class="nc-sidebar-source-node-btns group-hover:visible invisible cursor-pointer transform transition-transform duration-500 h-1.5 w-1.5 !text-gray-600 rotate-90"
:class="{ '!rotate-180': isExpanded }"
/>
</NcButton>
@ -204,12 +204,12 @@ const isTableOpened = computed(() => {
<template #title>
{{ $t('general.changeIcon') }}
</template>
<component
:is="iconMap.table"
<MdiTable
v-if="table.type === 'table'"
class="flex w-5 !text-gray-500 text-sm"
:class="{
'group-hover:text-gray-500': isUIAllowed('tableSort', { roles: projectRole }),
'group-hover:text-gray-500': isUIAllowed('tableSort', { roles: baseRole }),
'!text-black': openedTableId === table.id,
}"
/>
@ -217,7 +217,7 @@ const isTableOpened = computed(() => {
v-else
class="flex w-5 !text-gray-500 text-sm"
:class="{
'group-hover:text-gray-500': isUIAllowed('tableSort', { roles: projectRole }),
'group-hover:text-gray-500': isUIAllowed('tableSort', { roles: baseRole }),
'!text-black': openedTableId === table.id,
}"
/>
@ -243,7 +243,7 @@ const isTableOpened = computed(() => {
<NcDropdown
v-if="
!isSharedBase &&
(isUIAllowed('tableRename', { roles: projectRole }) || isUIAllowed('tableDelete', { roles: projectRole }))
(isUIAllowed('tableRename', { roles: baseRole }) || isUIAllowed('tableDelete', { roles: baseRole }))
"
v-e="['c:table:option']"
:trigger="['click']"
@ -258,10 +258,10 @@ const isTableOpened = computed(() => {
<template #overlay>
<NcMenu>
<NcMenuItem
v-if="isUIAllowed('tableRename', { roles: projectRole })"
v-e="['c:table:rename']"
v-if="isUIAllowed('tableRename', { roles: baseRole })"
:data-testid="`sidebar-table-rename-${table.title}`"
@click="openRenameTableDialog(table, project.bases[baseIndex].id)"
@click="openRenameTableDialog(table, base.sources[sourceIndex].id)"
>
<GeneralIcon icon="edit" class="text-gray-700" />
{{ $t('general.rename') }}
@ -270,8 +270,8 @@ const isTableOpened = computed(() => {
<NcMenuItem
v-if="
isUIAllowed('tableDuplicate') &&
project.bases?.[baseIndex] &&
(project.bases[baseIndex].is_meta || project.bases[baseIndex].is_local)
base.sources?.[sourceIndex] &&
(base.sources[sourceIndex].is_meta || base.sources[sourceIndex].is_local)
"
v-e="['c:table:duplicate']"
:data-testid="`sidebar-table-duplicate-${table.title}`"
@ -282,7 +282,7 @@ const isTableOpened = computed(() => {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('tableDelete', { roles: projectRole })"
v-if="isUIAllowed('tableDelete', { roles: baseRole })"
v-e="['c:table:delete']"
:data-testid="`sidebar-table-delete-${table.title}`"
class="!text-red-500 !hover:bg-red-50"
@ -310,13 +310,13 @@ const isTableOpened = computed(() => {
</div>
</div>
<DlgTableDelete
v-if="table.id && project?.id"
v-if="table.id && base?.id"
v-model:visible="isTableDeleteDialogVisible"
:table-id="table.id"
:project-id="project.id"
:base-id="base.id"
/>
</GeneralTooltip>
<DashboardTreeViewViewsList v-if="isExpanded" :table-id="table.id" :project-id="project.id" />
<DashboardTreeViewViewsList v-if="isExpanded" :table-id="table.id" :base-id="base.id" />
</div>
</template>

18
packages/nc-gui/components/dashboard/TreeView/ViewsList.vue

@ -28,7 +28,7 @@ interface Emits {
}
const emits = defineEmits<Emits>()
const project = inject(ProjectInj)!
const base = inject(ProjectInj)!
const table = inject(SidebarTableInj)!
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
@ -39,10 +39,10 @@ const { $e } = useNuxtApp()
const { t } = useI18n()
const isDefaultBase = computed(() => {
const base = project.value?.bases?.find((b) => b.id === table.value.base_id)
if (!base) return false
const source = base.value?.sources?.find((b) => b.id === table.value.source_id)
if (!source) return false
return _isDefaultBase(base)
return _isDefaultBase(source)
})
const { viewsByTable, activeView, recentViews } = storeToRefs(useViewsStore())
@ -197,7 +197,7 @@ async function changeView(view: ViewType) {
await navigateToView({
view,
tableId: table.value.id!,
projectId: project.value.id!,
baseId: base.value.id!,
hardReload: view.type === ViewTypes.FORM && selected.value[0] === view.id,
})
@ -217,7 +217,7 @@ async function onRename(view: ViewType, originalTitle?: string, undo = false) {
navigateToView({
view,
tableId: table.value.id!,
projectId: project.value.id!,
baseId: base.value.id!,
hardReload: view.type === ViewTypes.FORM && selected.value[0] === view.id,
})
@ -272,12 +272,12 @@ function openDeleteDialog(view: ViewType) {
emits('deleted')
removeFromRecentViews({ viewId: view.id, tableId: view.fk_model_id, projectId: project.value.id })
removeFromRecentViews({ viewId: view.id, tableId: view.fk_model_id, baseId: base.value.id })
refreshCommandPalette()
if (activeView.value?.id === view.id) {
navigateToTable({
tableId: table.value.id!,
projectId: project.value.id!,
baseId: base.value.id!,
})
}
@ -355,7 +355,7 @@ function onOpenModal({
navigateToView({
view,
tableId: table.value.id!,
projectId: project.value.id!,
baseId: base.value.id!,
hardReload: view.type === ViewTypes.FORM && selected.value[0] === view.id,
})

8
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -45,17 +45,17 @@ const { isMobileMode } = useGlobal()
const { isUIAllowed } = useRoles()
const project = inject(ProjectInj, ref())
const base = inject(ProjectInj, ref())
const activeView = inject(ActiveViewInj, ref())
const isLocked = inject(IsLockedInj, ref(false))
const isDefaultBase = computed(() => {
const base = project.value?.bases?.find((b) => b.id === vModel.value.base_id)
if (!base) return false
const source = base.value?.sources?.find((b) => b.id === vModel.value.source_id)
if (!source) return false
return _isDefaultBase(base)
return _isDefaultBase(source)
})
const isDropdownOpen = ref(false)

60
packages/nc-gui/components/dashboard/TreeView/index.vue

@ -18,8 +18,8 @@ import {
storeToRefs,
useDialog,
useNuxtApp,
useProject,
useProjects,
useBase,
useBases,
useRoles,
useTablesStore,
useTabs,
@ -37,23 +37,23 @@ const router = useRouter()
const route = router.currentRoute
const projectsStore = useProjects()
const basesStore = useBases()
const { createProject: _createProject } = projectsStore
const { createProject: _createProject } = basesStore
const { projects, projectsList, activeProjectId } = storeToRefs(projectsStore)
const { bases, basesList, activeProjectId } = storeToRefs(basesStore)
const { isWorkspaceLoading } = storeToRefs(useWorkspace())
const { openTable } = useTablesStore()
const projectCreateDlg = ref(false)
const baseCreateDlg = ref(false)
const projectStore = useProject()
const baseStore = useBase()
const { loadTables } = projectStore
const { loadTables } = baseStore
const { tables } = storeToRefs(projectStore)
const { tables } = storeToRefs(baseStore)
const { t } = useI18n()
@ -61,15 +61,15 @@ const { activeTable: _activeTable } = storeToRefs(useTablesStore())
const { refreshCommandPalette } = useCommandPalette()
const contextMenuTarget = reactive<{ type?: 'project' | 'base' | 'table' | 'main' | 'layout'; value?: any }>({})
const contextMenuTarget = reactive<{ type?: 'base' | 'source' | 'table' | 'main' | 'layout'; value?: any }>({})
const setMenuContext = (type: 'project' | 'base' | 'table' | 'main' | 'layout', value?: any) => {
const setMenuContext = (type: 'base' | 'source' | 'table' | 'main' | 'layout', value?: any) => {
contextMenuTarget.type = type
contextMenuTarget.value = value
}
function openRenameTableDialog(table: TableType, rightClick = false) {
if (!table || !table.base_id) return
if (!table || !table.source_id) return
$e('c:table:rename')
@ -78,7 +78,7 @@ function openRenameTableDialog(table: TableType, rightClick = false) {
const { close } = useDialog(resolveComponent('DlgTableRename'), {
'modelValue': isOpen,
'tableMeta': table,
'baseId': table.base_id, // || bases.value[0].id,
'sourceId': table.source_id, // || sources.value[0].id,
'onUpdate:modelValue': closeDialog,
})
@ -89,8 +89,8 @@ function openRenameTableDialog(table: TableType, rightClick = false) {
}
}
function openTableCreateDialog(baseId?: string, projectId?: string) {
if (!baseId && !(projectId || projectsList.value[0].id)) return
function openTableCreateDialog(sourceId?: string, baseId?: string) {
if (!sourceId && !(baseId || basesList.value[0].id)) return
$e('c:table:create:navdraw')
@ -98,8 +98,8 @@ function openTableCreateDialog(baseId?: string, projectId?: string) {
const { close } = useDialog(resolveComponent('DlgTableCreate'), {
'modelValue': isOpen,
'baseId': baseId, // || bases.value[0].id,
'projectId': projectId || projectsList.value[0].id,
'sourceId': sourceId, // || sources.value[0].id,
'baseId': baseId || basesList.value[0].id,
'onUpdate:modelValue': closeDialog,
})
@ -111,7 +111,7 @@ function openTableCreateDialog(baseId?: string, projectId?: string) {
}
const duplicateTable = async (table: TableType) => {
if (!table || !table.id || !table.project_id) return
if (!table || !table.id || !table.base_id) return
const isOpen = ref(true)
@ -182,17 +182,17 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
// prevent the key `T` is inputted to table title input
e.preventDefault()
$e('c:shortcut', { key: 'ALT + T' })
const projectId = activeProjectId.value
const project = projectId ? projects.value.get(projectId) : undefined
if (!project) return
const baseId = activeProjectId.value
const base = baseId ? bases.value.get(baseId) : undefined
if (!base) return
if (projectId) openTableCreateDialog(project.bases?.[0].id, projectId)
if (baseId) openTableCreateDialog(base.sources?.[0].id, baseId)
}
break
}
// ALT + L - only show active project
// ALT + L - only show active base
case 76: {
if (route.value.params.projectId) {
if (route.value.params.baseId) {
router.push({
query: {
...route.value.query,
@ -205,7 +205,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
// ALT + D
case 68: {
e.stopPropagation()
projectCreateDlg.value = true
baseCreateDlg.value = true
break
}
}
@ -213,7 +213,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
})
const handleContext = (e: MouseEvent) => {
if (!document.querySelector('.base-context, .table-context')?.contains(e.target as Node)) {
if (!document.querySelector('.source-context, .table-context')?.contains(e.target as Node)) {
setMenuContext('main')
}
}
@ -253,7 +253,7 @@ watch(
watch(
activeProjectId,
() => {
const activeProjectDom = document.querySelector(`.nc-treeview [data-project-id="${activeProjectId.value}"]`)
const activeProjectDom = document.querySelector(`.nc-treeview [data-base-id="${activeProjectId.value}"]`)
if (!activeProjectDom) return
if (isElementInvisible(activeProjectDom)) {
@ -271,15 +271,15 @@ watch(
<div class="nc-treeview-container flex flex-col justify-between select-none">
<div class="text-gray-500 font-medium pl-3.5 mb-1">{{ $t('objects.projects') }}</div>
<div mode="inline" class="nc-treeview pb-0.5 flex-grow min-h-50 overflow-x-hidden">
<template v-if="projectsList?.length">
<ProjectWrapper v-for="project of projectsList" :key="project.id" :project-role="project.project_role" :project="project">
<template v-if="basesList?.length">
<ProjectWrapper v-for="base of basesList" :key="base.id" :base-role="base.project_role" :base="base">
<DashboardTreeViewProjectNode />
</ProjectWrapper>
</template>
<WorkspaceEmptyPlaceholder v-else-if="!isWorkspaceLoading" />
</div>
<WorkspaceCreateProjectDlg v-model="projectCreateDlg" />
<WorkspaceCreateProjectDlg v-model="baseCreateDlg" />
</div>
</template>

10
packages/nc-gui/components/dashboard/settings/AuditTab.vue

@ -1,14 +1,14 @@
<script setup lang="ts">
import { Tooltip as ATooltip, Empty } from 'ant-design-vue'
import type { AuditType } from 'nocodb-sdk'
import { ProjectIdInj, h, iconMap, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useProject } from '#imports'
import { ProjectIdInj, h, iconMap, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useBase } from '#imports'
const { $api } = useNuxtApp()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const _projectId = inject(ProjectIdInj, undefined)
const projectId = computed(() => _projectId.value ?? project.value?.id)
const baseId = computed(() => _projectId.value ?? base.value?.id)
const { t } = useI18n()
@ -26,11 +26,11 @@ const { appInfo } = useGlobal()
async function loadAudits(page = currentPage.value, limit = currentLimit.value) {
try {
if (!project.value?.id) return
if (!base.value?.id) return
isLoading.value = true
const { list, pageInfo } = await $api.project.auditList(projectId.value, {
const { list, pageInfo } = await $api.base.auditList(baseId.value, {
offset: limit * (page - 1),
limit,
})

16
packages/nc-gui/components/dashboard/settings/BaseAudit.vue

@ -1,17 +1,17 @@
<script setup lang="ts">
import { Tooltip as ATooltip, Empty } from 'ant-design-vue'
import type { AuditType } from 'nocodb-sdk'
import { h, iconMap, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useProject } from '#imports'
import { h, iconMap, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useBase } from '#imports'
interface Props {
baseId: string
sourceId: string
}
const props = defineProps<Props>()
const projectStore = useProject()
const baseStore = useBase()
const { project } = storeToRefs(projectStore)
const { base } = storeToRefs(baseStore)
const { $api } = useNuxtApp()
@ -31,14 +31,14 @@ const { appInfo } = useGlobal()
async function loadAudits(page = currentPage.value, limit = currentLimit.value) {
try {
if (!props.baseId) return
if (!props.sourceId) return
isLoading.value = true
const { list, pageInfo } = await $api.project.auditList(project.value.id!, {
const { list, pageInfo } = await $api.base.auditList(base.value.id!, {
offset: limit * (page - 1),
limit,
baseId: props.baseId,
sourceId: props.sourceId,
})
audits.value = list
@ -106,7 +106,7 @@ const columns = [
<div class="flex flex-col gap-4 w-full">
<div v-if="!appInfo.auditEnabled" class="text-red-500">Audit logs are currently disabled by administrators.</div>
<div class="flex flex-row justify-between items-center">
<h6 class="mb-4 first-letter:capital font-bold">Audit : {{ project.title }}</h6>
<h6 class="mb-4 first-letter:capital font-bold">Audit : {{ base.title }}</h6>
<a-button class="self-start !rounded-md" @click="loadAudits">
<!-- Reload -->
<div class="flex items-center gap-2 text-gray-600 font-light">

122
packages/nc-gui/components/dashboard/settings/DataSources.vue

@ -1,7 +1,7 @@
<script setup lang="ts">
import Draggable from 'vuedraggable'
import type { BaseType } from 'nocodb-sdk'
import { ClientType, DataSourcesSubTab, storeToRefs, useCommandPalette, useNuxtApp, useProject } from '#imports'
import type { SourceType } from 'nocodb-sdk'
import { ClientType, DataSourcesSubTab, storeToRefs, useCommandPalette, useNuxtApp, useBase } from '#imports'
interface Props {
state: string
@ -20,16 +20,16 @@ const { $api, $e } = useNuxtApp()
const { t } = useI18n()
const { loadProject } = useProjects()
const { loadProject } = useBases()
const projectStore = useProject()
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
const { projectPageTab } = storeToRefs(useConfigStore())
const { refreshCommandPalette } = useCommandPalette()
const sources = ref<BaseType[]>([])
const sources = ref<SourceType[]>([])
const activeBaseId = ref('')
@ -42,16 +42,16 @@ const forceAwakened = ref(false)
const dataSourcesAwakened = ref(false)
const isDeleteBaseModalOpen = ref(false)
const toBeDeletedBase = ref<BaseType | undefined>()
const toBeDeletedBase = ref<SourceType | undefined>()
async function loadBases(changed?: boolean) {
try {
if (changed) refreshCommandPalette()
await until(() => !!project.value.id).toBeTruthy()
await until(() => !!base.value.id).toBeTruthy()
isReloading.value = true
vReload.value = true
const baseList = await $api.base.list(project.value.id as string)
const baseList = await $api.source.list(base.value.id as string)
if (baseList.list && baseList.list.length) {
sources.value = baseList.list
}
@ -63,46 +63,46 @@ async function loadBases(changed?: boolean) {
}
}
const baseAction = (baseId?: string, action?: string) => {
if (!baseId) return
activeBaseId.value = baseId
const baseAction = (sourceId?: string, action?: string) => {
if (!sourceId) return
activeBaseId.value = sourceId
vState.value = action || ''
}
const openDeleteBase = (base: BaseType) => {
$e('c:base:delete')
const openDeleteBase = (source: SourceType) => {
$e('c:source:delete')
isDeleteBaseModalOpen.value = true
toBeDeletedBase.value = base
toBeDeletedBase.value = source
}
const deleteBase = async () => {
if (!toBeDeletedBase.value) return
try {
await $api.base.delete(toBeDeletedBase.value.project_id as string, toBeDeletedBase.value.id as string)
await $api.source.delete(toBeDeletedBase.value.base_id as string, toBeDeletedBase.value.id as string)
$e('a:base:delete')
$e('a:source:delete')
sources.value.splice(sources.value.indexOf(toBeDeletedBase.value), 1)
await loadProject(project.value.id as string, true)
await loadProject(base.value.id as string, true)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const toggleBase = async (base: BaseType, state: boolean) => {
const toggleBase = async (source: BaseType, state: boolean) => {
try {
if (!state && sources.value.filter((src) => src.enabled).length < 2) {
message.info('There should be at least one enabled base!')
message.info('There should be at least one enabled source!')
return
}
base.enabled = state
await $api.base.update(base.project_id as string, base.id as string, {
id: base.id,
project_id: base.project_id,
enabled: base.enabled,
source.enabled = state
await $api.source.update(source.base_id as string, source.id as string, {
id: source.id,
base_id: source.base_id,
enabled: source.enabled,
})
await loadProject(project.value.id as string, true)
await loadProject(base.value.id as string, true)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -112,24 +112,24 @@ const moveBase = async (e: any) => {
try {
if (e.oldIndex === e.newIndex) return
// sources list is mutated so we have to get the new index and mirror it to backend
const base = sources.value[e.newIndex]
if (base) {
if (!base.order) {
// empty update call to reorder bases (migration)
await $api.base.update(base.project_id as string, base.id as string, {
id: base.id,
project_id: base.project_id,
const source = sources.value[e.newIndex]
if (source) {
if (!source.order) {
// empty update call to reorder sources (migration)
await $api.source.update(source.base_id as string, source.id as string, {
id: source.id,
base_id: source.base_id,
})
message.info(t('info.basesMigrated'))
} else {
await $api.base.update(base.project_id as string, base.id as string, {
id: base.id,
project_id: base.project_id,
await $api.source.update(source.base_id as string, source.id as string, {
id: source.id,
base_id: source.base_id,
order: e.newIndex + 1,
})
}
}
await loadProject(project.value.id as string, true)
await loadProject(base.value.id as string, true)
await loadBases()
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
@ -399,32 +399,32 @@ const isEditBaseModalOpen = computed({
</div>
</div>
</template>
<template #item="{ element: base, index }">
<template #item="{ element: source, index }">
<div v-if="index !== 0" class="ds-table-row border-gray-200">
<div class="ds-table-col ds-table-enabled">
<div class="flex items-center gap-1 cursor-pointer">
<a-tooltip>
<template #title>
<template v-if="base.enabled">{{ $t('activity.hideInUI') }}</template>
<template v-if="source.enabled">{{ $t('activity.hideInUI') }}</template>
<template v-else>{{ $t('activity.showInUI') }}</template>
</template>
<a-switch :checked="base.enabled ? true : false" @change="toggleBase(base, $event)" />
<a-switch :checked="source.enabled ? true : false" @change="toggleBase(source, $event)" />
</a-tooltip>
</div>
</div>
<div class="ds-table-col ds-table-name font-medium">
<GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" />
<div v-if="base.is_meta || base.is_local">-</div>
<div v-if="source.is_meta || source.is_local">-</div>
<div v-else class="flex items-center gap-1">
{{ base.is_meta || base.is_local ? $t('general.base') : base.alias }}
{{ source.is_meta || source.is_local ? $t('general.base') : source.alias }}
</div>
</div>
<div class="ds-table-col ds-table-type">
<GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" />
<div class="flex items-center gap-2">
<GeneralBaseLogo :base-type="base.type" />
<span class="text-gray-700 capitalize">{{ base.type }}</span>
<GeneralBaseLogo :source-type="source.type" />
<span class="text-gray-700 capitalize">{{ source.type }}</span>
</div>
</div>
@ -433,7 +433,7 @@ const isEditBaseModalOpen = computed({
<a-button
class="nc-action-btn cursor-pointer outline-0"
type="text"
@click="baseAction(base.id, DataSourcesSubTab.ERD)"
@click="baseAction(source.id, DataSourcesSubTab.ERD)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="erd" class="group-hover:text-accent" />
@ -443,7 +443,7 @@ const isEditBaseModalOpen = computed({
<a-button
type="text"
class="nc-action-btn cursor-pointer outline-0"
@click="baseAction(base.id, DataSourcesSubTab.UIAcl)"
@click="baseAction(source.id, DataSourcesSubTab.UIAcl)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="acl" class="group-hover:text-accent" />
@ -451,10 +451,10 @@ const isEditBaseModalOpen = computed({
</div>
</a-button>
<a-button
v-if="!base.is_meta && !base.is_local"
v-if="!source.is_meta && !source.is_local"
type="text"
class="nc-action-btn cursor-pointer outline-0"
@click="baseAction(base.id, DataSourcesSubTab.Metadata)"
@click="baseAction(source.id, DataSourcesSubTab.Metadata)"
>
<div class="flex items-center gap-2 text-gray-600">
<GeneralIcon icon="sync" class="group-hover:text-accent" />
@ -465,18 +465,18 @@ const isEditBaseModalOpen = computed({
</div>
<div class="ds-table-col ds-table-crud justify-end gap-x-1">
<a-button
v-if="!base.is_meta && !base.is_local"
v-if="!source.is_meta && !source.is_local"
class="nc-action-btn cursor-pointer outline-0 !w-8 !px-1 !rounded-lg mt-0.5"
type="text"
@click="baseAction(base.id, DataSourcesSubTab.Edit)"
@click="baseAction(source.id, DataSourcesSubTab.Edit)"
>
<GeneralIcon icon="edit" class="text-gray-600 -mt-0.5" />
</a-button>
<a-button
v-if="!base.is_meta && !base.is_local"
v-if="!source.is_meta && !source.is_local"
class="nc-action-btn cursor-pointer outline-0 !w-8 !px-1 !rounded-lg mt-0.5"
type="text"
@click="openDeleteBase(base)"
@click="openDeleteBase(source)"
>
<GeneralIcon icon="delete" class="text-red-500 -mt-0.5" />
</a-button>
@ -490,44 +490,44 @@ const isEditBaseModalOpen = computed({
<div class="py-6 px-8">
<LazyDashboardSettingsDataSourcesCreateBase
:connection-type="clientType"
@base-created="loadBases(true)"
@source-created="loadBases(true)"
@close="isNewBaseModalOpen = false"
/>
</div>
</GeneralModal>
<GeneralModal v-model:visible="isErdModalOpen" size="large">
<div class="h-[80vh]">
<LazyDashboardSettingsErd :base-id="activeBaseId" />
<LazyDashboardSettingsErd :source-id="activeBaseId" />
</div>
</GeneralModal>
<GeneralModal v-model:visible="isMetaDataModal" size="medium">
<div class="p-6">
<LazyDashboardSettingsMetadata :base-id="activeBaseId" @base-synced="loadBases(true)" />
<LazyDashboardSettingsMetadata :source-id="activeBaseId" @source-synced="loadBases(true)" />
</div>
</GeneralModal>
<GeneralModal v-model:visible="isUIAclModalOpen" class="!w-[60rem]">
<div class="p-6">
<LazyDashboardSettingsUIAcl :base-id="activeBaseId" />
<LazyDashboardSettingsUIAcl :source-id="activeBaseId" />
</div>
</GeneralModal>
<GeneralModal v-model:visible="isEditBaseModalOpen" closable :mask-closable="false" size="medium">
<div class="p-6">
<LazyDashboardSettingsDataSourcesEditBase
:base-id="activeBaseId"
@base-updated="loadBases(true)"
:source-id="activeBaseId"
@source-updated="loadBases(true)"
@close="isEditBaseModalOpen = false"
/>
</div>
</GeneralModal>
<GeneralModal v-model:visible="isBaseAuditModalOpen" class="!w-[70rem]">
<div class="p-6">
<LazyDashboardSettingsBaseAudit :base-id="activeBaseId" @close="isBaseAuditModalOpen = false" />
<LazyDashboardSettingsBaseAudit :source-id="activeBaseId" @close="isBaseAuditModalOpen = false" />
</div>
</GeneralModal>
<GeneralDeleteModal v-model:visible="isDeleteBaseModalOpen" :entity-name="$t('general.base')" :on-delete="deleteBase">
<GeneralDeleteModal v-model:visible="isDeleteBaseModalOpen" :entity-name="$t('general.datasource')" :on-delete="deleteBase">
<template #entity-preview>
<div v-if="toBeDeletedBase" class="flex flex-row items-center py-2 px-3.25 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralBaseLogo :base-type="toBeDeletedBase.type" />
<GeneralBaseLogo :source-type="toBeDeletedBase.type" />
<div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-3"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"

4
packages/nc-gui/components/dashboard/settings/Erd.vue

@ -1,11 +1,11 @@
<script setup lang="ts">
const props = defineProps<{
baseId: string
sourceId: string
}>()
</script>
<template>
<div class="w-full h-full !p-0">
<ErdView :base-id="props.baseId" />
<ErdView :source-id="props.sourceId" />
</div>
</template>

20
packages/nc-gui/components/dashboard/settings/Metadata.vue

@ -1,17 +1,17 @@
<script setup lang="ts">
import { Empty, extractSdkResponseErrorMsg, h, iconMap, message, storeToRefs, useI18n, useNuxtApp, useProject } from '#imports'
import { Empty, extractSdkResponseErrorMsg, h, iconMap, message, storeToRefs, useI18n, useNuxtApp, useBase } from '#imports'
const props = defineProps<{
baseId: string
sourceId: string
}>()
const emit = defineEmits(['baseSynced'])
const { $api } = useNuxtApp()
const projectStore = useProject()
const { loadTables } = projectStore
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const { loadTables } = baseStore
const { base } = storeToRefs(baseStore)
const { t } = useI18n()
@ -23,11 +23,11 @@ const metadiff = ref<any[]>([])
async function loadMetaDiff() {
try {
if (!project.value?.id) return
if (!base.value?.id) return
isLoading.value = true
isDifferent.value = false
metadiff.value = await $api.base.metaDiffGet(project.value?.id, props.baseId)
metadiff.value = await $api.source.metaDiffGet(base.value?.id, props.sourceId)
for (const model of metadiff.value) {
if (model.detectedChanges?.length > 0) {
model.syncState = model.detectedChanges.map((el: any) => el?.msg).join(', ')
@ -45,10 +45,10 @@ const { $poller } = useNuxtApp()
async function syncMetaDiff() {
try {
if (!project.value?.id || !isDifferent.value) return
if (!base.value?.id || !isDifferent.value) return
isLoading.value = true
const jobData = await $api.base.metaDiffSync(project.value?.id, props.baseId)
const jobData = await $api.source.metaDiffSync(base.value?.id, props.sourceId)
$poller.subscribe(
{ id: jobData.id },
@ -165,7 +165,7 @@ const columns = [
<div v-if="column.key === 'table_name'">
<div class="flex items-center gap-1">
<div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="record" class="text-gray-500" />
<GeneralTableIcon :meta="record" class="text-gray-500"></GeneralTableIcon>
</div>
<span class="overflow-ellipsis min-w-0 shrink-1">{{ record.title || record.table_name }}</span>
</div>

26
packages/nc-gui/components/dashboard/settings/Misc.vue

@ -1,16 +1,16 @@
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface'
import { onMounted } from '@vue/runtime-core'
import { ProjectIdInj, storeToRefs, useGlobal, useProject, watch } from '#imports'
import { ProjectIdInj, storeToRefs, useGlobal, useBase, watch } from '#imports'
const { includeM2M, showNull } = useGlobal()
const projectStore = useProject()
const projectsStore = useProjects()
const { loadTables, hasEmptyOrNullFilters } = projectStore
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const basesStore = useBases()
const { loadTables, hasEmptyOrNullFilters } = baseStore
const { base } = storeToRefs(baseStore)
const _projectId = inject(ProjectIdInj, undefined)
const projectId = computed(() => _projectId?.value ?? project.value?.id)
const baseId = computed(() => _projectId?.value ?? base.value?.id)
const { t } = useI18n()
@ -19,15 +19,15 @@ watch(includeM2M, async () => await loadTables())
const showNullAndEmptyInFilter = ref()
onMounted(async () => {
await projectsStore.loadProject(projectId.value!, true)
showNullAndEmptyInFilter.value = projectsStore.getProjectMeta(projectId.value!)?.showNullAndEmptyInFilter
await basesStore.loadProject(baseId.value!, true)
showNullAndEmptyInFilter.value = basesStore.getProjectMeta(baseId.value!)?.showNullAndEmptyInFilter
})
async function showNullAndEmptyInFilterOnChange(evt: CheckboxChangeEvent) {
const project = projectsStore.projects.get(projectId.value!)
if (!project) throw new Error(`Project ${projectId.value} not found`)
const base = basesStore.bases.get(baseId.value!)
if (!base) throw new Error(`Base ${baseId.value} not found`)
const meta = projectsStore.getProjectMeta(projectId.value!) ?? {}
const meta = basesStore.getProjectMeta(baseId.value!) ?? {}
// users cannot hide null & empty option if there is existing null / empty filters
if (!evt.target.checked) {
@ -41,9 +41,9 @@ async function showNullAndEmptyInFilterOnChange(evt: CheckboxChangeEvent) {
showNullAndEmptyInFilter: showNullAndEmptyInFilter.value,
}
// update local state
project.meta = newProjectMeta
base.meta = newProjectMeta
// update db
await projectsStore.updateProject(projectId.value!, {
await basesStore.updateProject(baseId.value!, {
meta: JSON.stringify(newProjectMeta),
})
}

16
packages/nc-gui/components/dashboard/settings/Modal.vue

@ -7,7 +7,7 @@ interface Props {
modelValue?: boolean
openKey?: string
dataSourcesState?: string
projectId?: string
baseId?: string
}
interface SubTabGroup {
@ -38,9 +38,9 @@ const vOpenKey = useVModel(props, 'openKey', emits)
const vDataState = useVModel(props, 'dataSourcesState', emits)
const projectId = toRef(props, 'projectId')
const baseId = toRef(props, 'baseId')
provide(ProjectIdInj, projectId)
provide(ProjectIdInj, baseId)
const { $e } = useNuxtApp()
@ -108,8 +108,8 @@ const tabsInfo: TabGroup = {
// $e('c:settings:audit')
// },
// },
projectSettings: {
// Project Settings
baseSettings: {
// Base Settings
title: t('labels.projectSettings'),
icon: iconMap.settings,
subTabs: {
@ -121,7 +121,7 @@ const tabsInfo: TabGroup = {
},
},
onClick: () => {
$e('c:settings:project-settings')
$e('c:settings:base-settings')
},
},
}
@ -257,14 +257,14 @@ watch(
v-model:reload="dataSourcesReload"
class="px-2 pb-2"
:data-testid="`nc-settings-subtab-${selectedSubTab.key}`"
:project-id="projectId"
:base-id="baseId"
@awaken="handleAwaken"
/>
<component
:is="selectedSubTab?.body"
v-else
class="px-2 py-6"
:project-id="projectId"
:base-id="baseId"
:data-testid="`nc-settings-subtab-${selectedSubTab.key}`"
/>
</div>

27
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -13,21 +13,21 @@ import {
useGlobal,
useI18n,
useNuxtApp,
useProject,
useBase,
} from '#imports'
const props = defineProps<{
baseId: string
sourceId: string
}>()
const { t } = useI18n()
const { $api, $e } = useNuxtApp()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const _projectId = inject(ProjectIdInj, ref())
const projectId = computed(() => _projectId.value ?? project.value?.id)
const baseId = computed(() => _projectId.value ?? base.value?.id)
const { includeM2M } = useGlobal()
@ -42,7 +42,7 @@ const searchInput = ref('')
const filteredTables = computed(() =>
tables.value.filter(
(el) =>
el?.base_id === props.baseId &&
el?.source_id === props.sourceId &&
((typeof el?._ptn === 'string' && el._ptn.toLowerCase().includes(searchInput.value.toLowerCase())) ||
(typeof el?.title === 'string' && el.title.toLowerCase().includes(searchInput.value.toLowerCase()))),
),
@ -50,11 +50,11 @@ const filteredTables = computed(() =>
async function loadTableList() {
try {
if (!projectId.value) return
if (!baseId.value) return
isLoading.value = true
tables.value = await $api.project.modelVisibilityList(projectId.value, {
tables.value = await $api.base.modelVisibilityList(baseId.value, {
includeM2M: includeM2M.value,
})
} catch (e) {
@ -66,10 +66,10 @@ async function loadTableList() {
async function saveUIAcl() {
try {
if (!projectId.value) return
if (!baseId.value) return
await $api.project.modelVisibilitySet(
projectId.value,
await $api.base.modelVisibilitySet(
baseId.value,
tables.value.filter((t) => t.edited),
)
// Updated UI ACL for tables successfully
@ -123,7 +123,7 @@ const columns = [
<template>
<div class="flex flex-row w-full items-center justify-center">
<div class="flex flex-col w-[900px]">
<span class="mb-4 first-letter:capital font-bold"> UI ACL : {{ project.title }} </span>
<span class="mb-4 first-letter:capital font-bold"> UI ACL : {{ base.title }} </span>
<div class="flex flex-row items-center w-full mb-4 gap-2 justify-between">
<a-input v-model:value="searchInput" :placeholder="$t('placeholder.searchModels')" class="nc-acl-search !w-[400px]">
<template #prefix>
@ -171,7 +171,10 @@ const columns = [
<div v-if="column.name === 'table_name'">
<div class="flex items-center gap-1">
<div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="{ meta: record.table_meta, type: record.ptype }" class="text-gray-500" />
<GeneralTableIcon
:meta="{ meta: record.table_meta, type: record.ptype }"
class="text-gray-500"
></GeneralTableIcon>
</div>
<GeneralTruncateText>
<span class="overflow-ellipsis min-w-0 shrink-1">{{ record._ptn }}</span>

14
packages/nc-gui/components/dashboard/settings/UIAclTabs.vue

@ -1,22 +1,22 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
</script>
<template>
<div v-if="!project || !project.bases"></div>
<template v-else-if="project.bases.length === 1">
<DashboardSettingsUIAcl :base-id="project.bases[0].id" class="mt-6" />
<div v-if="!base || !base.sources"></div>
<template v-else-if="base.sources.length === 1">
<DashboardSettingsUIAcl :source-id="base.sources[0].id" class="mt-6" />
</template>
<a-tabs v-else class="w-full">
<a-tab-pane v-for="base of project.bases" :key="base.id">
<a-tab-pane v-for="source of base.sources" :key="source.id">
<template #tab>
<div class="tab-title" data-testid="proj-view-tab__all-tables">
<div class="capitalize">{{ base.alias ?? 'Default' }}</div>
<div class="capitalize">{{ source.alias ?? 'Default' }}</div>
</div>
</template>
<DashboardSettingsUIAcl :base-id="base.id" class="mt-6" />
<DashboardSettingsUIAcl :source-id="source.id" class="mt-6" />
</a-tab-pane>
</a-tabs>
</template>

53
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -17,7 +17,7 @@ import {
iconMap,
nextTick,
onMounted,
projectTitleValidator,
baseTitleValidator,
readFile,
ref,
storeToRefs,
@ -29,18 +29,18 @@ import {
const props = defineProps<{ connectionType?: ClientType }>()
const emit = defineEmits(['baseCreated', 'close'])
const emit = defineEmits(['sourceCreated', 'close'])
const connectionType = computed(() => props.connectionType ?? ClientType.MYSQL)
const projectStore = useProject()
const { loadProject } = useProjects()
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const { loadProject } = useBases()
const { base } = storeToRefs(baseStore)
const { loadProjectTables } = useTablesStore()
const _projectId = inject(ProjectIdInj, undefined)
const projectId = computed(() => _projectId?.value ?? project.value?.id)
const baseId = computed(() => _projectId?.value ?? base.value?.id)
const useForm = Form.useForm
@ -56,7 +56,7 @@ const { $e } = useNuxtApp()
const { t } = useI18n()
const creatingBase = ref(false)
const creatingSource = ref(false)
const formState = ref<ProjectCreateForm>({
title: '',
@ -122,9 +122,9 @@ const validators = computed(() => {
'title': [
{
required: true,
message: 'Base name is required',
message: 'Source name is required',
},
projectTitleValidator,
baseTitleValidator,
],
'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator()],
@ -234,7 +234,7 @@ const focusInvalidInput = () => {
const { $poller } = useNuxtApp()
const createBase = async () => {
const createSource = async () => {
try {
await validate()
} catch (e) {
@ -243,15 +243,15 @@ const createBase = async () => {
}
try {
if (!projectId.value) return
if (!baseId.value) return
creatingBase.value = true
creatingSource.value = true
const connection = getConnectionConfig()
const config = { ...formState.value.dataSource, connection }
const jobData = await api.base.create(projectId.value, {
const jobData = await api.source.create(baseId.value, {
alias: formState.value.title,
type: formState.value.dataSource.client,
config,
@ -276,17 +276,17 @@ const createBase = async () => {
if (data.status === JobStatus.COMPLETED) {
$e('a:base:create:extdb')
if (projectId.value) {
await loadProject(projectId.value, true)
await loadProjectTables(projectId.value, true)
if (baseId.value) {
await loadProject(baseId.value, true)
await loadProjectTables(baseId.value, true)
}
emit('baseCreated')
emit('sourceCreated')
emit('close')
creatingBase.value = false
creatingSource.value = false
} else if (status === JobStatus.FAILED) {
message.error('Failed to create base')
creatingBase.value = false
creatingSource.value = false
}
}
},
@ -304,7 +304,7 @@ const testConnection = async () => {
return
}
$e('a:base:create:extdb:test-connection', [])
$e('a:source:create:extdb:test-connection', [])
try {
testingConnection.value = true
@ -403,8 +403,8 @@ watch(
</script>
<template>
<div class="create-base bg-white relative flex flex-col justify-center gap-2 w-full">
<h1 class="prose-2xl font-bold self-start mb-4 flex items-center gap-2">
<div class="create-source bg-white relative flex flex-col justify-center gap-2 w-full">
<h1 class="prose-xl font-bold self-start mb-4 flex items-center gap-2">
{{ $t('title.newBase') }}
<DashboardSettingsDataSourcesInfo />
<span class="flex-grow"></span>
@ -413,7 +413,7 @@ watch(
<a-form
ref="form"
:model="formState"
name="external-project-create-form"
name="external-base-create-form"
layout="horizontal"
no-style
:label-col="{ span: 8 }"
@ -424,7 +424,7 @@ watch(
maxHeight: '60vh',
}"
>
<a-form-item label="Base Name" v-bind="validateInfos.title">
<a-form-item label="Source Name" v-bind="validateInfos.title">
<a-input v-model:value="formState.title" class="nc-extdb-proj-name" />
</a-form-item>
@ -628,13 +628,12 @@ watch(
</NcButton>
<NcButton
v-e="['a:source:create']"
size="small"
type="primary"
:disabled="!testSuccess"
:loading="creatingBase"
class="nc-extdb-btn-submit !rounded-md"
@click="createBase"
@click="createSource"
>
{{ $t('general.submit') }}
</NcButton>
@ -688,7 +687,7 @@ watch(
@apply !min-h-0;
}
.create-base {
.create-source {
:deep(.ant-input-affix-wrapper),
:deep(.ant-input),
:deep(.ant-select) {

42
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { BaseType } from 'nocodb-sdk'
import type { SourceType } from 'nocodb-sdk'
import { Form, message } from 'ant-design-vue'
import type { SelectHandler } from 'ant-design-vue/es/vc-select/Select'
import type { DefaultConnection, ProjectCreateForm, SQLiteConnection } from '#imports'
@ -16,7 +16,7 @@ import {
getTestDatabaseName,
iconMap,
onMounted,
projectTitleValidator,
baseTitleValidator,
readFile,
ref,
storeToRefs,
@ -27,17 +27,17 @@ import {
} from '#imports'
const props = defineProps<{
baseId: string
sourceId: string
}>()
const emit = defineEmits(['baseUpdated', 'close'])
const projectStore = useProject()
const projectsStore = useProjects()
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const basesStore = useBases()
const { base } = storeToRefs(baseStore)
const _projectId = inject(ProjectIdInj, undefined)
const projectId = computed(() => _projectId?.value ?? project.value?.id)
const baseId = computed(() => _projectId?.value ?? base.value?.id)
const useForm = Form.useForm
@ -79,7 +79,7 @@ const customFormState = ref<ProjectCreateForm>({
const validators = computed(() => {
return {
'title': [projectTitleValidator],
'title': [baseTitleValidator],
'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator()],
...(formState.value.dataSource.client === ClientType.SQLITE
@ -214,13 +214,13 @@ const editBase = async () => {
}
try {
if (!project.value?.id) return
if (!base.value?.id) return
const connection = getConnectionConfig()
const config = { ...formState.value.dataSource, connection }
await api.base.update(project.value?.id, props.baseId, {
await api.source.update(base.value?.id, props.sourceId, {
alias: formState.value.title,
type: formState.value.dataSource.client,
config,
@ -228,9 +228,9 @@ const editBase = async () => {
inflection_table: formState.value.inflection.inflectionTable,
})
$e('a:base:edit:extdb')
$e('a:source:edit:extdb')
await projectsStore.loadProject(projectId.value!, true)
await basesStore.loadProject(baseId.value!, true)
emit('baseUpdated')
emit('close')
} catch (e: any) {
@ -246,7 +246,7 @@ const testConnection = async () => {
return
}
$e('a:base:edit:extdb:test-connection', [])
$e('a:source:edit:extdb:test-connection', [])
try {
testingConnection.value = true
@ -315,12 +315,12 @@ watch(
{ deep: true },
)
// load base config
// load source config
onMounted(async () => {
if (project.value?.id) {
if (base.value?.id) {
const definedParameters = ['host', 'port', 'user', 'password', 'database']
const activeBase = (await api.base.read(project.value?.id, props.baseId)) as BaseType
const activeBase = (await api.source.read(base.value?.id, props.sourceId)) as SourceType
const tempParameters = Object.entries(activeBase.config.connection)
.filter(([key]) => !definedParameters.includes(key))
@ -342,13 +342,13 @@ onMounted(async () => {
</script>
<template>
<div class="edit-base bg-white relative flex flex-col justify-start gap-2 w-full p-2">
<h1 class="prose-2xl font-bold self-start">{{ $t('activity.editBase') }}</h1>
<div class="edit-source bg-white relative flex flex-col justify-start gap-2 w-full p-2">
<h1 class="prose-2xl font-bold self-start">{{ $t('activity.editSource') }}</h1>
<a-form
ref="form"
:model="formState"
name="external-project-create-form"
name="external-base-create-form"
layout="horizontal"
no-style
:label-col="{ span: 8 }"
@ -359,7 +359,7 @@ onMounted(async () => {
maxHeight: '60vh',
}"
>
<a-form-item label="Base Name" v-bind="validateInfos.title">
<a-form-item label="Source Name" v-bind="validateInfos.title">
<a-input v-model:value="formState.title" class="nc-extdb-proj-name" />
</a-form-item>
@ -625,7 +625,7 @@ onMounted(async () => {
@apply !min-h-0;
}
.edit-base {
.edit-source {
:deep(.ant-input-affix-wrapper),
:deep(.ant-input),
:deep(.ant-select) {

22
packages/nc-gui/components/dlg/AirtableImport.vue

@ -13,13 +13,13 @@ import {
ref,
storeToRefs,
useNuxtApp,
useProject,
useBase,
watch,
} from '#imports'
const { modelValue, baseId } = defineProps<{
const { modelValue, sourceId } = defineProps<{
modelValue: boolean
baseId: string
sourceId: string
}>()
const emit = defineEmits(['update:modelValue'])
@ -30,13 +30,13 @@ const baseURL = $api.instance.defaults.baseURL
const { $state, $poller } = useNuxtApp()
const projectStore = useProject()
const baseStore = useBase()
const { refreshCommandPalette } = useCommandPalette()
const { loadTables } = projectStore
const { loadTables } = baseStore
const { project } = storeToRefs(projectStore)
const { base } = storeToRefs(baseStore)
const showGoToDashboardButton = ref(false)
@ -128,14 +128,14 @@ async function createOrUpdate() {
const { id, ...payload } = syncSource.value
if (id !== '') {
await $fetch(`/api/v1/db/meta/syncs/${id}`, {
await $fetch(`/api/v1/meta/syncs/${id}`, {
baseURL,
method: 'PATCH',
headers: { 'xc-auth': $state.token.value as string },
body: payload,
})
} else {
syncSource.value = await $fetch(`/api/v1/db/meta/projects/${project.value.id}/syncs/${baseId}`, {
syncSource.value = await $fetch(`/api/v1/meta/bases/${base.value.id}/syncs/${sourceId}`, {
baseURL,
method: 'POST',
headers: { 'xc-auth': $state.token.value as string },
@ -187,7 +187,7 @@ async function listenForUpdates() {
}
async function loadSyncSrc() {
const data: any = await $fetch(`/api/v1/db/meta/projects/${project.value.id}/syncs/${baseId}`, {
const data: any = await $fetch(`/api/v1/meta/bases/${base.value.id}/syncs/${sourceId}`, {
baseURL,
method: 'GET',
headers: { 'xc-auth': $state.token.value as string },
@ -229,7 +229,7 @@ async function loadSyncSrc() {
async function sync() {
try {
await $fetch(`/api/v1/db/meta/syncs/${syncSource.value.id}/trigger`, {
await $fetch(`/api/v1/meta/syncs/${syncSource.value.id}/trigger`, {
baseURL,
method: 'POST',
headers: { 'xc-auth': $state.token.value as string },
@ -248,7 +248,7 @@ async function abort() {
"This is a highly experimental feature and only marks job as not started, please don't abort the job unless you are sure job is stuck.",
onOk: async () => {
try {
await $fetch(`/api/v1/db/meta/syncs/${syncSource.value.id}/abort`, {
await $fetch(`/api/v1/meta/syncs/${syncSource.value.id}/abort`, {
baseURL,
method: 'POST',
headers: { 'xc-auth': $state.token.value as string },

24
packages/nc-gui/components/dlg/ProjectDelete.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
const props = defineProps<{
visible: boolean
projectId: string
baseId: string
}>()
const emits = defineEmits(['update:visible'])
@ -10,22 +10,22 @@ const visible = useVModel(props, 'visible', emits)
const { closeTab } = useTabs()
const projectsStore = useProjects()
const { deleteProject, navigateToFirstProjectOrHome } = projectsStore
const { projects } = storeToRefs(projectsStore)
const basesStore = useBases()
const { deleteProject, navigateToFirstProjectOrHome } = basesStore
const { bases } = storeToRefs(basesStore)
const { removeFromRecentViews } = useViewsStore()
const { refreshCommandPalette } = useCommandPalette()
const project = computed(() => projects.value.get(props.projectId))
const base = computed(() => bases.value.get(props.baseId))
const isLoading = ref(false)
const onDelete = async () => {
if (!project.value) return
if (!base.value) return
const toBeDeletedProject = JSON.parse(JSON.stringify(project.value))
const toBeDeletedProject = JSON.parse(JSON.stringify(base.value))
isLoading.value = true
try {
@ -36,14 +36,14 @@ const onDelete = async () => {
visible.value = false
if (toBeDeletedProject.id === projectsStore.activeProjectId) {
if (toBeDeletedProject.id === basesStore.activeProjectId) {
await navigateToFirstProjectOrHome()
}
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
} finally {
isLoading.value = false
removeFromRecentViews({ projectId: toBeDeletedProject.id! })
removeFromRecentViews({ baseId: toBeDeletedProject.id! })
}
}
</script>
@ -51,13 +51,13 @@ const onDelete = async () => {
<template>
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.project')" :on-delete="onDelete">
<template #entity-preview>
<div v-if="project" class="flex flex-row items-center py-2 px-2.25 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralProjectIcon :type="project.type" class="nc-view-icon px-1.5"></GeneralProjectIcon>
<div v-if="base" class="flex flex-row items-center py-2 px-2.25 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralProjectIcon :type="base.type" class="nc-view-icon px-1.5"></GeneralProjectIcon>
<div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ project.title }}
{{ base.title }}
</div>
</div>
</template>

18
packages/nc-gui/components/dlg/ProjectDuplicate.vue

@ -1,11 +1,11 @@
<script setup lang="ts">
import tinycolor from 'tinycolor2'
import type { ProjectType } from 'nocodb-sdk'
import type { BaseType } from 'nocodb-sdk'
import { useVModel } from '#imports'
const props = defineProps<{
modelValue: boolean
project: ProjectType
base: BaseType
onOk: (jobData: { name: string; id: string }) => Promise<void>
}>()
@ -35,17 +35,17 @@ const isLoading = ref(false)
const _duplicate = async () => {
try {
isLoading.value = true
// pick a random color from array and assign to project
const color = projectThemeColors[Math.floor(Math.random() * 1000) % projectThemeColors.length]
// pick a random color from array and assign to base
const color = baseThemeColors[Math.floor(Math.random() * 1000) % baseThemeColors.length]
const tcolor = tinycolor(color)
const complement = tcolor.complement()
const jobData = await api.project.duplicate(props.project.id as string, {
const jobData = await api.base.duplicate(props.base.id as string, {
options: optionsToExclude.value,
project: {
fk_workspace_id: props.project.fk_workspace_id,
type: props.project.type,
base: {
fk_workspace_id: props.base.fk_workspace_id,
type: props.base.type,
color,
meta: JSON.stringify({
theme: {
@ -75,7 +75,7 @@ const isEaster = ref(false)
</script>
<template>
<GeneralModal v-if="project" v-model:visible="dialogShow" class="!w-[30rem]" wrap-class-name="nc-modal-project-duplicate">
<GeneralModal v-if="base" v-model:visible="dialogShow" class="!w-[30rem]" wrap-class-name="nc-modal-base-duplicate">
<div>
<div class="prose-xl font-bold self-center" @dblclick="isEaster = !isEaster">
{{ $t('general.duplicate') }} {{ $t('objects.project') }}

12
packages/nc-gui/components/dlg/QuickImport.vue

@ -30,7 +30,7 @@ import {
storeToRefs,
useGlobal,
useI18n,
useProject,
useBase,
useVModel,
} from '#imports'
@ -41,11 +41,11 @@ import { useNuxtApp } from '#app'
interface Props {
modelValue: boolean
importType: 'csv' | 'json' | 'excel'
baseId: string
sourceId: string
importDataOnly?: boolean
}
const { importType, importDataOnly = false, baseId, ...rest } = defineProps<Props>()
const { importType, importDataOnly = false, sourceId, ...rest } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
@ -63,7 +63,7 @@ const { t } = useI18n()
const progressMsg = ref('Parsing Data ...')
const { tables } = storeToRefs(useProject())
const { tables } = storeToRefs(useBase())
const activeKey = ref('uploadTab')
@ -546,13 +546,13 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
<LazyTemplateEditor
v-if="templateEditorModal"
ref="templateEditorRef"
:project-template="templateData"
:base-template="templateData"
:import-data="importData"
:import-columns="importColumns"
:import-data-only="importDataOnly"
:quick-import-type="importType"
:max-rows-to-parse="importState.parserConfig.maxRowsToParse"
:base-id="baseId"
:source-id="sourceId"
:import-worker="importWorker"
class="nc-quick-import-template-editor"
@import="handleImport"

32
packages/nc-gui/components/dlg/TableCreate.vue

@ -6,7 +6,7 @@ import {
nextTick,
onMounted,
ref,
useProject,
useBase,
useTableNew,
useTablesStore,
useTabs,
@ -16,8 +16,8 @@ import {
const props = defineProps<{
modelValue: boolean
sourceId: string
baseId: string
projectId: string
}>()
const emit = defineEmits(['update:modelValue', 'create'])
@ -30,30 +30,30 @@ const inputEl = ref<HTMLInputElement>()
const { addTab } = useTabs()
const { isMysql, isMssql, isPg } = useProject()
const { isMysql, isMssql, isPg } = useBase()
const { loadProjectTables, addTable } = useTablesStore()
const { table, createTable, generateUniqueTitle, tables, project } = useTableNew({
const { table, createTable, generateUniqueTitle, tables, base } = useTableNew({
async onTableCreate(table) {
// await loadProject(props.projectId)
// await loadProject(props.baseId)
await addTab({
id: table.id as string,
title: table.title,
type: TabType.TABLE,
projectId: props.projectId,
// baseId: props.baseId,
baseId: props.baseId,
// sourceId: props.sourceId,
})
addTable(props.projectId, table)
await loadProjectTables(props.projectId, true)
addTable(props.baseId, table)
await loadProjectTables(props.baseId, true)
emit('create', table)
dialogShow.value = false
},
sourceId: props.sourceId,
baseId: props.baseId,
projectId: props.projectId,
})
const useForm = Form.useForm
@ -66,7 +66,7 @@ const validators = computed(() => {
validator: (_: any, value: any) => {
// validate duplicate alias
return new Promise((resolve, reject) => {
if ((tables.value || []).some((t) => t.title === (value || '') && t.base_id === props.baseId)) {
if ((tables.value || []).some((t) => t.title === (value || '') && t.source_id === props.sourceId)) {
return reject(new Error('Duplicate table alias'))
}
return resolve(true)
@ -77,15 +77,15 @@ const validators = computed(() => {
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
let tableNameLengthLimit = 255
if (isMysql(props.baseId)) {
if (isMysql(props.sourceId)) {
tableNameLengthLimit = 64
} else if (isPg(props.baseId)) {
} else if (isPg(props.sourceId)) {
tableNameLengthLimit = 63
} else if (isMssql(props.baseId)) {
} else if (isMssql(props.sourceId)) {
tableNameLengthLimit = 128
}
const projectPrefix = project?.value?.prefix || ''
if ((projectPrefix + value).length > tableNameLengthLimit) {
const basePrefix = base?.value?.prefix || ''
if ((basePrefix + value).length > tableNameLengthLimit) {
return reject(new Error(`Table name exceeds ${tableNameLengthLimit} characters`))
}
resolve()

20
packages/nc-gui/components/dlg/TableDelete.vue

@ -5,7 +5,7 @@ import { UITypes, isSystemColumn } from 'nocodb-sdk'
const props = defineProps<{
visible: boolean
tableId: string
projectId: string
baseId: string
}>()
const emits = defineEmits(['update:visible'])
@ -17,14 +17,14 @@ const { closeTab } = useTabs()
const { getMeta, removeMeta } = useMetas()
const { loadTables, projectUrl, isXcdbBase } = useProject()
const { loadTables, baseUrl, isXcdbBase } = useBase()
const { refreshCommandPalette } = useCommandPalette()
const { removeFromRecentViews } = useViewsStore()
const { projectTables, activeTable } = storeToRefs(useTablesStore())
const { baseTables, activeTable } = storeToRefs(useTablesStore())
const { openTable } = useTablesStore()
const tables = computed(() => projectTables.value.get(props.projectId) ?? [])
const tables = computed(() => baseTables.value.get(props.baseId) ?? [])
const table = computed(() => tables.value.find((t) => t.id === props.tableId))
@ -43,7 +43,7 @@ const onDelete = async () => {
const meta = (await getMeta(toBeDeletedTable.id as string, true)) as TableType
const relationColumns = meta?.columns?.filter((c) => c.uidt === UITypes.LinkToAnotherRecord && !isSystemColumn(c))
if (relationColumns?.length && !isXcdbBase(toBeDeletedTable.base_id)) {
if (relationColumns?.length && !isXcdbBase(toBeDeletedTable.source_id)) {
const refColMsgs = await Promise.all(
relationColumns.map(async (c, i) => {
const refMeta = (await getMeta((c?.colOptions as LinkToAnotherRecordType)?.fk_related_model_id as string)) as TableType
@ -71,7 +71,7 @@ const onDelete = async () => {
await loadTables()
// Remove from recent views
removeFromRecentViews({ projectId: props.projectId, tableId: toBeDeletedTable.id as string })
removeFromRecentViews({ baseId: props.baseId, tableId: toBeDeletedTable.id as string })
removeMeta(toBeDeletedTable.id as string)
refreshCommandPalette()
@ -79,11 +79,11 @@ const onDelete = async () => {
$e('a:table:delete')
if (oldActiveTableId === toBeDeletedTable.id) {
// Navigate to project if no tables left or open first table
// Navigate to base if no tables left or open first table
if (tables.value.length === 0) {
await navigateTo(
projectUrl({
id: props.projectId,
baseUrl({
id: props.baseId,
type: 'database',
}),
)
@ -110,7 +110,7 @@ const onDelete = async () => {
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.table')" :on-delete="onDelete">
<template #entity-preview>
<div v-if="table" class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralTableIcon :meta="table" class="nc-view-icon" />
<GeneralTableIcon :meta="table" class="nc-view-icon"></GeneralTableIcon>
<div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"

2
packages/nc-gui/components/dlg/TableDuplicate.vue

@ -34,7 +34,7 @@ const isLoading = ref(false)
const _duplicate = async () => {
try {
isLoading.value = true
const jobData = await api.dbTable.duplicate(props.table.project_id!, props.table.id!, { options: optionsToExclude.value })
const jobData = await api.dbTable.duplicate(props.table.base_id!, props.table.id!, { options: optionsToExclude.value })
props.onOk(jobData as any)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))

28
packages/nc-gui/components/dlg/TableRename.vue

@ -13,7 +13,7 @@ import {
useCommandPalette,
useMetas,
useNuxtApp,
useProject,
useBase,
useTablesStore,
useTabs,
useUndoRedo,
@ -25,10 +25,10 @@ import {
interface Props {
modelValue?: boolean
tableMeta: TableType
baseId: string
sourceId: string
}
const { tableMeta, baseId, ...props } = defineProps<Props>()
const { tableMeta, sourceId, ...props } = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'updated'])
@ -42,9 +42,9 @@ const { updateTab } = useTabs()
const { loadProjectTables } = useTablesStore()
const projectStore = useProject()
const { loadTables, isMysql, isMssql, isPg } = projectStore
const { tables, project } = storeToRefs(projectStore)
const baseStore = useBase()
const { loadTables, isMysql, isMssql, isPg } = baseStore
const { tables, base } = storeToRefs(baseStore)
const { refreshCommandPalette } = useCommandPalette()
@ -68,15 +68,15 @@ const validators = computed(() => {
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
let tableNameLengthLimit = 255
if (isMysql(baseId)) {
if (isMysql(sourceId)) {
tableNameLengthLimit = 64
} else if (isPg(baseId)) {
} else if (isPg(sourceId)) {
tableNameLengthLimit = 63
} else if (isMssql(baseId)) {
} else if (isMssql(sourceId)) {
tableNameLengthLimit = 128
}
const projectPrefix = project?.value?.prefix || ''
if ((projectPrefix + value).length > tableNameLengthLimit) {
const basePrefix = base?.value?.prefix || ''
if ((basePrefix + value).length > tableNameLengthLimit) {
return reject(new Error(`Table name exceeds ${tableNameLengthLimit} characters`))
}
resolve()
@ -127,14 +127,14 @@ const renameTable = async (undo = false, disableTitleDiffCheck?: boolean | undef
loading.value = true
try {
await $api.dbTable.update(tableMeta.id as string, {
project_id: tableMeta.project_id,
base_id: tableMeta.base_id,
table_name: formState.title,
title: formState.title,
})
dialogShow.value = false
await loadProjectTables(tableMeta.project_id!, true)
await loadProjectTables(tableMeta.base_id!, true)
if (!undo) {
addUndo({
@ -168,7 +168,7 @@ const renameTable = async (undo = false, disableTitleDiffCheck?: boolean | undef
$e('a:table:rename')
useTitle(`${project.value?.title}: ${newMeta?.title}`)
useTitle(`${base.value?.title}: ${newMeta?.title}`)
dialogShow.value = false
} catch (e: any) {

6
packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue

@ -66,7 +66,7 @@ watch(
class="!rounded-md !ml-0.5"
validate-trigger="onBlur"
placeholder="Add people by email..."
data-testid="docs-share-dlg-share-project-collaborate-emails"
data-testid="docs-share-dlg-share-base-collaborate-emails"
/>
</a-form-item>
</div>
@ -77,9 +77,9 @@ watch(
v-model:value="invitationUsersData.role"
class="!rounded-md !bg-white"
dropdown-class-name="nc-dropdown-user-role !rounded-md"
data-testid="docs-share-dlg-share-project-collaborate-role"
data-testid="docs-share-dlg-share-base-collaborate-role"
>
<a-select-option v-for="(role, index) in projectRoles" :key="index" :value="role" class="nc-role-option">
<a-select-option v-for="(role, index) in baseRoles" :key="index" :value="role" class="nc-role-option">
<div
class="flex flex-row h-full justify-start items-center"
:data-testid="`nc-share-invite-user-role-option-${role}`"

4
packages/nc-gui/components/dlg/share-and-collaborate/ManageUsers.vue

@ -73,7 +73,7 @@ const rolesTypes = [
<template>
<div class="flex flex-col mx-4 h-112">
<div class="flex mt-2.5 mb-2.5 border-b-1 border-gray-50 pb-1.5" :style="{ fontWeight: 500 }">Manage Members</div>
<div class="flex mt-2.5 mb-2.5 text-xs font-bold">Project Owner</div>
<div class="flex mt-2.5 mb-2.5 text-xs font-bold">Base Owner</div>
<div v-if="owner" class="flex flex-row px-2 py-2 items-center gap-x-2 border-1 border-gray-100 rounded-md">
<a-avatar></a-avatar>
<div class="flex flex-col justify-center">
@ -91,7 +91,7 @@ const rolesTypes = [
</div>
</div>
<div class="flex flex-col mb-2 pr-0.5 h-96 overflow-y-auto users-list border-b-1 border-gray-100">
<div v-if="nonOwners.length === 0" class="text-xs mt-2">No users have access to this project</div>
<div v-if="nonOwners.length === 0" class="text-xs mt-2">No users have access to this base</div>
<div
v-for="user of nonOwners"
:key="user.id"

38
packages/nc-gui/components/dlg/share-and-collaborate/ShareBase.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { extractSdkResponseErrorMsg, message, onMounted, storeToRefs, useDashboard, useNuxtApp, useProject } from '#imports'
import { extractSdkResponseErrorMsg, message, onMounted, storeToRefs, useDashboard, useNuxtApp, useBase } from '#imports'
interface ShareBase {
uuid?: string
@ -16,19 +16,19 @@ const { dashboardUrl } = useDashboard()
const { $api, $e } = useNuxtApp()
const base = ref<null | ShareBase>(null)
const sharedBase = ref<null | ShareBase>(null)
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const url = computed(() => (base.value && base.value.uuid ? `${dashboardUrl.value}#/base/${base.value.uuid}` : ''))
const url = computed(() => (sharedBase.value && sharedBase.value.uuid ? `${dashboardUrl.value}#/base/${sharedBase.value.uuid}` : ''))
const loadBase = async () => {
try {
if (!project.value.id) return
if (!base.value.id) return
const res = await $api.project.sharedBaseGet(project.value.id)
const res = await $api.base.sharedBaseGet(base.value.id)
base.value = {
sharedBase.value = {
uuid: res.uuid,
url: res.url,
role: res.roles,
@ -40,14 +40,14 @@ const loadBase = async () => {
const createShareBase = async (role = ShareBaseRole.Viewer) => {
try {
if (!project.value.id) return
if (!base.value.id) return
const res = await $api.project.sharedBaseUpdate(project.value.id, {
const res = await $api.base.sharedBaseUpdate(base.value.id, {
roles: role,
})
base.value = res ?? {}
base.value!.role = role
sharedBase.value = res ?? {}
sharedBase.value!.role = role
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -57,10 +57,10 @@ const createShareBase = async (role = ShareBaseRole.Viewer) => {
const disableSharedBase = async () => {
try {
if (!project.value.id) return
if (!base.value.id) return
await $api.project.sharedBaseDisable(project.value.id)
base.value = null
await $api.base.sharedBaseDisable(base.value.id)
sharedBase.value = null
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -69,12 +69,12 @@ const disableSharedBase = async () => {
}
onMounted(() => {
if (!base.value) {
if (!sharedBase.value) {
loadBase()
}
})
const isSharedBaseEnabled = computed(() => !!base.value?.uuid)
const isSharedBaseEnabled = computed(() => !!sharedBase.value?.uuid)
const isToggleBaseLoading = ref(false)
const isRoleToggleLoading = ref(false)
@ -96,12 +96,12 @@ const toggleSharedBase = async () => {
}
const onRoleToggle = async () => {
if (!base.value) return
if (!sharedBase.value) return
if (isRoleToggleLoading.value) return
isRoleToggleLoading.value = true
try {
if (base.value.role === ShareBaseRole.Viewer) {
if (sharedBase.value.role === ShareBaseRole.Viewer) {
await createShareBase(ShareBaseRole.Editor)
} else {
await createShareBase(ShareBaseRole.Viewer)
@ -134,7 +134,7 @@ const onRoleToggle = async () => {
<a-switch
v-e="['c:share:base:role:toggle']"
:loading="isRoleToggleLoading"
:checked="base?.role === ShareBaseRole.Editor"
:checked="sharedBase?.role === ShareBaseRole.Editor"
class="ml-2"
@click="onRoleToggle"
/>

2
packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue

@ -371,7 +371,7 @@ const isPublicShareDisabled = computed(() => {
data-testid="nc-modal-share-view__theme-picker"
class="!p-0 !bg-inherit"
:model-value="activeView?.meta?.theme?.primaryColor"
:colors="projectThemeColors"
:colors="baseThemeColors"
:row-size="9"
:advanced="false"
@input="onChangeTheme"

6
packages/nc-gui/components/dlg/share-and-collaborate/ShareProject.vue

@ -5,14 +5,14 @@ import ShareBase from './ShareBase.vue'
const { formStatus } = storeToRefs(useShare())
onMounted(async () => {
formStatus.value = 'project-collaborate'
formStatus.value = 'base-collaborate'
})
</script>
<template>
<div class="flex flex-col mx-4 mb-4 mt-2">
<a-tabs v-model:activeKey="formStatus">
<a-tab-pane key="project-collaborate">
<a-tab-pane key="base-collaborate">
<template #tab>
<div class="flex flex-row items-center text-xs px-2">
<MdiAccountPlusOutline class="mr-1" />
@ -25,7 +25,7 @@ onMounted(async () => {
<template #tab>
<div class="flex flex-row items-center text-xs px-2">
<MdiEarth class="mr-1" />
<div data-testid="db-share-base">Share Base</div>
<div data-testid="db-share-base">Share Source</div>
</div>
</template>
<ShareBase />

20
packages/nc-gui/components/dlg/share-and-collaborate/View.vue

@ -10,9 +10,9 @@ const { isViewToolbar } = defineProps<{
const { copy } = useCopy()
const { dashboardUrl } = useDashboard()
const projectStore = useProject()
const { project } = storeToRefs(projectStore)
const { navigateToProjectPage } = projectStore
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
const { navigateToProjectPage } = baseStore
const { activeView } = storeToRefs(useViewsStore())
let view: Ref<ViewType | undefined>
@ -29,7 +29,7 @@ const { formStatus, showShareModal, invitationUsersData, isInvitationLinkCopied
const { resetData } = useShare()
// const { inviteUser } = useManageUsers()
// const expandedSharedType = ref<'none' | 'project' | 'view'>('view')
// const expandedSharedType = ref<'none' | 'base' | 'view'>('view')
const isOpeningManageAccess = ref(false)
const inviteUrl = computed(() =>
@ -89,17 +89,17 @@ watch(showShareModal, (val) => {
:class="{ active: showShareModal }"
wrap-class-name="nc-modal-share-collaborate"
:closable="false"
:mask-closable="formStatus === 'project-collaborateSaving' ? false : true"
:mask-closable="formStatus === 'base-collaborateSaving' ? false : true"
:ok-button-props="{ hidden: true } as any"
:cancel-button-props="{ hidden: true } as any"
:footer="null"
:width="formStatus === 'manageCollaborators' ? '60rem' : '40rem'"
>
<div v-if="formStatus === 'project-collaborateSaving'" class="flex flex-row w-full px-5 justify-between items-center py-1">
<div v-if="formStatus === 'base-collaborateSaving'" class="flex flex-row w-full px-5 justify-between items-center py-1">
<div class="flex text-base font-bold">Adding Members</div>
<a-spin :indicator="indicator" />
</div>
<template v-else-if="formStatus === 'project-collaborateSaved'">
<template v-else-if="formStatus === 'base-collaborateSaved'">
<div class="flex flex-col py-1.5">
<div class="flex flex-row w-full px-5 justify-between items-center py-0.5">
<div class="flex text-base font-medium">Members added</div>
@ -154,14 +154,14 @@ watch(showShareModal, (val) => {
</div>
<div class="share-base">
<div class="flex flex-row items-center gap-x-2 px-4 pt-3 pb-3 select-none">
<GeneralProjectIcon :type="project.type" class="nc-view-icon group-hover" />
<GeneralProjectIcon :type="base.type" class="nc-view-icon group-hover" />
<div>{{ $t('activity.shareBase.label') }}</div>
<div
class="max-w-79/100 ml-2 px-2 py-0.5 rounded-md bg-gray-100 capitalize text-ellipsis overflow-hidden"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap' }"
>
{{ project.title }}
{{ base.title }}
</div>
</div>
<LazyDlgShareAndCollaborateShareBase />
@ -179,7 +179,7 @@ watch(showShareModal, (val) => {
>
<!-- <a-button
v-if="formStatus === 'project-collaborate'"
v-if="formStatus === 'base-collaborate'"
data-testid="docs-share-btn"
class="!border-0 !rounded-md"
type="primary"

16
packages/nc-gui/components/erd/View.vue

@ -1,11 +1,11 @@
<script setup lang="ts">
import type { BaseType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import type { SourceType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { isLinksOrLTAR } from 'nocodb-sdk'
import type { ERDConfig } from './utils'
import { reactive, ref, storeToRefs, useMetas, useProject, watch } from '#imports'
import { reactive, ref, storeToRefs, useMetas, useBase, watch } from '#imports'
const props = defineProps({
baseId: {
sourceId: {
type: String,
default: '',
},
@ -19,7 +19,7 @@ const props = defineProps({
},
})
const { bases, tables: projectTables } = storeToRefs(useProject())
const { sources, tables: baseTables } = storeToRefs(useBase())
const { metas, getMeta } = useMetas()
@ -51,7 +51,7 @@ const populateTables = async () => {
let localTables: TableType[] = []
if (props.table) {
// if table is provided only get the table and its related tables
localTables = projectTables.value.filter(
localTables = baseTables.value.filter(
(t) =>
t.id === props.table?.id ||
metas.value[props.table!.id!].columns?.find((column) => {
@ -59,7 +59,7 @@ const populateTables = async () => {
}),
)
} else {
localTables = projectTables.value
localTables = baseTables.value
}
await loadMetaOfTablesNotInMetas(localTables)
@ -81,7 +81,7 @@ const toggleFullScreen = () => {
config.isFullScreen = !config.isFullScreen
}
watch([metas, projectTables], populateTables, {
watch([metas, baseTables], populateTables, {
flush: 'post',
immediate: true,
})
@ -93,7 +93,7 @@ watch(config, populateTables, {
const filteredTables = computed(() =>
tables.value.filter((t) =>
props?.baseId ? t.base_id === props.baseId : t.base_id === bases.value?.filter((base: BaseType) => base.enabled)[0].id,
props?.sourceId ? t.source_id === props.sourceId : t.source_id === sources.value?.filter((source: SourceType) => source.enabled)[0].id,
),
)

4
packages/nc-gui/components/general/AddBaseButton.vue

@ -12,11 +12,11 @@ const toggleDialog = inject(ToggleDialogInj, () => {})
<div
v-if="isUIAllowed('settingsPage')"
class="flex items-center w-full pl-3 hover:(text-primary bg-primary bg-opacity-5)"
@click="toggleDialog(true, undefined, undefined, projectId)"
@click="toggleDialog(true, undefined, undefined, baseId)"
>
<div>
<div class="flex items-center space-x-1">
<component :is="iconMap.users" class="mr-1 nc-new-base" />
<component :is="iconMap.users" class="mr-1 nc-new-source" />
<div>{{ t('title.teamAndSettings') }}</div>
</div>
</div>

10
packages/nc-gui/components/general/HelpAndSupport.vue

@ -1,16 +1,16 @@
<script lang="ts" setup>
import { iconMap, ref, storeToRefs, useGlobal, useProject, useRoute } from '#imports'
import { iconMap, ref, storeToRefs, useGlobal, useBase, useRoute } from '#imports'
const showDrawer = ref(false)
const { appInfo } = useGlobal()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const route = useRoute()
const openSwaggerLink = () => {
openLink(`./api/v1/db/meta/projects/${route.params.projectId}/swagger`, appInfo.value.ncSiteUrl)
openLink(`./api/v1/meta/bases/${route.params.baseId}/swagger`, appInfo.value.ncSiteUrl)
}
</script>
@ -40,7 +40,7 @@ const openSwaggerLink = () => {
<LazyGeneralSocialCard class="!w-full nc-social-card">
<template #before>
<a-list-item v-if="project">
<a-list-item v-if="base">
<nuxt-link
v-e="['a:navbar:user:swagger']"
no-prefetch
@ -52,7 +52,7 @@ const openSwaggerLink = () => {
<div class="ml-3 flex items-center text-sm">
<LogosSwagger />
<!-- Swagger Documentation -->
<span class="ml-3">{{ project.title }} : {{ $t('title.swaggerDocumentation') }}</span>
<span class="ml-3">{{ base.title }} : {{ $t('title.swaggerDocumentation') }}</span>
</div>
</nuxt-link>
</a-list-item>

18
packages/nc-gui/components/general/MiniSidebar.vue

@ -1,11 +1,11 @@
<script lang="ts" setup>
import { computed, iconMap, navigateTo, storeToRefs, useGlobal, useProject, useRoute, useSidebar } from '#imports'
import { computed, iconMap, navigateTo, storeToRefs, useGlobal, useBase, useRoute, useSidebar } from '#imports'
const { signOut, signedIn, user, currentVersion } = useGlobal()
const { isOpen } = useSidebar('nc-mini-sidebar', { isOpen: true })
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const route = useRoute()
@ -80,9 +80,9 @@ const logout = async () => {
<a-menu-item class="active:(ring ring-accent ring-opacity-100)">
<div
v-e="['c:project:create:xcdb']"
v-e="['c:base:create:xcdb']"
class="group flex items-center gap-2 py-2 hover:text-primary"
@click="navigateTo('/project/create')"
@click="navigateTo('/base/create')"
>
<component :is="iconMap.plus" class="text-lg group-hover:text-accent" />
{{ $t('activity.createProject') }}
@ -91,9 +91,9 @@ const logout = async () => {
<a-menu-item class="rounded-b active:(ring ring-accent)">
<div
v-e="['c:project:create:extdb']"
v-e="['c:base:create:extdb']"
class="group flex items-center gap-2 py-2 hover:text-primary"
@click="navigateTo('/project/create-external')"
@click="navigateTo('/base/create-external')"
>
<component :is="iconMap.database" class="text-lg group-hover:text-accent" />
<div v-html="$t('activity.createProjectExtended.extDB')" />
@ -105,12 +105,12 @@ const logout = async () => {
</a-dropdown>
<a-tooltip placement="right">
<template v-if="project" #title>{{ project.title }}</template>
<template v-if="base" #title>{{ base.title }}</template>
<div
:class="[route.name.includes('nc-projectId') ? 'active' : 'pointer-events-none !text-gray-400']"
:class="[route.name.includes('nc-baseId') ? 'active' : 'pointer-events-none !text-gray-400']"
class="nc-mini-sidebar-item"
@click="navigateTo(`/${route.params.projectType}/${route.params.projectId}`)"
@click="navigateTo(`/${route.params.baseType}/${route.params.baseId}`)"
>
<component :is="iconMap.database" class="cursor-pointer transform hover:scale-105 text-2xl" />
</div>

10
packages/nc-gui/components/general/ProjectIcon.vue

@ -7,19 +7,19 @@ const { hoverable } = defineProps<{
<template>
<GeneralIcon
icon="project"
class="text-[#2824FB] nc-project-icon"
icon="ncDatabase"
class="text-[#2824FB] base"
:class="{
'nc-project-icon-hoverable': hoverable,
'nc-base-icon-hoverable': hoverable,
}"
/>
</template>
<style scoped>
.nc-project-icon {
.nc-base-icon {
@apply text-xl;
}
.nc-project-icon-hoverable {
.nc-base-icon-hoverable {
@apply cursor-pointer !hover:bg-gray-200 !hover:bg-opacity-50;
}
</style>

6
packages/nc-gui/components/general/ShareProject.vue

@ -13,7 +13,7 @@ const { isMobileMode } = useGlobal()
const { visibility, showShareModal } = storeToRefs(useShare())
const { activeTable } = storeToRefs(useTablesStore())
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { $e } = useNuxtApp()
@ -38,9 +38,9 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<template>
<div
v-if="isUIAllowed('projectShare') && visibility !== 'hidden' && (activeTable || project)"
v-if="isUIAllowed('baseShare') && visibility !== 'hidden' && (activeTable || base)"
class="flex flex-col justify-center h-full"
data-testid="share-project-button"
data-testid="share-base-button"
:data-sharetype="visibility"
>
<NcButton

1
packages/nc-gui/components/general/TableIcon.vue

@ -16,6 +16,7 @@ const { meta: tableMeta } = defineProps<{
:emoji="tableMeta.meta?.icon"
readonly
/>
<component :is="iconMap.eye" v-else-if="tableMeta?.type === 'view'" class="w-5 mx-0.75" />
<component :is="iconMap.table" v-else class="w-5 mx-0.5" />
</template>

27
packages/nc-gui/components/general/UserIcon.vue

@ -1,27 +1,12 @@
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
size?: 'small' | 'medium' | 'base' | 'large' | 'xlarge'
name?: string
email?: string
}>(),
{
email: '',
},
)
const props = defineProps<{
size?: 'small' | 'medium' | 'base' | 'large' | 'xlarge'
name?: string
}>()
const { user } = useGlobal()
const emailProp = toRef(props, 'email')
const backgroundColor = computed(() => {
// in comments we need to generate user icon from email
if (emailProp.value.length) {
return stringToColour(emailProp.value)
}
return user.value?.email ? stringToColour(user.value?.email) : '#FFFFFF'
})
const backgroundColor = computed(() => (user.value?.id ? stringToColour(user.value?.id) : '#FFFFFF'))
const size = computed(() => props.size || 'medium')
@ -46,7 +31,7 @@ const usernameInitials = computed(() => {
<template>
<div
class="flex nc-user-avatar font-bold"
class="flex nc-user-avatar"
:class="{
'min-w-4 min-h-4': size === 'small',
'min-w-6 min-h-6': size === 'medium',

4
packages/nc-gui/components/general/language/Menu.vue

@ -25,7 +25,7 @@ async function changeLanguage(lang: string) {
<a
href="https://docs.nocodb.com/engineering/translation/#how-to-contribute--for-community-members"
target="_blank"
class="caption nc-project-menu-item py-2 text-primary underline hover:opacity-75"
class="caption nc-base-menu-item py-2 text-primary underline hover:opacity-75"
>
{{ $t('activity.translate') }}
</a>
@ -39,7 +39,7 @@ async function changeLanguage(lang: string) {
:value="key"
@click="changeLanguage(key)"
>
<div :class="key === locale ? '!font-semibold !text-primary' : ''" class="nc-project-menu-item capitalize">
<div :class="key === locale ? '!font-semibold !text-primary' : ''" class="nc-base-menu-item capitalize">
{{ Language[key] || lang }}
</div>
</a-menu-item>

4
packages/nc-gui/components/notification/Item/ProjectEvent.vue

@ -24,14 +24,14 @@ const action = computed(() => {
const onClick = () => {
if (item.value.type === AppEvents.PROJECT_DELETE) return
navigateToProject({ projectId: item.value.body.id })
navigateToProject({ baseId: item.value.body.id })
}
</script>
<template>
<NotificationItemWrapper :item="item" @click="onClick">
<div class="text-xs gap-2">
Project
Base
<GeneralProjectIcon style="vertical-align: middle" :type="item.body.type" /> <strong>{{ item.body.title }}</strong>
{{ action }} successfully
</div>

4
packages/nc-gui/components/notification/Item/ProjectInvite.vue

@ -10,10 +10,10 @@ const item = toRef(props, 'item')
</script>
<template>
<NotificationItemWrapper :item="item" @click="navigateToProject({ projectId: item.body.id })">
<NotificationItemWrapper :item="item" @click="navigateToProject({ baseId: item.body.id })">
<div class="text-xs">
<strong>{{ item.body.invited_by }}</strong> has invited you to collaborate on
<!-- <GeneralProjectIcon style="vertical-align: middle" :type="item.body.type" /> <strong>{{ item.body.title }}</strong> project. -->
<!-- <GeneralProjectIcon style="vertical-align: middle" :type="item.body.type" /> <strong>{{ item.body.title }}</strong> base. -->
</div>
</NotificationItemWrapper>
</template>

2
packages/nc-gui/components/notification/Item/SharedViewEvent.vue

@ -23,7 +23,7 @@ const action = computed(() => {
const onClick = () => {
if (item.value.type === AppEvents.VIEW_DELETE) return
navigateToProject({ projectId: item.value.body.id })
navigateToProject({ baseId: item.value.body.id })
}
</script>

2
packages/nc-gui/components/notification/Item/TableEvent.vue

@ -23,7 +23,7 @@ const action = computed(() => {
const onClick = () => {
if (item.value.type === AppEvents.TABLE_DELETE) return
navigateToProject({ projectId: item.value.body.id })
navigateToProject({ baseId: item.value.body.id })
}
</script>

2
packages/nc-gui/components/notification/Item/ViewEvent.vue

@ -23,7 +23,7 @@ const action = computed(() => {
const onClick = () => {
if (item.value.type === AppEvents.VIEW_DELETE) return
navigateToProject({ projectId: item.value.body.id })
navigateToProject({ baseId: item.value.body.id })
}
</script>

4
packages/nc-gui/components/profile/overview/contributionActivity.vue

@ -19,7 +19,7 @@
</div>
</a-timeline-item>
<a-timeline-item>
<div>Answered 2 discussions in 1 project</div>
<div>Answered 2 discussions in 1 base</div>
<div class="text-primary">slair.xyz / project_8</div>
<div class="prose-xs">
<div class="flex items-center mr-4">
@ -49,7 +49,7 @@
</div>
</a-timeline-item>
<a-timeline-item>
<div>Answered 2 discussions in 1 project</div>
<div>Answered 2 discussions in 1 base</div>
<div class="text-primary">slair.xyz / project_8</div>
<div class="prose-xs">
<div class="flex items-center mr-4">

16
packages/nc-gui/components/project/AccessSettings.vue

@ -4,11 +4,11 @@ import { OrderedProjectRoles, ProjectRoles, WorkspaceRolesToProjectRoles } from
import InfiniteLoading from 'v3-infinite-loading'
import { isEeUI, storeToRefs, stringToColour, timeAgo } from '#imports'
const projectsStore = useProjects()
const { getProjectUsers, createProjectUser, updateProjectUser, removeProjectUser } = projectsStore
const { activeProjectId } = storeToRefs(projectsStore)
const basesStore = useBases()
const { getProjectUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore
const { activeProjectId } = storeToRefs(basesStore)
const { projectRoles } = useRoles()
const { baseRoles } = useRoles()
const collaborators = ref<
{
@ -33,7 +33,7 @@ const loadCollaborators = async () => {
currentPage.value += 1
const { users, totalRows } = await getProjectUsers({
projectId: activeProjectId.value!,
baseId: activeProjectId.value!,
page: currentPage.value,
...(!userSearchText.value ? {} : ({ searchText: userSearchText.value } as any)),
limit: 20,
@ -44,7 +44,7 @@ const loadCollaborators = async () => {
...collaborators.value,
...users.map((user: any) => ({
...user,
project_roles: user.roles,
base_roles: user.roles,
roles:
user.roles ??
(user.workspace_roles
@ -98,7 +98,7 @@ const updateCollaborator = async (collab: any, roles: ProjectRoles) => {
} else {
collab.roles = ProjectRoles.NO_ACCESS
}
} else if (collab.project_roles) {
} else if (collab.base_roles) {
collab.roles = roles
await updateProjectUser(activeProjectId.value!, collab)
} else {
@ -140,7 +140,7 @@ onMounted(async () => {
try {
await loadCollaborators()
const currentRoleIndex = OrderedProjectRoles.findIndex(
(role) => projectRoles.value && Object.keys(projectRoles.value).includes(role),
(role) => baseRoles.value && Object.keys(baseRoles.value).includes(role),
)
if (currentRoleIndex !== -1) {
accessibleRoles.value = OrderedProjectRoles.slice(currentRoleIndex + 1)

50
packages/nc-gui/components/project/AllTables.vue

@ -1,11 +1,11 @@
<script lang="ts" setup>
import type { BaseType, TableType } from 'nocodb-sdk'
import type { SourceType, TableType } from 'nocodb-sdk'
import dayjs from 'dayjs'
import NcTooltip from '~/components/nc/Tooltip.vue'
const { activeTables } = storeToRefs(useTablesStore())
const { openTable } = useTablesStore()
const { openedProject } = storeToRefs(useProjects())
const { openedProject } = storeToRefs(useBases())
const isNewBaseModalOpen = ref(false)
@ -18,16 +18,16 @@ const { $e } = useNuxtApp()
const isImportModalOpen = ref(false)
const defaultBase = computed(() => {
return openedProject.value?.bases?.[0]
return openedProject.value?.sources?.[0]
})
const bases = computed(() => {
// Convert array of bases to map of bases
const sources = computed(() => {
// Convert array of sources to map of sources
const baseMap = new Map<string, BaseType>()
const baseMap = new Map<string, SourceType>()
openedProject.value?.bases?.forEach((base) => {
baseMap.set(base.id!, base)
openedProject.value?.sources?.forEach((source) => {
baseMap.set(source.id!, source)
})
return baseMap
@ -37,17 +37,17 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
$e('c:table:create:navdraw')
const isOpen = ref(true)
let baseId = openedProject.value!.bases?.[0].id
let sourceId = openedProject.value!.sources?.[0].id
if (typeof baseIndex === 'number') {
baseId = openedProject.value!.bases?.[baseIndex].id
sourceId = openedProject.value!.sources?.[baseIndex].id
}
if (!baseId || !openedProject.value?.id) return
if (!sourceId || !openedProject.value?.id) return
const { close } = useDialog(resolveComponent('DlgTableCreate'), {
'modelValue': isOpen,
baseId, // || bases.value[0].id,
'projectId': openedProject.value.id,
sourceId, // || sources.value[0].id,
'baseId': openedProject.value.id,
'onCreate': closeDialog,
'onUpdate:modelValue': () => closeDialog(),
})
@ -82,7 +82,7 @@ const onCreateBaseClick = () => {
<div
v-if="isUIAllowed('tableCreate')"
role="button"
class="nc-project-view-all-table-btn"
class="nc-base-view-all-table-btn"
data-testid="proj-view-btn__add-new-table"
@click="openTableCreateDialog()"
>
@ -93,14 +93,14 @@ const onCreateBaseClick = () => {
v-if="isUIAllowed('tableCreate')"
v-e="['c:table:import']"
role="button"
class="nc-project-view-all-table-btn"
class="nc-base-view-all-table-btn"
data-testid="proj-view-btn__import-data"
@click="isImportModalOpen = true"
>
<GeneralIcon icon="download" />
<div class="label">{{ $t('activity.import') }} {{ $t('general.data') }}</div>
</div>
<component :is="isDataSourceLimitReached ? NcTooltip : 'div'" v-if="isUIAllowed('baseCreate')">
<component :is="isDataSourceLimitReached ? NcTooltip : 'div'" v-if="isUIAllowed('sourceCreate')">
<template #title>
<div>
{{ $t('tooltip.reachedSourceLimit') }}
@ -109,7 +109,7 @@ const onCreateBaseClick = () => {
<div
v-e="['c:table:create-source']"
role="button"
class="nc-project-view-all-table-btn"
class="nc-base-view-all-table-btn"
data-testid="proj-view-btn__create-source"
:class="{
disabled: isDataSourceLimitReached,
@ -127,14 +127,14 @@ const onCreateBaseClick = () => {
<div class="w-1/5">{{ $t('labels.createdOn') }}</div>
</div>
<div
class="nc-project-view-all-table-list nc-scrollbar-md"
class="nc-base-view-all-table-list nc-scrollbar-md"
:style="{
height: 'calc(100vh - var(--topbar-height) - 18rem)',
}"
>
<div
v-for="table in [...activeTables].sort(
(a, b) => a.base_id!.localeCompare(b.base_id!) * 20
(a, b) => a.source_id!.localeCompare(b.source_id!) * 20
)"
:key="table.id"
class="py-4 flex flex-row w-full cursor-pointer hover:bg-gray-100 border-b-1 border-gray-100 px-2.25"
@ -146,10 +146,10 @@ const onCreateBaseClick = () => {
{{ table?.title }}
</div>
<div class="w-1/5 text-gray-600" data-testid="proj-view-list__item-type">
<div v-if="table.base_id === defaultBase?.id" class="ml-0.75">-</div>
<div v-if="table.source_id === defaultBase?.id" class="ml-0.75">-</div>
<div v-else>
<GeneralBaseLogo :base-type="bases.get(table.base_id!)?.type" class="w-4 mr-1" />
{{ bases.get(table.base_id!)?.alias }}
<GeneralBaseLogo :source-type="sources.get(table.source_id!)?.type" class="w-4 mr-1" />
{{ sources.get(table.source_id!)?.alias }}
</div>
</div>
<div class="w-1/5 text-gray-400 ml-0.25" data-testid="proj-view-list__item-created-at">
@ -157,7 +157,7 @@ const onCreateBaseClick = () => {
</div>
</div>
</div>
<ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :base="defaultBase" />
<ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" />
<GeneralModal v-model:visible="isNewBaseModalOpen" size="medium">
<div class="py-6 px-8">
<LazyDashboardSettingsDataSourcesCreateBase @close="isNewBaseModalOpen = false" />
@ -167,7 +167,7 @@ const onCreateBaseClick = () => {
</template>
<style lang="scss" scoped>
.nc-project-view-all-table-btn {
.nc-base-view-all-table-btn {
@apply flex flex-col gap-y-6 p-4 bg-gray-100 rounded-xl w-56 cursor-pointer text-gray-600 hover:(bg-gray-200 text-black);
.nc-icon {
@ -179,7 +179,7 @@ const onCreateBaseClick = () => {
}
}
.nc-project-view-all-table-btn.disabled {
.nc-base-view-all-table-btn.disabled {
@apply bg-gray-50 text-gray-400 hover:(bg-gray-50 text-gray-400) cursor-not-allowed;
}
</style>

28
packages/nc-gui/components/project/ImportModal.vue

@ -1,21 +1,21 @@
<script lang="ts" setup>
import type { BaseType } from 'nocodb-sdk'
import type { SourceType } from 'nocodb-sdk'
const props = defineProps<{
visible: boolean
base: BaseType
source: SourceType
}>()
const emits = defineEmits(['update:visible'])
const base = toRef(props, 'base')
const source = toRef(props, 'source')
const visible = useVModel(props, 'visible', emits)
const { $e } = useNuxtApp()
function openAirtableImportDialog(baseId?: string) {
if (!baseId) return
function openAirtableImportDialog(sourceId?: string) {
if (!sourceId) return
$e('a:actions:import-airtable')
@ -23,7 +23,7 @@ function openAirtableImportDialog(baseId?: string) {
const { close } = useDialog(resolveComponent('DlgAirtableImport'), {
'modelValue': isOpen,
'baseId': baseId,
'sourceId': sourceId,
'onUpdate:modelValue': closeDialog,
})
@ -35,7 +35,7 @@ function openAirtableImportDialog(baseId?: string) {
}
function openQuickImportDialog(type: 'csv' | 'excel' | 'json') {
if (!base.value.id) return
if (!source.value.id) return
$e(`a:actions:import-${type}`)
@ -44,7 +44,7 @@ function openQuickImportDialog(type: 'csv' | 'excel' | 'json') {
const { close } = useDialog(resolveComponent('DlgQuickImport'), {
'modelValue': isOpen,
'importType': type,
'baseId': base.value.id,
'sourceId': source.value.id,
'onUpdate:modelValue': closeDialog,
})
@ -59,7 +59,7 @@ const onClick = (type: 'airtable' | 'csv' | 'excel' | 'json') => {
visible.value = false
if (type === 'airtable') {
openAirtableImportDialog(base.value.id)
openAirtableImportDialog(source.value.id)
} else {
openQuickImportDialog(type)
}
@ -71,21 +71,21 @@ const onClick = (type: 'airtable' | 'csv' | 'excel' | 'json') => {
<div class="flex flex-col px-8 pt-6 pb-9">
<div class="text-lg font-medium mb-6">{{ $t('general.import') }}</div>
<div class="row mb-10">
<div class="nc-project-view-import-sub-btn" @click="onClick('airtable')">
<div class="nc-base-view-import-sub-btn" @click="onClick('airtable')">
<GeneralIcon icon="airtable" />
<div class="label">{{ $t('labels.airtable') }}</div>
</div>
<div class="nc-project-view-import-sub-btn" @click="onClick('csv')">
<div class="nc-base-view-import-sub-btn" @click="onClick('csv')">
<GeneralIcon icon="csv" />
<div class="label">{{ $t('labels.csv') }}</div>
</div>
</div>
<div class="row">
<div class="nc-project-view-import-sub-btn" @click="onClick('excel')">
<div class="nc-base-view-import-sub-btn" @click="onClick('excel')">
<GeneralIcon icon="excelColored" />
<div class="label">{{ $t('labels.excel') }}</div>
</div>
<div class="nc-project-view-import-sub-btn" @click="onClick('json')">
<div class="nc-base-view-import-sub-btn" @click="onClick('json')">
<GeneralIcon icon="code" />
<div class="label">{{ $t('labels.json') }}</div>
</div>
@ -98,7 +98,7 @@ const onClick = (type: 'airtable' | 'csv' | 'excel' | 'json') => {
.row {
@apply flex flex-row gap-x-10;
}
.nc-project-view-import-sub-btn {
.nc-base-view-import-sub-btn {
@apply flex flex-col gap-y-6 p-16 bg-gray-50 items-center justify-center rounded-xl w-56 cursor-pointer text-gray-600 hover:(bg-gray-100 !text-black);
.nc-icon {

20
packages/nc-gui/components/project/View.vue

@ -1,11 +1,11 @@
<script lang="ts" setup>
import { useTitle } from '@vueuse/core'
import NcLayout from '~icons/nc-icons/layout'
const { openedProject } = storeToRefs(useProjects())
const { openedProject } = storeToRefs(useBases())
const { activeTables } = storeToRefs(useTablesStore())
const { activeWorkspace } = storeToRefs(useWorkspace())
const { navigateToProjectPage } = useProject()
const { navigateToProjectPage } = useBase()
const router = useRouter()
const route = router.currentRoute
@ -13,12 +13,12 @@ const route = router.currentRoute
const { $e } = useNuxtApp()
/* const defaultBase = computed(() => {
return openedProject.value?.bases?.[0]
return openedProject.value?.sources?.[0]
}) */
const { isUIAllowed } = useRoles()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { projectPageTab } = storeToRefs(useConfigStore())
@ -65,7 +65,7 @@ watch(
</script>
<template>
<div class="h-full nc-project-view">
<div class="h-full nc-base-view">
<div
class="flex flex-row pl-2 pr-2 border-b-1 border-gray-200 justify-between w-full"
:class="{ 'nc-table-toolbar-mobile': isMobileMode, 'h-[var(--topbar-height)]': !isMobileMode }"
@ -80,7 +80,7 @@ watch(
<LazyGeneralShareProject />
</div>
<div
class="flex mx-12 my-8 nc-project-view-tab"
class="flex mx-12 my-8 nc-base-view-tab"
:style="{
height: 'calc(100% - var(--topbar-height))',
}"
@ -104,8 +104,8 @@ watch(
</template>
<ProjectAllTables />
</a-tab-pane>
<!-- <a-tab-pane v-if="defaultBase" key="erd" tab="Project ERD" force-render class="pt-4 pb-12">
<ErdView :base-id="defaultBase!.id" class="!h-full" />
<!-- <a-tab-pane v-if="defaultBase" key="erd" tab="Base ERD" force-render class="pt-4 pb-12">
<ErdView :source-id="defaultBase!.id" class="!h-full" />
</a-tab-pane> -->
<a-tab-pane v-if="isUIAllowed('newUser')" key="collaborator">
<template #tab>
@ -122,14 +122,14 @@ watch(
<GeneralIcon icon="database" />
<div>{{ $t('labels.dataSources') }}</div>
<div
v-if="project.bases?.length"
v-if="base.sources?.length"
class="tab-info"
:class="{
'bg-primary-selected': projectPageTab === 'data-source',
'bg-gray-50': projectPageTab !== 'data-source',
}"
>
{{ project.bases.length - 1 }}
{{ base.sources.length - 1 }}
</div>
</div>
</template>

4
packages/nc-gui/components/shared-view/Grid.vue

@ -12,7 +12,7 @@ import {
provide,
ref,
useGlobal,
useProject,
useBase,
useProvideSmartsheetStore,
useSharedView,
} from '#imports'
@ -21,7 +21,7 @@ const { sharedView, meta, sorts, nestedFilters } = useSharedView()
const { signedIn } = useGlobal()
const { loadProject } = useProject()
const { loadProject } = useBase()
const { isLocked } = useProvideSmartsheetStore(sharedView, meta, true, sorts, nestedFilters)

10
packages/nc-gui/components/smartsheet/ApiSnippet.vue

@ -10,7 +10,7 @@ import {
useCopy,
useGlobal,
useI18n,
useProject,
useBase,
useSmartsheetStoreOrThrow,
useVModel,
useViewData,
@ -25,8 +25,8 @@ const emits = defineEmits(['update:modelValue'])
const { t } = useI18n()
const projectStore = useProject()
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
const { appInfo, token } = useGlobal()
@ -84,7 +84,7 @@ const selectedLangName = ref(langs[0].name)
const apiUrl = computed(
() =>
new URL(
`/api/v1/db/data/noco/${project.value.id}/${meta.value?.title}/views/${view.value?.title}`,
`/api/v1/data/noco/${base.value.id}/${meta.value?.title}/views/${view.value?.title}`,
(appInfo.value && appInfo.value.ncSiteUrl) || '/',
).href,
)
@ -118,7 +118,7 @@ const api = new Api({
api.dbViewRow.list(
"noco",
${JSON.stringify(project.value.title)},
${JSON.stringify(base.value.title)},
${JSON.stringify(meta.value?.title)},
${JSON.stringify(view.value?.title)}, ${JSON.stringify(queryParams.value, null, 4)}).then(function (data) {
console.log(data);

6
packages/nc-gui/components/smartsheet/Cell.vue

@ -47,7 +47,7 @@ import {
storeToRefs,
toRef,
useDebounceFn,
useProject,
useBase,
useSmartsheetRowStoreOrThrow,
} from '#imports'
@ -97,9 +97,9 @@ const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))
const { currentRow } = useSmartsheetRowStoreOrThrow()
const { sqlUis } = storeToRefs(useProject())
const { sqlUis } = storeToRefs(useBase())
const sqlUi = ref(column.value?.base_id ? sqlUis.value[column.value?.base_id] : Object.values(sqlUis.value)[0])
const sqlUi = ref(column.value?.source_id ? sqlUis.value[column.value?.source_id] : Object.values(sqlUis.value)[0])
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))

4
packages/nc-gui/components/smartsheet/Gallery.vue

@ -212,14 +212,14 @@ watch(
<template #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)">
<div v-e="['a:row:delete']" class="nc-project-menu-item">
<div v-e="['a:row:delete']" class="nc-base-menu-item">
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</div>
</a-menu-item>
<!-- <a-menu-item v-if="contextMenuTarget" @click="openNewRecordFormHook.trigger()"> -->
<!-- <div v-e="['a:row:insert']" class="nc-project-menu-item"> -->
<!-- <div v-e="['a:row:insert']" class="nc-base-menu-item"> -->
<!-- &lt;!&ndash; Insert New Row &ndash;&gt; -->
<!-- {{ $t('activity.insertRow') }} -->
<!-- </div> -->

4
packages/nc-gui/components/smartsheet/Kanban.vue

@ -679,7 +679,7 @@ watch(
<template v-if="!isLocked && !isPublic && hasEditPermission" #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="expandForm(contextMenuTarget)">
<div v-e="['a:kanban:expand-record']" class="nc-project-menu-item nc-kanban-context-menu-item">
<div v-e="['a:kanban:expand-record']" class="nc-base-menu-item nc-kanban-context-menu-item">
<component :is="iconMap.expand" class="flex" />
<!-- Expand Record -->
{{ $t('activity.expandRecord') }}
@ -687,7 +687,7 @@ watch(
</a-menu-item>
<a-divider class="!m-0 !p-0" />
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget)">
<div v-e="['a:kanban:delete-record']" class="nc-project-menu-item nc-kanban-context-menu-item">
<div v-e="['a:kanban:delete-record']" class="nc-base-menu-item nc-kanban-context-menu-item">
<component :is="iconMap.delete" class="flex" />
<!-- Delete Record -->
{{ $t('activity.deleteRecord') }}

6
packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { UITypes } from 'nocodb-sdk'
import { MetaInj, computed, useColumnCreateStoreOrThrow, useProject, useVModel } from '#imports'
import { MetaInj, computed, useColumnCreateStoreOrThrow, useBase, useVModel } from '#imports'
const props = defineProps<{
value: any
@ -16,7 +16,7 @@ const { onAlter, onDataTypeChange, validateInfos, sqlUi } = useColumnCreateStore
// todo: 2nd argument of `getDataTypeListForUiType` is missing!
const dataTypes = computed(() => sqlUi.value.getDataTypeListForUiType(vModel.value as { uidt: UITypes }, '' as any))
const { isPg } = useProject()
const { isPg } = useBase()
const meta = inject(MetaInj, ref())
@ -93,7 +93,7 @@ vModel.value.au = !!vModel.value.au */
<a-input v-model:value="vModel.dtxs" class="!rounded-md" :disabled="!sqlUi.columnEditable(vModel)" @input="onAlter" />
</a-form-item>
<LazySmartsheetColumnPgBinaryOptions v-if="isPg(meta?.base_id) && vModel.dt === 'bytea'" v-model:value="vModel" />
<LazySmartsheetColumnPgBinaryOptions v-if="isPg(meta?.source_id) && vModel.dt === 'bytea'" v-model:value="vModel" />
</template>
</div>
</template>

8
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -19,7 +19,7 @@ import {
useI18n,
useMetas,
useNuxtApp,
useProject,
useBase,
watchEffect,
} from '#imports'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
@ -66,7 +66,7 @@ const isForm = inject(IsFormInj, ref(false))
const isKanban = inject(IsKanbanInj, ref(false))
const { isMysql, isMssql } = useProject()
const { isMysql, isMssql } = useBase()
const reloadDataTrigger = inject(ReloadViewDataHookInj)
@ -327,8 +327,8 @@ if (props.fromTableExplorer) {
v-if="
!isVirtualCol(formState) &&
!isAttachment(formState) &&
!isMssql(meta!.base_id) &&
!(isMysql(meta!.base_id) && (isJSON(formState) || isTextArea(formState)))
!isMssql(meta!.source_id) &&
!(isMysql(meta!.source_id) && (isJSON(formState) || isTextArea(formState)))
"
v-model:value="formState"
/>

10
packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { ModelTypes, MssqlUi, SqliteUi, UITypes } from 'nocodb-sdk'
import { MetaInj, inject, ref, storeToRefs, useProject, useVModel } from '#imports'
import { MetaInj, inject, ref, storeToRefs, useBase, useVModel } from '#imports'
import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
@ -16,8 +16,8 @@ const meta = inject(MetaInj, ref())
const { setAdditionalValidations, validateInfos, onDataTypeChange, sqlUi, isXcdbBase } = useColumnCreateStoreOrThrow()
const projectStore = useProject()
const { tables } = storeToRefs(projectStore)
const baseStore = useBase()
const { tables } = storeToRefs(baseStore)
const { t } = useI18n()
@ -47,7 +47,7 @@ const refTables = computed(() => {
return []
}
return tables.value.filter((t) => t.type === ModelTypes.TABLE && t.base_id === meta.value?.base_id)
return tables.value.filter((t) => t.type === ModelTypes.TABLE && t.source_id === meta.value?.source_id)
})
const filterOption = (value: string, option: { key: string }) => option.key.toLowerCase().includes(value.toLowerCase())
@ -80,7 +80,7 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
<a-select-option v-for="table of refTables" :key="table.title" :value="table.id">
<div class="flex items-center gap-2">
<div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="table" class="text-gray-500" />
<GeneralTableIcon :meta="table" class="text-gray-500"></GeneralTableIcon>
</div>
<span class="overflow-ellipsis min-w-0 shrink-1">{{ table.title }}</span>

8
packages/nc-gui/components/smartsheet/column/LookupOptions.vue

@ -2,7 +2,7 @@
import { onMounted } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { MetaInj, inject, ref, storeToRefs, useColumnCreateStoreOrThrow, useMetas, useProject, useVModel } from '#imports'
import { MetaInj, inject, ref, storeToRefs, useColumnCreateStoreOrThrow, useMetas, useBase, useVModel } from '#imports'
const props = defineProps<{
value: any
@ -18,8 +18,8 @@ const { t } = useI18n()
const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow()
const projectStore = useProject()
const { tables } = storeToRefs(projectStore)
const baseStore = useBase()
const { tables } = storeToRefs(baseStore)
const { metas } = useMetas()
@ -37,7 +37,7 @@ const refTables = computed(() => {
}
const _refTables = meta.value.columns
.filter((column) => isLinksOrLTAR(column) && !column.system && column.base_id === meta.value?.base_id)
.filter((column) => isLinksOrLTAR(column) && !column.system && column.source_id === meta.value?.source_id)
.map((column) => ({
col: column.colOptions,
column,

8
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -2,7 +2,7 @@
import { onMounted } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, TableType, UITypes } from 'nocodb-sdk'
import { isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { MetaInj, inject, ref, storeToRefs, useColumnCreateStoreOrThrow, useMetas, useProject, useVModel } from '#imports'
import { MetaInj, inject, ref, storeToRefs, useColumnCreateStoreOrThrow, useMetas, useBase, useVModel } from '#imports'
const props = defineProps<{
value: any
@ -16,8 +16,8 @@ const meta = inject(MetaInj, ref())
const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow()
const projectStore = useProject()
const { tables } = storeToRefs(projectStore)
const baseStore = useBase()
const { tables } = storeToRefs(baseStore)
const { metas } = useMetas()
@ -55,7 +55,7 @@ const refTables = computed(() => {
isLinksOrLTAR(c) &&
(c.colOptions as LinkToAnotherRecordType).type !== 'bt' &&
!c.system &&
c.base_id === meta.value?.base_id,
c.source_id === meta.value?.source_id,
)
.map((c) => ({
col: c.colOptions,

4
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -31,7 +31,7 @@ const vModel = useVModel(props, 'value', emit)
const { formState, setAdditionalValidations, validateInfos, isMysql } = useColumnCreateStoreOrThrow()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { loadMagic, optionsMagic: _optionsMagic } = useNocoEe()
@ -140,7 +140,7 @@ const addNewOption = () => {
}
const optionsMagic = async () => {
await _optionsMagic(project, formState, getNextColor, options.value, renderedOptions.value)
await _optionsMagic(base, formState, getNextColor, options.value, renderedOptions.value)
}
const syncOptions = () => {

10
packages/nc-gui/components/smartsheet/details/Api.vue

@ -10,7 +10,7 @@ import {
useCopy,
useGlobal,
useI18n,
useProject,
useBase,
useSmartsheetStoreOrThrow,
useViewData,
watch,
@ -18,8 +18,8 @@ import {
const { t } = useI18n()
const projectStore = useProject()
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
const { appInfo, token } = useGlobal()
@ -75,7 +75,7 @@ const selectedLangName = ref(langs[0].name)
const apiUrl = computed(
() =>
new URL(
`/api/v1/db/data/noco/${project.value.id}/${meta.value?.title}/views/${view.value?.title}`,
`/api/v1/data/noco/${base.value.id}/${meta.value?.title}/views/${view.value?.title}`,
(appInfo.value && appInfo.value.ncSiteUrl) || '/',
).href,
)
@ -109,7 +109,7 @@ const api = new Api({
api.dbViewRow.list(
"noco",
${JSON.stringify(project.value.title)},
${JSON.stringify(base.value.title)},
${JSON.stringify(meta.value?.title)},
${JSON.stringify(view.value?.title)}, ${JSON.stringify(queryParams.value, null, 4)}).then(function (data) {
console.log(data);

2
packages/nc-gui/components/smartsheet/details/Erd.vue

@ -4,6 +4,6 @@ const { activeTable } = storeToRefs(useTablesStore())
<template>
<div class="flex flex-col p-4" style="height: calc(100vh - (var(--topbar-height) * 2))">
<LazyErdView :table="activeTable" :base-id="activeTable?.base_id" :show-all-columns="false" />
<LazyErdView :table="activeTable" :source-id="activeTable?.source_id" :show-all-columns="false" />
</div>
</template>

23
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -8,9 +8,7 @@ const { loadCommentsAndLogs, commentsAndLogs, saveComment, comment, updateCommen
const commentsWrapperEl = ref<HTMLDivElement>()
onMounted(async () => {
await loadCommentsAndLogs()
})
await loadCommentsAndLogs()
const { user } = useGlobal()
@ -93,7 +91,7 @@ const value = computed({
})
watch(
[commentsAndLogs, tab],
commentsAndLogs,
() => {
setTimeout(() => {
if (commentsWrapperEl.value) commentsWrapperEl.value.scrollTop = commentsWrapperEl.value?.scrollHeight
@ -150,24 +148,23 @@ const processedAudit = (log: string) => {
'pb-2': tab !== 'comments',
}"
>
<div v-if="tab === 'comments'" class="flex flex-col h-full">
<div v-if="tab === 'comments'" ref="commentsWrapperEl" class="flex flex-col h-full">
<div v-if="comments.length === 0" class="flex flex-col my-1 text-center justify-center h-full">
<div class="text-center text-3xl text-gray-700">
<GeneralIcon icon="commentHere" />
</div>
<div class="font-medium text-center my-6 text-gray-500">{{ $t('activity.startCommenting') }}</div>
</div>
<div v-else ref="commentsWrapperEl" class="flex flex-col h-full py-2 pl-2 pr-1 space-y-2 nc-scrollbar-md">
<div v-else class="flex flex-col h-full py-2 pl-2 pr-1 space-y-2 nc-scrollbar-md">
<div v-for="log of comments" :key="log.id">
<div class="bg-white rounded-xl group border-1 gap-2 border-gray-200">
<div class="flex flex-col p-4 gap-3">
<div class="flex justify-between">
<div class="flex items-center gap-2">
<GeneralUserIcon size="base" :name="log.display_name ?? log.user" :email="log.user" />
<GeneralUserIcon size="base" :name="log.display_name ?? log.user" />
<div class="flex flex-col">
<span class="truncate font-bold max-w-42">
{{ log.display_name ?? log.user.split('@')[0] ?? 'Shared base' }}
{{ log.display_name ?? log.user.split('@')[0].slice(0, 2) ?? 'Shared source' }}
</span>
<div v-if="log.id !== editLog?.id" class="text-xs text-gray-500">
{{ log.created_at !== log.updated_at ? `Edited ${timeAgo(log.updated_at)}` : timeAgo(log.created_at) }}
@ -240,12 +237,12 @@ const processedAudit = (log: string) => {
<div class="bg-white rounded-xl border-1 gap-3 border-gray-200">
<div class="flex flex-col p-4 gap-3">
<div class="flex justify-between">
<div class="flex items-center gap-2">
<GeneralUserIcon size="base" :name="log.display_name ?? log.user" :email="log.user" />
<div class="flex font-bold items-center gap-2">
<GeneralUserIcon size="base" :name="log.display_name ?? log.user" />
<div class="flex flex-col">
<span class="truncate max-w-50 font-bold">
{{ log.display_name ?? log.user.split('@')[0].slice(0, 2) ?? 'Shared base' }}
<span class="truncate max-w-50">
{{ log.display_name ?? log.user.split('@')[0].slice(0, 2) ?? 'Shared source' }}
</span>
<div v-if="log.id !== editLog?.id" class="text-xs text-gray-500">
{{ timeAgo(log.created_at) }}

115
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -3,6 +3,7 @@ import type { TableType, ViewType } from 'nocodb-sdk'
import { isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import MdiChevronDown from '~icons/mdi/chevron-down'
import TableIcon from '~icons/nc-icons/table'
import {
CellClickHookInj,
@ -14,7 +15,6 @@ import {
ReloadRowDataHookInj,
computedInject,
createEventHook,
iconMap,
inject,
message,
provide,
@ -66,9 +66,6 @@ const router = useRouter()
const isPublic = inject(IsPublicInj, ref(false))
// to check if a expanded form which is not yet saved exist or not
const isUnsavedFormExist = ref(false)
const { isUIAllowed } = useRoles()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
@ -154,7 +151,6 @@ const onClose = () => {
const onDuplicateRow = () => {
duplicatingRowInProgress.value = true
isUnsavedFormExist.value = true
const oldRow = { ...row.value.row }
delete oldRow.ncRecordId
const newRow = Object.assign(
@ -172,38 +168,25 @@ const onDuplicateRow = () => {
}, 500)
}
const save = async () => {
if (isNew.value) {
const data = await _save(rowState.value)
await syncLTARRefs(data)
reloadTrigger?.trigger()
} else {
await _save()
reloadTrigger?.trigger()
}
isUnsavedFormExist.value = false
}
const isPreventChangeModalOpen = ref(false)
const discardPreventModal = () => {
emits('next')
isPreventChangeModalOpen.value = false
}
const onNext = async () => {
if (changedColumns.value.size > 0) {
isPreventChangeModalOpen.value = true
await Modal.confirm({
title: 'Do you want to save the changes?',
okText: 'Save',
cancelText: 'Discard',
onOk: async () => {
await save()
emits('next')
},
onCancel: () => {
emits('next')
},
})
} else {
emits('next')
}
}
const saveChanges = async () => {
isUnsavedFormExist.value = false
await save()
emits('next')
}
const reloadParentRowHook = inject(ReloadRowDataHookInj, createEventHook())
// override reload trigger and use it to reload grid and the form itself
@ -225,10 +208,6 @@ if (isKanban.value) {
}
}
watch(isUnsavedFormExist, () => {
console.log(isUnsavedFormExist.value, 'HEHEH')
})
provide(IsExpandedFormOpenInj, isExpanded)
const cellWrapperEl = ref()
@ -251,6 +230,18 @@ const addNewRow = () => {
isExpanded.value = true
}, 500)
}
const save = async () => {
if (isNew.value) {
const data = await _save(rowState.value)
await syncLTARRefs(data)
reloadTrigger?.trigger()
} else {
await _save()
reloadTrigger?.trigger()
}
}
// attach keyboard listeners to switch between rows
// using alt + left/right arrow keys
useActiveKeyupListener(
@ -334,9 +325,14 @@ const onConfirmDeleteRowClick = async () => {
showDeleteRowModal.value = false
await deleteRowById(primaryKey.value)
message.success('Row deleted')
// if (!props.lastRow) {
// await onNext()
// } else if (!props.firstRow) {
// emits('prev')
// } else {
// }
reloadTrigger.trigger()
onClose()
showDeleteRowModal.value = false
}
watch(
@ -382,7 +378,7 @@ export default {
<div class="h-[85vh] xs:(max-h-full) max-h-215 flex flex-col p-6">
<div class="flex h-8 flex-shrink-0 w-full items-center nc-expanded-form-header relative mb-4 justify-between">
<template v-if="!isMobileMode">
<div class="flex gap-3 w-100">
<div class="flex gap-3">
<div class="flex gap-2">
<NcButton
v-if="props.showNextPrevIcons"
@ -403,9 +399,13 @@ export default {
<MdiChevronDown class="text-md" />
</NcButton>
</div>
<div v-if="displayValue" class="flex items-center truncate font-bold text-gray-800 text-xl">
<div v-if="displayValue" class="flex items-center truncate max-w-32 font-bold text-gray-800 text-xl">
{{ displayValue }}
</div>
<div class="bg-gray-100 px-2 gap-1 flex my-1 items-center rounded-lg text-gray-800 font-medium">
<TableIcon class="w-6 h-6 text-sm" />
All {{ meta.title }}
</div>
</div>
<div class="flex gap-2">
<NcDropdown v-if="!isNew">
@ -438,7 +438,7 @@ export default {
<NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew"
v-e="['c:row-expand:delete']"
class="!text-red-500 !hover:bg-red-50"
class="!text-red-500"
@click="!isNew && onDeleteRowClick()"
>
<component :is="iconMap.delete" data-testid="nc-expanded-form-delete" class="cursor-pointer nc-delete-row" />
@ -595,7 +595,7 @@ export default {
<NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew"
v-e="['c:row-expand:delete']"
class="!text-red-500 !hover:bg-red-50"
class="!text-red-500"
@click="!isNew && onDeleteRowClick()"
>
<div data-testid="nc-expanded-form-delete">
@ -624,7 +624,6 @@ export default {
type="primary"
size="medium"
class="nc-expand-form-save-btn !xs:(text-base)"
:disabled="changedColumns.size === 0 && !isUnsavedFormExist"
@click="save"
>
<div class="xs:px-1">Save</div>
@ -643,32 +642,16 @@ export default {
</div>
</NcModal>
<GeneralDeleteModal v-model:visible="showDeleteRowModal" entity-name="Record" :on-delete="() => onConfirmDeleteRowClick()">
<template #entity-preview>
<span>
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<component :is="iconMap.table" class="nc-view-icon" />
<div class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75 break-keep whitespace-nowrap">
{{ meta.title }}
</div>
</div>
</span>
</template>
</GeneralDeleteModal>
<!-- Prevent unsaved change modal -->
<NcModal v-model:visible="isPreventChangeModalOpen" size="small">
<template #header>
<div class="flex flex-row items-center gap-x-2">Do you want to save the changes ?</div>
</template>
<div class="mt-2">
<div class="flex flex-row justify-end gap-x-2 mt-6">
<NcButton type="secondary" @click="discardPreventModal">{{ $t('general.quit') }}</NcButton>
<NcButton key="submit" type="primary" label="Rename Table" loading-label="Renaming Table" @click="saveChanges">
{{ $t('activity.saveAndQuit') }}
</NcButton>
</div>
<NcModal v-model:visible="showDeleteRowModal" class="!w-[25rem] !xs-">
<div class="">
<div class="prose-xl font-bold self-center">Delete row ?</div>
<div class="mt-4">Are you sure you want to delete this row?</div>
</div>
<div class="flex flex-row gap-x-2 mt-4 pt-1.5 justify-end pt-4 gap-x-3">
<NcButton v-if="isMobileMode" type="secondary" @click="showDeleteRowModal = false">{{ $t('general.cancel') }} </NcButton>
<NcButton v-e="['a:row-expand:delete']" @click="onConfirmDeleteRowClick">{{ $t('general.confirm') }} </NcButton>
</div>
</NcModal>
</template>

14
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -1530,7 +1530,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<NcMenuItem
v-if="!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected)"
v-e="['a:row:delete-bulk']"
class="nc-project-menu-item !text-red-600 !hover:bg-red-50"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
data-testid="nc-delete-row"
@click="deleteSelectedRows"
>
@ -1542,7 +1542,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<!-- <NcMenuItem -->
<!-- v-if="contextMenuTarget && selectedRange.isSingleCell()" -->
<!-- v-e="['a:row:insert']" -->
<!-- class="nc-project-menu-item" -->
<!-- class="nc-base-menu-item" -->
<!-- @click="addEmptyRow(contextMenuTarget.row + 1)" -->
<!-- > -->
<!-- <GeneralIcon icon="plus" /> -->
@ -1553,7 +1553,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<NcMenuItem
v-if="contextMenuTarget"
v-e="['a:row:copy']"
class="nc-project-menu-item"
class="nc-base-menu-item"
data-testid="context-menu-item-copy"
@click="copyValue(contextMenuTarget)"
>
@ -1570,7 +1570,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
(isLinksOrLTAR(fields[contextMenuTarget.col]) || !isVirtualCol(fields[contextMenuTarget.col]))
"
v-e="['a:row:clear']"
class="nc-project-menu-item"
class="nc-base-menu-item"
@click="clearCell(contextMenuTarget)"
>
<GeneralIcon icon="close" />
@ -1581,7 +1581,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<NcMenuItem
v-else-if="contextMenuTarget"
v-e="['a:row:clear-range']"
class="nc-project-menu-item"
class="nc-base-menu-item"
@click="clearSelectedRangeOfCells()"
>
<GeneralIcon icon="closeBox" class="text-gray-500" />
@ -1592,7 +1592,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<NcMenuItem
v-if="contextMenuTarget && (selectedRange.isSingleCell() || selectedRange.isSingleRow())"
v-e="['a:row:delete']"
class="nc-project-menu-item !text-red-600 !hover:bg-red-50"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
@click="confirmDeleteRow(contextMenuTarget.row)"
>
<GeneralIcon icon="delete" />
@ -1602,7 +1602,7 @@ const handleCellClick = (event: MouseEvent, row: number, col: number) => {
<div v-else-if="contextMenuTarget && deleteRangeOfRows">
<NcMenuItem
v-e="['a:row:delete']"
class="nc-project-menu-item !text-red-600 !hover:bg-red-50"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
@click="deleteSelectedRangeOfRows"
>
<GeneralIcon icon="delete" class="text-gray-500 text-red-600" />

6
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -34,7 +34,7 @@ import {
isYear,
storeToRefs,
toRef,
useProject,
useBase,
} from '#imports'
const renderIcon = (column: ColumnType, abstractType: any) => {
@ -105,9 +105,9 @@ export default defineComponent({
const column = inject(ColumnInj, columnMeta)
const { sqlUis } = storeToRefs(useProject())
const { sqlUis } = storeToRefs(useBase())
const sqlUi = ref(column.value?.base_id ? sqlUis.value[column.value?.base_id] : Object.values(sqlUis.value)[0])
const sqlUi = ref(column.value?.source_id ? sqlUis.value[column.value?.source_id] : Object.values(sqlUis.value)[0])
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))

2
packages/nc-gui/components/smartsheet/header/DeleteColumnModal.vue

@ -20,7 +20,7 @@ const { getMeta } = useMetas()
const { includeM2M } = useGlobal()
const { loadTables } = useProject()
const { loadTables } = useBase()
const isLoading = ref(false)

2
packages/nc-gui/components/smartsheet/toolbar/Erd.vue

@ -31,7 +31,7 @@ const vModel = useVModel(props, 'modelValue', emits)
</div>
<div class="w-full h-70vh">
<LazyErdView :table="activeTable" :base-id="activeTable?.base_id" />
<LazyErdView :table="activeTable" :source-id="activeTable?.source_id" />
</div>
</a-modal>
</template>

12
packages/nc-gui/components/smartsheet/toolbar/ExportSubActions.vue

@ -14,7 +14,7 @@ import {
storeToRefs,
useI18n,
useNuxtApp,
useProject,
useBase,
useSmartsheetStoreOrThrow,
} from '#imports'
@ -24,8 +24,8 @@ const isPublicView = inject(IsPublicInj, ref(false))
const fields = inject(FieldsInj, ref([]))
const projectStore = useProject()
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
const { $api } = useNuxtApp()
@ -55,7 +55,7 @@ const exportFile = async (exportType: ExportTypes) => {
} else {
res = await $api.dbViewRow.export(
'noco',
project.value?.id as string,
base.value?.id as string,
meta.value?.id as string,
selectedView?.value.id as string,
exportType,
@ -100,7 +100,7 @@ const exportFile = async (exportType: ExportTypes) => {
<template>
<a-menu-item>
<div v-e="['a:download:csv']" class="nc-project-menu-item" @click="exportFile(ExportTypes.CSV)">
<div v-e="['a:download:csv']" class="nc-base-menu-item" @click="exportFile(ExportTypes.CSV)">
<component :is="iconMap.csv" />
<!-- Download as CSV -->
{{ $t('activity.downloadCSV') }}
@ -108,7 +108,7 @@ const exportFile = async (exportType: ExportTypes) => {
</a-menu-item>
<a-menu-item>
<div v-e="['a:download:excel']" class="nc-project-menu-item" @click="exportFile(ExportTypes.EXCEL)">
<div v-e="['a:download:excel']" class="nc-base-menu-item" @click="exportFile(ExportTypes.EXCEL)">
<component :is="iconMap.excel" />
<!-- Download as XLSX -->
{{ $t('activity.downloadExcel') }}

6
packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue

@ -26,7 +26,7 @@ import {
ref,
storeToRefs,
toRef,
useProject,
useBase,
} from '#imports'
import type { Filter } from '#imports'
import SingleSelect from '~/components/cell/SingleSelect.vue'
@ -86,9 +86,9 @@ const checkTypeFunctions = {
type FilterType = keyof typeof checkTypeFunctions
const { sqlUis } = storeToRefs(useProject())
const { sqlUis } = storeToRefs(useBase())
const sqlUi = ref(column.value?.base_id ? sqlUis.value[column.value?.base_id] : Object.values(sqlUis.value)[0])
const sqlUi = ref(column.value?.source_id ? sqlUis.value[column.value?.source_id] : Object.values(sqlUis.value)[0])
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))

6
packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue

@ -14,7 +14,7 @@ import {
storeToRefs,
useI18n,
useNuxtApp,
useProject,
useBase,
useRoles,
useSharedView,
useSmartsheetStoreOrThrow,
@ -28,7 +28,7 @@ const isPublicView = inject(IsPublicInj, ref(false))
const isView = false
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { $api } = useNuxtApp()
@ -66,7 +66,7 @@ const exportFile = async (exportType: ExportTypes) => {
} else {
res = await $api.dbViewRow.export(
'noco',
project?.value.id as string,
base?.value.id as string,
meta.value?.id as string,
selectedView.value?.id as string,
exportType,

4
packages/nc-gui/components/smartsheet/toolbar/QrScannerButton.vue

@ -16,7 +16,7 @@ const router = useRouter()
const { $api } = useNuxtApp()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { isMobileMode } = useGlobal()
@ -83,7 +83,7 @@ const onDecode = async (codeValue: string) => {
const selectedColumnToScanFor = getColumnToSearchForByBarOrQrCodeColumnId(idOfSelectedColumnToScanFor.value)
const whereClause = `(${selectedColumnToScanFor?.title},eq,${codeValue})`
const foundRowsForCode = (
await $api.dbViewRow.list(NOCO, project.value.id!, meta.value!.id!, view.value!.title!, {
await $api.dbViewRow.list(NOCO, base.value.id!, meta.value!.id!, view.value!.title!, {
where: whereClause,
})
).list

2
packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue

@ -2,7 +2,7 @@
import type { GridType } from 'nocodb-sdk'
import { ActiveViewInj, IsLockedInj, iconMap, inject, ref, storeToRefs, useMenuCloseOnEsc, useUndoRedo } from '#imports'
const { isSharedBase } = storeToRefs(useProject())
const { isSharedBase } = storeToRefs(useBase())
const view = inject(ActiveViewInj, ref())

8
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -8,14 +8,14 @@ import {
iconMap,
isRtlLang,
message,
projectThemeColors,
baseThemeColors,
ref,
storeToRefs,
useCopy,
useDashboard,
useI18n,
useNuxtApp,
useProject,
useBase,
useRoles,
useSmartsheetStoreOrThrow,
watch,
@ -34,7 +34,7 @@ const { dashboardUrl } = useDashboard()
const { isUIAllowed } = useRoles()
const { isSharedBase } = storeToRefs(useProject())
const { isSharedBase } = storeToRefs(useBase())
const { isMobileMode } = useGlobal()
@ -390,7 +390,7 @@ watch(shared, () => {
data-testid="nc-modal-share-view__theme-picker"
class="!p-0"
:model-value="shared.meta.theme?.primaryColor"
:colors="projectThemeColors"
:colors="baseThemeColors"
:row-size="9"
:advanced="false"
@input="onChangeTheme"

16
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -16,7 +16,7 @@ import {
useI18n,
useMenuCloseOnEsc,
useNuxtApp,
useProject,
useBase,
useRoles,
useSmartsheetStoreOrThrow,
} from '#imports'
@ -58,11 +58,11 @@ const quickImportDialogs: Record<(typeof quickImportDialogTypes)[number], Ref<bo
const { isUIAllowed } = useRoles()
useProject()
useBase()
const meta = inject(MetaInj, ref())
const currentBaseId = computed(() => meta.value?.base_id)
const currentBaseId = computed(() => meta.value?.source_id)
/*
const Icon = computed(() => {
@ -119,7 +119,7 @@ useMenuCloseOnEsc(open)
<template v-if="isUIAllowed('csvTableImport') && !isView && !isPublicView && !isSqlView">
<a-sub-menu key="upload">
<template #title>
<div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<div v-e="['c:navdraw:preview-as']" class="nc-base-menu-item group">
<UploadIcon class="w-4 h-4" />
{{ $t('general.upload') }}
<div class="flex-1" />
@ -133,7 +133,7 @@ useMenuCloseOnEsc(open)
<a-menu-item v-if="isUIAllowed(`${type}TableImport`) && !isView && !isPublicView" :key="type">
<div
v-e="[`a:upload:${type}`]"
class="nc-project-menu-item"
class="nc-base-menu-item"
:class="{ disabled: isLocked }"
@click="!isLocked ? (dialog.value = true) : {}"
>
@ -146,7 +146,7 @@ useMenuCloseOnEsc(open)
</template>
<a-sub-menu key="download">
<template #title>
<div v-e="['c:download']" class="nc-project-menu-item group">
<div v-e="['c:download']" class="nc-base-menu-item group">
<DownloadIcon class="w-4 h-4" />
{{ $t('general.download') }}
<div class="flex-1" />
@ -166,7 +166,7 @@ useMenuCloseOnEsc(open)
class="scrollbar-thin-dull max-h-90vh overflow-auto !py-0"
>
<template #title>
<div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group px-0 !py-0">
<div v-e="['c:navdraw:preview-as']" class="nc-base-menu-item group px-0 !py-0">
<LazySmartsheetToolbarLockType hide-tick :type="lockType" />
<component :is="iconMap.arrowRight" />
@ -197,7 +197,7 @@ useMenuCloseOnEsc(open)
:key="tp"
v-model="quickImportDialogs[tp].value"
:import-type="tp"
:base-id="currentBaseId"
:source-id="currentBaseId"
:import-data-only="true"
/>
</template>

8
packages/nc-gui/components/smartsheet/toolbar/ViewInfo.vue

@ -5,7 +5,7 @@ const { isMobileMode } = useGlobal()
const { openedViewsTab, activeView } = storeToRefs(useViewsStore())
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { activeTable } = storeToRefs(useTablesStore())
@ -25,11 +25,11 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
<template v-if="!isMobileMode">
<NcTooltip class="ml-0.75 max-w-1/4">
<template #title>
{{ project?.title }}
{{ base?.title }}
</template>
<div class="flex flex-row items-center gap-x-1.5">
<GeneralProjectIcon
:meta="{ type: project?.type }"
:meta="{ type: base?.type }"
class="!grayscale"
:style="{
filter: 'grayscale(100%) brightness(115%)',
@ -37,7 +37,7 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
/>
<div class="hidden !2xl:(flex truncate ml-1)">
<span class="truncate text-gray-700">
{{ project?.title }}
{{ base?.title }}
</span>
</div>
</div>

4
packages/nc-gui/components/tabs/Smartsheet.vue

@ -89,8 +89,8 @@ const onDrop = async (event: DragEvent) => {
const data = JSON.parse(event.dataTransfer!.getData('text/json'))
// Do something with the received data
// if dragged item is not from the same base, return
if (data.baseId !== meta.value?.base_id) return
// if dragged item is not from the same source, return
if (data.sourceId !== meta.value?.source_id) return
// if dragged item or opened view is not a table, return
if (data.type !== 'table' || meta.value?.type !== 'table') return

18
packages/nc-gui/components/tabs/auth/ApiTokenManagement.vue

@ -9,7 +9,7 @@ import {
useCopy,
useI18n,
useNuxtApp,
useProject,
useBase,
} from '#imports'
interface ApiToken extends ApiTokenType {
@ -20,8 +20,8 @@ const { t } = useI18n()
const { $api, $e } = useNuxtApp()
const projectStore = useProject()
const { project } = storeToRefs(projectStore)
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
const { copy } = useCopy()
@ -34,9 +34,9 @@ const showDeleteTokenModal = ref(false)
const selectedTokenData = ref<ApiToken>({})
const loadApiTokens = async () => {
if (!project.value?.id) return
if (!base.value?.id) return
tokensInfo.value = (await $api.apiToken.list(project.value.id)).list
tokensInfo.value = (await $api.apiToken.list(base.value.id)).list
}
const openNewTokenModal = () => {
@ -59,9 +59,9 @@ const copyToken = async (token: string | undefined) => {
const generateToken = async () => {
try {
if (!project.value?.id) return
if (!base.value?.id) return
await $api.apiToken.create(project.value.id, selectedTokenData.value)
await $api.apiToken.create(base.value.id, selectedTokenData.value)
showNewTokenModal.value = false
// Token generated successfully
message.success(t('msg.success.tokenGenerated'))
@ -76,9 +76,9 @@ const generateToken = async () => {
const deleteToken = async () => {
try {
if (!project.value?.id || !selectedTokenData.value.token) return
if (!base.value?.id || !selectedTokenData.value.token) return
await $api.apiToken.delete(project.value.id, selectedTokenData.value.token)
await $api.apiToken.delete(base.value.id, selectedTokenData.value.token)
// Token deleted successfully
message.success(t('msg.success.tokenDeleted'))

34
packages/nc-gui/components/tabs/auth/UserManagement.vue

@ -13,7 +13,7 @@ import {
useDashboard,
useI18n,
useNuxtApp,
useProject,
useBase,
useRoles,
watchDebounced,
} from '#imports'
@ -25,7 +25,7 @@ const { $e } = useNuxtApp()
const { api } = useApi()
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { copy } = useCopy()
@ -53,10 +53,10 @@ const searchText = ref<string>('')
const loadUsers = async (page = currentPage.value, limit = currentLimit.value) => {
try {
if (!project.value?.id) return
if (!base.value?.id) return
// TODO: Types of api is not correct
const response: any = await api.auth.projectUserList(project.value?.id, {
const response: any = await api.auth.baseUserList(base.value?.id, {
query: {
limit,
offset: (page - 1) * limit,
@ -75,16 +75,16 @@ const loadUsers = async (page = currentPage.value, limit = currentLimit.value) =
const inviteUser = async (user: User) => {
try {
if (!project.value?.id) return
if (!base.value?.id) return
if (!user.roles) {
// mark it as editor by default
user.roles = 'editor'
}
await api.auth.projectUserAdd(project.value.id, user as ProjectUserReqType)
await api.auth.baseUserAdd(base.value.id, user as ProjectUserReqType)
// Successfully added user to project
// Successfully added user to base
message.success(t('msg.success.userAddedToProject'))
await loadUsers()
} catch (e: any) {
@ -96,11 +96,11 @@ const inviteUser = async (user: User) => {
const deleteUser = async () => {
try {
if (!project.value?.id || !selectedUser.value?.id) return
if (!base.value?.id || !selectedUser.value?.id) return
await api.auth.projectUserRemove(project.value.id, selectedUser.value.id)
await api.auth.baseUserRemove(base.value.id, selectedUser.value.id)
// Successfully deleted user from project
// Successfully deleted user from base
message.success(t('msg.success.userDeletedFromProject'))
await loadUsers()
@ -131,10 +131,10 @@ const onDelete = (user: User) => {
}
const resendInvite = async (user: User) => {
if (!project.value?.id) return
if (!base.value?.id) return
try {
await api.auth.projectUserResendInvite(project.value.id, user.id)
await api.auth.baseUserResendInvite(base.value.id, user.id)
// Invite email sent successfully
message.success(t('msg.success.inviteEmailSent'))
@ -209,7 +209,7 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
>
<div class="flex flex-col h-full">
<div class="flex flex-row justify-center mt-2 text-center w-full text-base">
This action will remove this user from this project
This action will remove this user from this base
</div>
<div class="flex mt-6 justify-end space-x-2">
<a-button class="!rounded-md" @click="showUserDeleteModal = false"> {{ $t('general.cancel') }}</a-button>
@ -287,7 +287,7 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
</div>
<div class="flex w-1/6 flex-wrap justify-end">
<template v-if="!isSuperAdmin(user)">
<a-tooltip v-if="user.project_id" placement="bottom">
<a-tooltip v-if="user.base_id" placement="bottom">
<template #title>
<span>{{ $t('activity.editUser') }}</span>
</template>
@ -299,8 +299,8 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
</a-button>
</a-tooltip>
<!-- Add user to project -->
<a-tooltip v-if="!user.project_id" placement="bottom">
<!-- Add user to base -->
<a-tooltip v-if="!user.base_id" placement="bottom">
<template #title>
<span>{{ $t('activity.addUserToProject') }}</span>
</template>
@ -312,7 +312,7 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
</a-button>
</a-tooltip>
<!-- Remove user from the project -->
<!-- Remove user from the base -->
<a-tooltip v-else placement="bottom">
<template #title>
<span>{{ $t('activity.deleteUser') }}</span>

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

@ -9,7 +9,7 @@ import {
useDashboard,
useI18n,
useNuxtApp,
useProject,
useBase,
} from '#imports'
interface ShareBase {
@ -29,23 +29,23 @@ const { dashboardUrl } = useDashboard()
const { $api, $e } = useNuxtApp()
const base = ref<null | ShareBase>(null)
const sharedBase = ref<null | ShareBase>(null)
const showEditBaseDropdown = ref(false)
const { project } = storeToRefs(useProject())
const { base } = storeToRefs(useBase())
const { copy } = useCopy()
const url = computed(() => (base.value && base.value.uuid ? `${dashboardUrl.value}#/base/${base.value.uuid}` : null))
const url = computed(() => (sharedBase.value && sharedBase.value.uuid ? `${dashboardUrl.value}#/base/${sharedBase.value.uuid}` : null))
const loadBase = async () => {
try {
if (!project.value.id) return
if (!base.value.id) return
const res = await $api.project.sharedBaseGet(project.value.id)
const res = await $api.base.sharedBaseGet(base.value.id)
base.value = {
sharedBase.value = {
uuid: res.uuid,
url: res.url,
role: res.roles,
@ -57,14 +57,14 @@ const loadBase = async () => {
const createShareBase = async (role = ShareBaseRole.Viewer) => {
try {
if (!project.value.id) return
if (!base.value.id) return
const res = await $api.project.sharedBaseUpdate(project.value.id, {
const res = await $api.base.sharedBaseUpdate(base.value.id, {
roles: role,
})
base.value = res ?? {}
base.value!.role = role
sharedBase.value = res ?? {}
sharedBase.value!.role = role
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -74,10 +74,10 @@ const createShareBase = async (role = ShareBaseRole.Viewer) => {
const disableSharedBase = async () => {
try {
if (!project.value.id) return
if (!base.value.id) return
await $api.project.sharedBaseDisable(project.value.id)
base.value = null
await $api.base.sharedBaseDisable(base.value.id)
sharedBase.value = null
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -87,15 +87,15 @@ const disableSharedBase = async () => {
const recreate = async () => {
try {
if (!project.value.id) return
if (!base.value.id) return
const sharedBase = await $api.project.sharedBaseCreate(project.value.id, {
roles: base.value?.role || ShareBaseRole.Viewer,
const sharedBase = await $api.base.sharedBaseCreate(base.value.id, {
roles: sharedBase.value?.role || ShareBaseRole.Viewer,
})
const newBase = sharedBase || {}
base.value = { ...newBase, role: base.value?.role }
sharedBase.value = { ...newBase, role: sharedBase.value?.role }
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -145,7 +145,7 @@ style="background: transparent; border: 1px solid #ddd"></iframe>`)
}
onMounted(() => {
if (!base.value) {
if (!sharedBase.value) {
loadBase()
}
})
@ -153,14 +153,14 @@ onMounted(() => {
<template>
<div class="flex flex-col gap-2 w-full" data-testid="nc-share-base-sub-modal">
<!-- Generate publicly shareable readonly base -->
<!-- Generate publicly shareable readonly source -->
<div class="flex text-xs text-gray-500 justify-start ml-1">{{ $t('msg.info.generatePublicShareableReadonlyBase') }}</div>
<div class="flex flex-row justify-between mx-1">
<a-dropdown v-model="showEditBaseDropdown" class="flex" overlay-class-name="nc-dropdown-shared-base-toggle">
<a-button>
<div class="flex flex-row rounded-md items-center space-x-2 nc-disable-shared-base">
<div v-if="base?.uuid">{{ $t('activity.shareBase.enable') }}</div>
<div v-if="sharedBase?.uuid">{{ $t('activity.shareBase.enable') }}</div>
<div v-else>{{ $t('activity.shareBase.disable') }}</div>
<IcRoundKeyboardArrowDown class="h-[1rem]" />
</div>
@ -169,7 +169,7 @@ onMounted(() => {
<template #overlay>
<a-menu>
<a-menu-item>
<div v-if="base?.uuid" class="py-3" @click="disableSharedBase">{{ $t('activity.shareBase.disable') }}</div>
<div v-if="sharedBase?.uuid" class="py-3" @click="disableSharedBase">{{ $t('activity.shareBase.disable') }}</div>
<div v-else class="py-3" @click="createShareBase(ShareBaseRole.Viewer)">{{ $t('activity.shareBase.enable') }}</div>
</a-menu-item>
</a-menu>
@ -177,8 +177,8 @@ onMounted(() => {
</a-dropdown>
<a-select
v-if="base?.uuid"
v-model:value="base.role"
v-if="sharedBase?.uuid"
v-model:value="sharedBase.role"
class="flex nc-shared-base-role"
dropdown-class-name="nc-dropdown-share-base-role"
>
@ -201,7 +201,7 @@ onMounted(() => {
</a-select-option>
</a-select>
</div>
<div v-if="base?.uuid" class="flex flex-row mt-2 bg-red-50 py-4 mx-1 px-2 items-center rounded-sm w-full justify-between">
<div v-if="sharedBase?.uuid" class="flex flex-row mt-2 bg-red-50 py-4 mx-1 px-2 items-center rounded-sm w-full justify-between">
<span class="flex text-xs overflow-x-hidden overflow-ellipsis text-gray-700 pl-2 nc-url">{{ url }}</span>
<div class="flex border-l-1 pt-1 pl-1">

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save