Browse Source

Merge pull request #3234 from nocodb/fix/gui-v2-shared-base-issues

fix(gui-v2) shared base issues
pull/3246/head
Raju Udava 2 years ago committed by GitHub
parent
commit
60d1b358fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  2. 3
      packages/nc-gui-v2/components/smartsheet-toolbar/MoreActions.vue
  3. 12
      packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue
  4. 17
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  5. 11
      packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue
  6. 13
      packages/nc-gui-v2/components/virtual-cell/HasMany.vue
  7. 9
      packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue
  8. 19
      packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue
  9. 32
      packages/nc-gui-v2/composables/useProject.ts
  10. 16
      packages/nc-gui-v2/composables/useUIPermission/index.ts
  11. 10
      packages/nc-gui-v2/composables/useViewColumns.ts
  12. 6
      packages/nc-gui-v2/composables/useViewData.ts
  13. 2
      packages/nc-gui-v2/composables/useViewFilters.ts
  14. 17
      packages/nc-gui-v2/composables/useViewSorts.ts
  15. 2
      packages/nc-gui-v2/layouts/base.vue
  16. 47
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/index.vue
  17. 3
      packages/nocodb/src/lib/utils/projectAcl.ts

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

@ -241,7 +241,7 @@ function openTableCreateDialog() {
<span class="text-gray-500 group-hover:(text-primary/100) flex-1">{{ $t('tooltip.addTable') }}</span>
<a-dropdown :trigger="['click']" @click.stop>
<a-dropdown v-if="!isSharedBase" :trigger="['click']" @click.stop>
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100" />
<template #overlay>
@ -341,7 +341,7 @@ function openTableCreateDialog() {
<div class="nc-tbl-title flex-1">{{ table.title }}</div>
<a-dropdown
v-if="!isLocked && (isUIAllowed('table-rename') || isUIAllowed('table-delete'))"
v-if="!isSharedBase && !isLocked && (isUIAllowed('table-rename') || isUIAllowed('table-delete'))"
:trigger="['click']"
@click.stop
>
@ -380,7 +380,7 @@ function openTableCreateDialog() {
</div>
</div>
<template v-if="!isLocked" #overlay>
<template v-if="!isLocked && !isSharedBase" #overlay>
<a-menu class="!py-0 rounded text-sm">
<template v-if="contextMenuTarget.type === 'table'">
<a-menu-item v-if="isUIAllowed('table-rename')" @click="openRenameTableDialog(contextMenuTarget.value)">

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

@ -130,7 +130,7 @@ const exportFile = async (exportType: ExportTypes) => {
</div>
<div
v-if="isUIAllowed('SharedViewList') && !isView && !isPublicView"
v-if="isUIAllowed('sharedViewList') && !isView && !isPublicView"
v-t="['a:actions:shared-view-list']"
class="nc-menu-item"
@click="sharedViewListDlg = true"
@ -139,7 +139,6 @@ const exportFile = async (exportType: ExportTypes) => {
<!-- Shared View List -->
{{ $t('activity.listSharedView') }}
</div>
<div
v-if="isUIAllowed('webhook') && !isView && !isPublicView"
v-t="['c:actions:webhook']"

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

@ -4,8 +4,7 @@ import { ViewTypes } from 'nocodb-sdk'
import { computed } from 'vue'
import { message } from 'ant-design-vue'
import { useNuxtApp } from '#app'
import { useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
import { extractSdkResponseErrorMsg, useProject, useSmartsheetStoreOrThrow } from '#imports'
import MdiOpenInNewIcon from '~icons/mdi/open-in-new'
import MdiCopyIcon from '~icons/mdi/content-copy'
@ -19,6 +18,8 @@ const { dashboardUrl } = useDashboard()
const { isUIAllowed } = useUIPermission()
const { isSharedBase } = useProject()
let showShareModel = $ref(false)
let passwordProtected = $ref(false)
@ -118,7 +119,12 @@ onMounted(() => {
<template>
<div>
<a-button v-if="isUIAllowed('share-view')" v-t="['c:view:share']" outlined class="nc-btn-share-view nc-toolbar-btn">
<a-button
v-if="isUIAllowed('share-view') && !isSharedBase"
v-t="['c:view:share']"
outlined
class="nc-btn-share-view nc-toolbar-btn"
>
<div class="flex align-center gap-1" @click="genShareLink">
<MdiOpenInNewIcon />
<!-- Share View -->

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

@ -489,14 +489,25 @@ const onNavigate = (dir: NavigateDir) => {
<template v-if="!isLocked && isUIAllowed('xcDatatableEditable')" #overlay>
<a-menu class="bg-white shadow" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)"
><span class="text-xs">Delete row</span></a-menu-item
><span class="text-xs">
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</span></a-menu-item
>
<a-menu-item @click="deleteSelectedRows"
><span class="text-xs">
<!-- Delete Selected Rows -->
{{ $t('activity.deleteSelectedRow') }}
</span></a-menu-item
>
<a-menu-item @click="deleteSelectedRows"><span class="text-xs">Delete all selected rows</span></a-menu-item>
<a-menu-item v-if="contextMenuTarget" @click="clearCell(contextMenuTarget)"
><span class="text-xs">Clear cell</span>
</a-menu-item>
<a-menu-item v-if="contextMenuTarget" @click="addEmptyRow(contextMenuTarget.row + 1)">
<span class="text-xs">Insert new row</span>
<span class="text-xs">
<!-- Insert New Row -->
{{ $t('activity.insertRow') }}
</span>
</a-menu-item>
</a-menu>
</template>

11
packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue

@ -13,6 +13,7 @@ import {
ref,
useProvideLTARStore,
useSmartsheetRowStoreOrThrow,
useUIPermission,
} from '#imports'
import MdiArrowExpand from '~icons/mdi/arrow-expand'
import MdiPlus from '~icons/mdi/plus'
@ -31,13 +32,16 @@ const row = inject(RowInj)!
const active = inject(ActiveCellInj)!
const readonly = inject(ReadonlyInj, false)
const readOnly = inject(ReadonlyInj, false)
const isLocked = inject(IsLockedInj)
const { isUIAllowed } = useUIPermission()
const listItemsDlg = ref(false)
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
const { loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>,
row,
@ -74,7 +78,10 @@ const unlinkRef = async (rec: Record<string, any>) => {
<ItemChip :item="value" :value="value[relatedTablePrimaryValueProp]" @unlink="unlinkRef(value)" />
</template>
</div>
<div v-if="!readonly || !isLocked" class="flex-1 flex justify-end gap-1 min-h-[30px] align-center">
<div
v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')"
class="flex-1 flex justify-end gap-1 min-h-[30px] align-center"
>
<component
:is="addIcon"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 select-none group-hover:(text-gray-500) nc-plus"

13
packages/nc-gui-v2/components/virtual-cell/HasMany.vue

@ -15,6 +15,7 @@ import {
ref,
useProvideLTARStore,
useSmartsheetRowStoreOrThrow,
useUIPermission,
} from '#imports'
const ItemChip = defineAsyncComponent(() => import('./components/ItemChip.vue'))
@ -33,7 +34,7 @@ const reloadTrigger = inject(ReloadViewDataHookInj)!
const isForm = inject(IsFormInj)
const readonly = inject(ReadonlyInj, false)
const readOnly = inject(ReadonlyInj, false)
const isLocked = inject(IsLockedInj)
@ -41,7 +42,10 @@ const listItemsDlg = ref(false)
const childListDlg = ref(false)
const { isUIAllowed } = useUIPermission()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
const { loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>,
row,
@ -91,13 +95,16 @@ const unlinkRef = async (rec: Record<string, any>) => {
</span>
</template>
</div>
<div v-if="!isLocked" class="flex-grow flex justify-end gap-1 min-h-[30px] align-center">
<div
v-if="!isLocked && isUIAllowed('xcDatatableEditable')"
class="flex-grow flex justify-end gap-1 min-h-[30px] align-center"
>
<MdiArrowExpand
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"
@click="childListDlg = true"
/>
<MdiPlus
v-if="!readonly"
v-if="!readOnly"
class="select-none text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus"
@click="listItemsDlg = true"
/>

9
packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue

@ -14,6 +14,7 @@ import {
ref,
useProvideLTARStore,
useSmartsheetRowStoreOrThrow,
useUIPermission,
} from '#imports'
const ItemChip = defineAsyncComponent(() => import('./components/ItemChip.vue'))
@ -32,7 +33,7 @@ const reloadTrigger = inject(ReloadViewDataHookInj)!
const isForm = inject(IsFormInj)
const readonly = inject(ReadonlyInj, false)
const readOnly = inject(ReadonlyInj, false)
const isLocked = inject(IsLockedInj)
@ -40,6 +41,8 @@ const listItemsDlg = ref(false)
const childListDlg = ref(false)
const { isUIAllowed } = useUIPermission()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()
const { loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>,
@ -91,14 +94,14 @@ const unlinkRef = async (rec: Record<string, any>) => {
</template>
</div>
<div v-if="!isLocked" class="flex-1 flex justify-end gap-1 min-h-[30px] align-center">
<div v-if="!isLocked && isUIAllowed('xcDatatableEditable')" class="flex-1 flex justify-end gap-1 min-h-[30px] align-center">
<MdiArrowExpand
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"
@click="childListDlg = true"
/>
<MdiPlus
v-if="!readonly"
v-if="!readOnly"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus"
@click="listItemsDlg = true"
/>

19
packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue

@ -1,5 +1,14 @@
<script lang="ts" setup>
import { ActiveCellInj, IsFormInj, ReadonlyInj, defineAsyncComponent, inject, ref, useLTARStoreOrThrow } from '#imports'
import {
ActiveCellInj,
IsFormInj,
ReadonlyInj,
defineAsyncComponent,
inject,
ref,
useLTARStoreOrThrow,
useUIPermission,
} from '#imports'
interface Props {
value?: string | number | boolean
@ -14,7 +23,9 @@ const ExpandedForm: any = defineAsyncComponent(() => import('../../smartsheet/ex
const { relatedTableMeta } = useLTARStoreOrThrow()!
const readonly = inject(ReadonlyInj, false)
const { isUIAllowed } = useUIPermission()
const readOnly = inject(ReadonlyInj, false)
const active = inject(ActiveCellInj, ref(false))
@ -39,13 +50,13 @@ export default {
>
<span class="name">{{ value }}</span>
<div v-show="active || isForm" v-if="!readonly && !isLocked" class="flex align-center">
<div v-show="active || isForm" v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')" class="flex align-center">
<MdiCloseThick class="unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" @click.stop="emit('unlink')" />
</div>
<Suspense>
<ExpandedForm
v-if="!readonly && !isLocked && expandedFormDlg"
v-if="!readOnly && !isLocked && expandedFormDlg"
v-model="expandedFormDlg"
:row="{ row: item }"
:meta="relatedTableMeta"

32
packages/nc-gui-v2/composables/useProject.ts

@ -18,6 +18,14 @@ export function useProject(projectId?: MaybeRef<string>) {
// todo: refactor path param name and variable name
const projectType = $computed(() => route.params.projectType as string)
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '')
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType))
const isPg = computed(() => projectBaseType === 'pg')
const sqlUi = computed(
() => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>,
)
const isSharedBase = computed(() => projectType === 'base')
async function loadProjectMetaInfo(force?: boolean) {
if (!projectMetaInfo.value || force) {
const data = await $api.project.metaGet(project.value.id!, {}, {})
@ -28,7 +36,17 @@ export function useProject(projectId?: MaybeRef<string>) {
async function loadProjectRoles() {
projectRoles.value = {}
if (project.value.id) {
if (isSharedBase.value) {
const user = await $api.auth.me(
{},
{
headers: {
'xc-shared-base-id': route.params.projectId,
},
},
)
projectRoles.value = user.roles
} else if (project.value.id) {
const user = await $api.auth.me({ project_id: project.value.id })
projectRoles.value = user.roles
}
@ -37,8 +55,7 @@ export function useProject(projectId?: MaybeRef<string>) {
async function loadTables() {
if (project.value.id) {
const tablesResponse = await $api.dbTable.list(project.value.id, {
// FIXME: type
includeM2M: includeM2M.value || '',
includeM2M: includeM2M.value,
})
if (tablesResponse.list) tables.value = tablesResponse.list
}
@ -58,15 +75,6 @@ export function useProject(projectId?: MaybeRef<string>) {
await loadTables()
}
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '')
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType))
const isPg = computed(() => projectBaseType === 'pg')
const sqlUi = computed(
() => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>,
)
const isSharedBase = computed(() => projectType === 'base')
return {
project,
tables,

16
packages/nc-gui-v2/composables/useUIPermission/index.ts

@ -7,7 +7,7 @@ export function useUIPermission() {
const projectRoles = useState<Record<string, boolean>>(USER_PROJECT_ROLES, () => ({}))
const getRoles = (skipPreviewAs = false) => {
const baseRoles = computed(() => {
let userRoles = user.value?.roles || {}
// if string populate key-value paired object
@ -19,24 +19,24 @@ export function useUIPermission() {
}
// merge user role and project specific user roles
let roles = {
const roles = {
...userRoles,
...projectRoles.value,
}
return roles
})
const isUIAllowed = (permission: Permission | string, skipPreviewAs = false) => {
let roles = baseRoles.value
if (previewAs.value && !skipPreviewAs) {
roles = {
[previewAs.value]: true,
}
}
return roles
}
const isUIAllowed = (permission: Permission | string, skipPreviewAs = false) => {
return Object.entries<boolean>(getRoles(skipPreviewAs)).some(([role, hasRole]) => {
return Object.entries<boolean>(roles).some(([role, hasRole]) => {
const rolePermission = rolePermissions[role as keyof typeof rolePermissions] as '*' | Record<Permission, true>
return hasRole && (rolePermission === '*' || rolePermission?.[permission as Permission])
})
}

10
packages/nc-gui-v2/composables/useViewColumns.ts

@ -8,6 +8,7 @@ import type { Field } from '~/lib'
export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRef<TableType>, reloadData?: () => void) {
const isPublic = inject(IsPublicInj, ref(false))
const fields = ref<Field[]>()
const filterQuery = ref('')
@ -16,10 +17,13 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
const { isUIAllowed } = useUIPermission()
const { isSharedBase } = useProject()
const loadViewColumns = async () => {
if (!meta || !view) return
let order = 1
if (view.value?.id) {
const data = (isPublic.value ? meta.value?.columns : await $api.dbViewColumn.list(view.value.id)) as any[]
@ -48,7 +52,7 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
}
const showAll = async (ignoreIds?: any) => {
if (isPublic.value) {
if (isPublic.value || isSharedBase) {
fields.value = fields.value?.map((field: Field) => ({
...field,
show: true,
@ -71,7 +75,7 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
reloadData?.()
}
const hideAll = async (ignoreIds?: any) => {
if (isPublic.value) {
if (isPublic.value || isSharedBase) {
fields.value = fields.value?.map((field: Field) => ({
...field,
show: false,
@ -140,7 +144,7 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
},
set(v: boolean) {
if (view?.value?.id) {
if (!isPublic.value) {
if (!isPublic.value && !isSharedBase) {
$api.dbView
.update(view.value.id, {
show_system_fields: v,

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

@ -2,7 +2,7 @@ import type { Api, ColumnType, FormType, GalleryType, PaginatedType, TableType,
import type { ComputedRef, Ref } from 'vue'
import { message } from 'ant-design-vue'
import { useNuxtApp } from '#app'
import { IsPublicInj, NOCO, extractPkFromRow, extractSdkResponseErrorMsg, useProject } from '#imports'
import { IsPublicInj, NOCO, extractPkFromRow, extractSdkResponseErrorMsg, useProject, useUIPermission } from '#imports'
const formatData = (list: Record<string, any>[]) =>
list.map((row) => ({
@ -42,6 +42,8 @@ export function useViewData(
const { project } = useProject()
const { fetchSharedViewData, paginationData: sharedPaginationData } = useSharedView()
const { $api } = useNuxtApp()
const { sorts, nestedFilters: filters } = useSharedView()
const { isUIAllowed } = useUIPermission()
const paginationData = computed({
get: () => (isPublic.value ? sharedPaginationData.value : _paginationData.value),
@ -109,6 +111,8 @@ export function useViewData(
const response = !isPublic.value
? await $api.dbViewRow.list('noco', project.value.id!, meta.value.id!, viewMeta!.value.id, {
...params,
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(filters.value) }),
where: where?.value,
})
: await fetchSharedViewData()

2
packages/nc-gui-v2/composables/useViewFilters.ts

@ -60,8 +60,6 @@ export function useViewFilters(
}
const loadFilters = async (hookId?: string) => {
if (isPublic.value) return
if (hookId) {
if (parentId) {
filters.value = await $api.dbTableFilter.childrenRead(parentId)

17
packages/nc-gui-v2/composables/useViewSorts.ts

@ -7,15 +7,19 @@ export function useViewSorts(
reloadData?: () => void,
) {
const _sorts = ref<SortType[]>([])
const { sorts: sharedViewSorts, sharedView } = useSharedView()
const reloadHook = inject(ReloadViewDataHookInj)
const isPublic = inject(IsPublicInj, ref(false))
const { isSharedBase } = useProject()
const sorts = computed<SortType[]>({
get: () => (isPublic.value ? sharedViewSorts.value : _sorts.value),
get: () => (isPublic.value || isSharedBase ? sharedViewSorts.value : _sorts.value),
set: (value) => {
if (isPublic.value) {
if (isPublic.value || isSharedBase) {
sharedViewSorts.value = value
} else {
_sorts.value = value
@ -39,9 +43,7 @@ export function useViewSorts(
}
const saveOrUpdate = async (sort: SortType, i: number) => {
// TODO:
// if (!this.shared && this._isUIAllowed('sortSync')) {
if (isPublic.value) {
if (isPublic.value || isSharedBase) {
sorts.value[i] = sort
sorts.value = [...sorts.value]
return
@ -66,10 +68,7 @@ export function useViewSorts(
}
const deleteSort = async (sort: SortType, i: number) => {
// TOOD:
// if (!this.shared && sort.id && this._isUIAllowed('sortSync')) {
if (isUIAllowed('sortSync') && sort.id && !isPublic.value) {
if (isUIAllowed('sortSync') && sort.id && !isPublic.value && !isSharedBase) {
await $api.dbTableSort.delete(sort.id)
}
sorts.value.splice(i, 1)

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

@ -48,7 +48,7 @@ const logout = () => {
<GeneralReleaseInfo />
<GeneralShareBaseButton />
<GeneralShareBaseButton v-if="!isSharedBase" />
<a-tooltip placement="bottom" :mouse-enter-delay="1">
<template #title> Switch language</template>

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

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { UploadChangeParam, UploadFile } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import { ref, useDialog, useDropZone, useFileDialog, useNuxtApp, watch } from '#imports'
import { ref, useDialog, useDropZone, useFileDialog, useNuxtApp, useProject, watch } from '#imports'
import DlgQuickImport from '~/components/dlg/QuickImport.vue'
const dropZone = ref<HTMLDivElement>()
@ -10,6 +10,8 @@ const { isOverDropZone } = useDropZone(dropZone, onDrop)
const { files, open, reset } = useFileDialog()
const { isSharedBase } = useProject()
const { $e } = useNuxtApp()
type QuickImportTypes = 'excel' | 'json' | 'csv'
@ -106,26 +108,31 @@ function openQuickImportDialog(type: QuickImportTypes, file: File) {
</script>
<template>
<div ref="dropZone" class="h-full w-full text-gray-600 flex items-center justify-center relative">
<general-overlay
:model-value="true"
:class="[isOverDropZone ? 'bg-gray-300/75 border-primary shadow' : 'bg-gray-100/25 border-gray-500 cursor-pointer']"
inline
style="top: 20%; left: 20%; right: 20%; bottom: 20%"
class="text-3xl flex items-center justify-center gap-2 border-1 border-dashed rounded hover:border-primary"
@click="open"
>
<template v-if="isOverDropZone"> <MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here </template>
</general-overlay>
<div class="flex flex-col gap-6 items-center justify-center md:w-1/2 mx-auto text-center">
<div class="h-full w-full text-gray-600 flex items-center justify-center relative">
<div v-if="isSharedBase" class="flex flex-col gap-6 items-center justify-center mx-auto text-center">
<div class="text-3xl">Welcome to NocoDB!</div>
<div class="flex items-center flex-wrap justify-center gap-2 prose-lg leading-8">
To get started, either drop a <span class="flex items-center gap-2"><PhFileCsv /> CSV</span>,
<span class="flex items-center gap-2"><BiFiletypeJson /> JSON</span> or
<span class="flex items-center gap-2"><BiFiletypeXlsx /> Excel</span> file here or click the button in the top-left of
this page.
</div>
<div v-else ref="dropZone">
<general-overlay
:model-value="true"
:class="[isOverDropZone ? 'bg-gray-300/75 border-primary shadow' : 'bg-gray-100/25 border-gray-500 cursor-pointer']"
inline
style="top: 20%; left: 20%; right: 20%; bottom: 20%"
class="text-3xl flex items-center justify-center gap-2 border-1 border-dashed rounded hover:border-primary"
@click="open"
>
<template v-if="isOverDropZone"> <MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here </template>
</general-overlay>
<div class="flex flex-col gap-6 items-center justify-center md:w-1/2 mx-auto text-center">
<div class="text-3xl">Welcome to NocoDB!</div>
<div class="flex items-center flex-wrap justify-center gap-2 prose-lg leading-8">
To get started, either drop a <span class="flex items-center gap-2"><PhFileCsv /> CSV</span>,
<span class="flex items-center gap-2"><BiFiletypeJson /> JSON</span> or
<span class="flex items-center gap-2"><BiFiletypeXlsx /> Excel</span> file here or click the button in the top-left of
this page.
</div>
</div>
</div>
</div>

3
packages/nocodb/src/lib/utils/projectAcl.ts

@ -211,6 +211,7 @@ export default {
// sort & filter
sortList: true,
filterList: true,
projectInfoGet: true,
galleryViewGet: true,
@ -240,7 +241,7 @@ export default {
indexList: true,
list: true,
xcExportAsCsv: true,
dataCount: true,
dataCount: true
},
user_new: {
passwordChange: true,

Loading…
Cancel
Save