Browse Source

Merge pull request #2892 from nocodb/refactor/views-sidebar-layout

refactor(gui-v2): fix right sidebar layout
pull/2782/head
Braks 2 years ago committed by GitHub
parent
commit
9b838ac8a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui-v2/app.vue
  2. 17
      packages/nc-gui-v2/assets/style-v2.scss
  3. 149
      packages/nc-gui-v2/components/dashboard/TabView.vue
  4. 4
      packages/nc-gui-v2/components/dlg/ViewCreate.vue
  5. 13
      packages/nc-gui-v2/components/smartsheet-toolbar/AddRow.vue
  6. 10
      packages/nc-gui-v2/components/smartsheet-toolbar/DeleteTable.vue
  7. 10
      packages/nc-gui-v2/components/smartsheet-toolbar/LockMenu.vue
  8. 9
      packages/nc-gui-v2/components/smartsheet-toolbar/Reload.vue
  9. 15
      packages/nc-gui-v2/components/smartsheet-toolbar/ShareView.vue
  10. 8
      packages/nc-gui-v2/components/smartsheet-toolbar/ToggleDrawer.vue
  11. 23
      packages/nc-gui-v2/components/smartsheet/Toolbar.vue
  12. 6
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue
  13. 2
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  14. 35
      packages/nc-gui-v2/components/smartsheet/sidebar/Toolbar.vue
  15. 56
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  16. 12
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  17. 63
      packages/nc-gui-v2/composables/useApi.ts
  18. 12
      packages/nc-gui-v2/package-lock.json
  19. 193
      packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue

2
packages/nc-gui-v2/app.vue

@ -30,7 +30,7 @@ const toggleSidebar = () => {
</script>
<template>
<a-layout>
<a-layout class="min-h-[100vh]">
<a-layout-header class="flex !bg-primary items-center text-white px-4 shadow-md">
<MaterialSymbolsMenu v-if="$state.signedIn.value" class="text-xl cursor-pointer" @click="toggleSidebar" />

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

@ -76,3 +76,20 @@ html {
.nc-menu-item {
@apply cursor-pointer text-xs flex align-center gap-2 p-4 relative after:(content-[''] absolute top-0 left-0 w-full h-full right 0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
}
.nc-sidebar-right-item {
@apply relative flex items-center;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out;
content: '';
}
&:hover::after {
@apply ring shadow-2xl transform scale-110;
}
svg {
@apply z-1 text-xl p-1 text-gray-500;
}
}

149
packages/nc-gui-v2/components/dashboard/TabView.vue

@ -1,149 +0,0 @@
<script setup lang="ts">
import useTabs from '~/composables/useTabs'
import MdiPlusIcon from '~icons/mdi/plus'
import MdiTableIcon from '~icons/mdi/table'
import MdiCsvIcon from '~icons/mdi/file-document-outline'
import MdiExcelIcon from '~icons/mdi/file-excel'
import MdiJSONIcon from '~icons/mdi/code-json'
import MdiAirTableIcon from '~icons/mdi/table-large'
import MdiRequestDataSourceIcon from '~icons/mdi/open-in-new'
import MdiAccountGroupIcon from '~icons/mdi/account-group'
const { tabs, activeTab, closeTab } = useTabs()
const { isUIAllowed } = useUIPermission()
const tableCreateDialog = ref(false)
const airtableImportDialog = ref(false)
const quickImportDialog = ref(false)
const importType = ref('')
const currentMenu = ref<string[]>(['addORImport'])
function onEdit(targetKey: number, action: string) {
if (action !== 'add') {
closeTab(targetKey)
}
}
function openQuickImportDialog(type: string) {
quickImportDialog.value = true
importType.value = type
}
</script>
<template>
<div>
<a-tabs v-model:activeKey="activeTab" hide-add type="editable-card" :tab-position="top" @edit="onEdit">
<a-tab-pane v-for="(tab, i) in tabs" :key="i" :value="i" class="text-capitalize" :closable="true">
<template #tab>
<span class="flex items-center gap-2">
<MdiAccountGroupIcon v-if="tab.type === 'auth'" class="text-primary" />
<MdiTableIcon v-else class="text-primary" />
{{ tab.title }}
</span>
</template>
</a-tab-pane>
<template #leftExtra>
<a-menu v-model:selectedKeys="currentMenu" mode="horizontal">
<a-sub-menu key="addORImport">
<template #title>
<span class="flex items-center gap-2">
<MdiPlusIcon />
Add / Import
</span>
</template>
<a-menu-item-group v-if="isUIAllowed('addTable')">
<a-menu-item key="add-new-table" v-t="['a:actions:create-table']" @click="tableCreateDialog = true">
<span class="flex items-center gap-2">
<MdiTableIcon class="text-primary" />
<!-- Add new table -->
{{ $t('tooltip.addTable') }}
</span>
</a-menu-item>
</a-menu-item-group>
<a-menu-item-group title="QUICK IMPORT FROM">
<a-menu-item
v-if="isUIAllowed('airtableImport')"
key="quick-import-airtable"
v-t="['a:actions:import-airtable']"
@click="airtableImportDialog = true"
>
<span class="flex items-center gap-2">
<MdiAirTableIcon class="text-primary" />
<!-- TODO: i18n -->
Airtable
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('csvImport')"
key="quick-import-csv"
v-t="['a:actions:import-csv']"
@click="openQuickImportDialog('csv')"
>
<span class="flex items-center gap-2">
<MdiCsvIcon class="text-primary" />
<!-- TODO: i18n -->
CSV file
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('jsonImport')"
key="quick-import-json"
v-t="['a:actions:import-json']"
@click="openQuickImportDialog('json')"
>
<span class="flex items-center gap-2">
<MdiJSONIcon class="text-primary" />
<!-- TODO: i18n -->
JSON file
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport')"
key="quick-import-excel"
v-t="['a:actions:import-excel']"
@click="openQuickImportDialog('excel')"
>
<span class="flex items-center gap-2">
<MdiExcelIcon class="text-primary" />
<!-- TODO: i18n -->
Microsoft Excel
</span>
</a-menu-item>
</a-menu-item-group>
<a-divider class="ma-0 mb-2" />
<a-menu-item
v-if="isUIAllowed('importRequest')"
key="add-new-table"
v-t="['e:datasource:import-request']"
class="ma-0 mt-3"
>
<a href="https://github.com/nocodb/nocodb/issues/2052" target="_blank" class="prose-sm pa-0">
<span class="flex items-center gap-2">
<MdiRequestDataSourceIcon class="text-primary" />
<!-- TODO: i18n -->
Request a data source you need?
</span>
</a>
</a-menu-item>
</a-sub-menu>
</a-menu>
</template>
</a-tabs>
<DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" />
<DlgQuickImport v-if="quickImportDialog" v-model="quickImportDialog" :import-type="importType" />
<DlgAirtableImport v-if="airtableImportDialog" v-model="airtableImportDialog" />
<v-window v-model="activeTab">
<v-window-item v-for="(tab, i) in tabs" :key="i" :value="i">
<TabsAuth v-if="tab.type === 'auth'" :tab-meta="tab" />
<TabsSmartsheet v-else :tab-meta="tab" />
</v-window-item>
</v-window>
</div>
</template>
<style scoped lang="scss">
:deep(.ant-menu-item-group-list) .ant-menu-item {
@apply m-0 pa-0 pl-4 pr-16;
}
</style>

4
packages/nc-gui-v2/components/dlg/ViewCreate.vue

@ -99,9 +99,7 @@ function init() {
async function onSubmit() {
const isValid = await formValidator?.validateFields()
if (!isValid) return
if (form.type) {
if (isValid && form.type) {
const _meta = unref(meta)
if (!_meta || !_meta.id) return

13
packages/nc-gui-v2/components/smartsheet-toolbar/AddRow.vue

@ -1,10 +1,15 @@
<script setup lang="ts">
import MdiAddIcon from '~icons/mdi/plus-outline'
const emit = defineEmits(['add-row'])
const emits = defineEmits(['addRow'])
</script>
<template>
<MdiAddIcon class="text-grey" @click="emit('add-row')" />
</template>
<a-tooltip placement="left">
<template #title> {{ $t('activity.addRow') }} </template>
<style scoped></style>
<div class="nc-sidebar-right-item hover:after:bg-primary/75 group">
<MdiAddIcon class="group-hover:(!text-white)" @click="emits('addRow')" />
</div>
</a-tooltip>
</template>

10
packages/nc-gui-v2/components/smartsheet-toolbar/DeleteTable.vue

@ -3,7 +3,11 @@ import MdiDeleteIcon from '~icons/mdi/delete-outline'
</script>
<template>
<MdiDeleteIcon class="text-grey" />
</template>
<a-tooltip placement="left">
<template #title> {{ $t('activity.deleteTable') }} </template>
<style scoped></style>
<div class="nc-sidebar-right-item hover:after:bg-red-500 group">
<MdiDeleteIcon class="group-hover:(!text-white)" />
</div>
</a-tooltip>
</template>

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

@ -51,15 +51,11 @@ const Icon = computed(() => {
})
</script>
<script lang="ts">
export default {
name: 'LockMenu',
}
</script>
<template>
<a-dropdown max-width="350" :trigger="['click']">
<Icon class="mx-1 nc-view-lock-menu text-grey"> mdi-lock-outline </Icon>
<div class="nc-sidebar-right-item hover:after:bg-indigo-500 group">
<Icon class="cursor-pointer group-hover:(!text-white)" />
</div>
<template #overlay>
<div class="min-w-[350px] max-w-[500px] shadow bg-white">
<div>

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

@ -1,11 +1,18 @@
<script setup lang="ts">
import { ReloadViewDataHookInj } from '~/context'
import MdiReloadIcon from '~icons/mdi/reload'
const reloadTri = inject(ReloadViewDataHookInj)
</script>
<template>
<MdiReloadIcon class="text-grey" @click="reloadTri.trigger()" />
<a-tooltip placement="left">
<template #title> {{ $t('general.reload') }} </template>
<div class="nc-sidebar-right-item hover:after:bg-green-500 group">
<MdiReloadIcon class="group-hover:(!text-white)" @click="reloadTri.trigger()" />
</div>
</a-tooltip>
</template>
<style scoped></style>

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

@ -1,18 +1,13 @@
<script lang="ts" setup>
import MdiOpenInNew from '~icons/mdi/open-in-new'
import { useUIPermission } from '#imports'
const { isUIAllowed } = useUIPermission()
</script>
<template>
<div>
<a-button v-t="['c:view:share']" outlined class="nc-btn-share-view nc-toolbar-btn" size="small">
<div class="flex align-center gap-1">
<MdiOpenInNew class="text-grey" />
<!-- Share View -->
{{ $t('activity.shareView') }}
</div>
</a-button>
<div v-t="['c:view:share']" class="nc-sidebar-right-item hover:after:bg-secondary/75 group">
<MdiOpenInNew class="group-hover:(!text-white)" />
{{ $t('activity.shareView') }}
</div>
</template>
<style scoped />

8
packages/nc-gui-v2/components/smartsheet-toolbar/ToggleDrawer.vue

@ -1,14 +1,14 @@
<script setup lang="ts">
import MdiDoorOpenIcon from '~icons/mdi/door-open'
import MdiDoorClosedIcon from '~icons/mdi/door-closed'
import MdiMenuClose from '~icons/mdi/menu-close'
const drawerOpen = inject('navDrawerOpen', ref(false))
const Icon = computed(() => (drawerOpen.value ? MdiDoorOpenIcon : MdiDoorClosedIcon))
</script>
<template>
<a-tooltip placement="left">
<template #title> {{ $t('tooltip.toggleNavDraw') }} </template>
<Icon class="rounded text-xl p-1 text-gray-500 hover:(text-white bg-pink-500 shadow)" @click="drawerOpen = !drawerOpen" />
<div class="nc-sidebar-right-item hover:after:bg-pink-500 group">
<MdiMenuClose class="group-hover:(!text-white)" @click="drawerOpen = !drawerOpen" />
</div>
</a-tooltip>
</template>

23
packages/nc-gui-v2/components/smartsheet/Toolbar.vue

@ -15,26 +15,6 @@
<SmartsheetToolbarMoreActions />
<div class="flex-1" />
<SmartsheetToolbarLockMenu />
<div class="dot" />
<SmartsheetToolbarReload />
<div class="dot" />
<SmartsheetToolbarAddRow />
<div class="dot" />
<SmartsheetToolbarDeleteTable />
<div class="dot" />
<SmartsheetToolbarToggleDrawer class="mr-2" />
<div class="dot" />
</div>
</template>
@ -42,7 +22,4 @@
:deep(.nc-toolbar-btn) {
@apply border-0 !text-xs font-semibold px-2;
}
.dot {
@apply w-[3px] h-[3px] bg-gray-300 mx-1 rounded-full;
}
</style>

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

@ -30,9 +30,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<template>
<a-menu :selected-keys="[]" class="flex-1 flex flex-col">
<a-divider class="my-2" />
<h3 class="px-3 text-xs font-semibold flex items-center gap-4">
<h3 class="px-3 py-1 text-xs font-semibold flex items-center gap-4">
{{ $t('activity.createView') }}
<a-tooltip>
<template #title>
@ -96,7 +94,7 @@ function onOpenModal(type: ViewTypes, title = '') {
</a-tooltip>
</a-menu-item>
<div class="flex-auto justify-end flex flex-col md:gap-4 lg:gap-8 mt-4">
<div class="flex-auto justify-end flex flex-col gap-4 mt-4">
<button
class="flex items-center gap-2 w-full mx-3 px-4 py-3 rounded !bg-primary text-white transform translate-x-4 hover:(translate-x-0 shadow-lg) transition duration-150 ease"
@click="onApiSnippet"

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

@ -194,7 +194,7 @@ function onDeleted() {
</script>
<template>
<h3 class="nc-headline pt-3 px-3 text-xs font-semibold">{{ $t('objects.views') }}</h3>
<h3 class="pt-3 px-3 text-xs font-semibold">{{ $t('objects.views') }}</h3>
<a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu" :selected-keys="selected">
<RenameableMenuItem

35
packages/nc-gui-v2/components/smartsheet/sidebar/Toolbar.vue

@ -0,0 +1,35 @@
<template>
<div class="flex gap-2">
<slot name="start" />
<SmartsheetToolbarLockMenu />
<div class="dot" />
<SmartsheetToolbarReload />
<div class="dot" />
<SmartsheetToolbarAddRow />
<div class="dot" />
<SmartsheetToolbarDeleteTable />
<div class="dot" />
<SmartsheetToolbarToggleDrawer />
<slot name="end" />
</div>
</template>
<style scoped>
:deep(.nc-toolbar-btn) {
@apply border-0 !text-xs font-semibold px-2;
}
.dot {
@apply w-[3px] h-[3px] bg-gray-300 rounded-full;
}
</style>

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

@ -2,8 +2,11 @@
import type { FormType, GalleryType, GridType, KanbanType, ViewTypes } from 'nocodb-sdk'
import MenuTop from './MenuTop.vue'
import MenuBottom from './MenuBottom.vue'
import Toolbar from './Toolbar.vue'
import { inject, provide, ref, useApi, useViews, watch } from '#imports'
import { ActiveViewInj, MetaInj, ViewListInj } from '~/context'
import MdiXml from '~icons/mdi/xml'
import MdiHook from '~icons/mdi/hook'
const meta = inject(MetaInj, ref())
@ -16,7 +19,9 @@ const { api } = useApi()
provide(ViewListInj, views)
/** Sidebar visible */
const drawerOpen = inject('navDrawerOpen', ref(false))
const drawerOpen = inject('navDrawerOpen', ref(true))
const sidebarCollapsed = computed(() => !drawerOpen.value)
/** View type to create from modal */
let viewCreateType = $ref<ViewTypes>()
@ -54,9 +59,46 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
</script>
<template>
<a-layout-sider theme="light" class="shadow" :width="drawerOpen ? 0 : 250">
<div class="flex flex-col h-full">
<a-layout-sider
:collapsed="sidebarCollapsed"
collapsiple
collapsed-width="50"
width="250"
class="shadow !mt-[-9px]"
style="height: calc(100% + 9px)"
theme="light"
>
<Toolbar v-if="drawerOpen" class="flex items-center py-3 px-3 justify-between border-b-1" />
<Toolbar v-else class="py-3 px-2 max-w-[50px] flex !flex-col-reverse gap-4 items-center mt-[-1px]">
<template #start>
<a-tooltip placement="left">
<template #title> {{ $t('objects.webhooks') }} </template>
<div class="nc-sidebar-right-item hover:after:bg-gray-300">
<MdiHook />
</div>
</a-tooltip>
<div class="dot" />
<a-tooltip placement="left">
<template #title> Get API Snippet </template>
<div class="nc-sidebar-right-item group hover:after:bg-yellow-500">
<MdiXml class="group-hover:(!text-white)" />
</div>
</a-tooltip>
<div class="dot" />
</template>
</Toolbar>
<div v-if="drawerOpen" class="flex-1 flex flex-col">
<MenuTop @open-modal="openModal" @deleted="loadViews" @sorted="loadViews" />
<a-divider class="my-2" />
<MenuBottom @open-modal="openModal" />
</div>
@ -68,4 +110,12 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
:deep(.ant-menu-title-content) {
@apply w-full;
}
:deep(.ant-layout-sider-children) {
@apply flex flex-col;
}
.dot {
@apply w-[3px] h-[3px] bg-gray-300 rounded-full;
}
</style>

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

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { ColumnType, ViewType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk'
import { computed, inject, onMounted, provide, watch, watchEffect } from '#imports'
import { computed, inject, provide, watch, watchEffect } from '#imports'
import { ActiveViewInj, FieldsInj, IsLockedInj, MetaInj, ReloadViewDataHookInj, TabMetaInj } from '~/context'
import useMetas from '~/composables/useMetas'
@ -27,7 +27,7 @@ provide(ActiveViewInj, activeView)
provide(IsLockedInj, false)
provide(ReloadViewDataHookInj, reloadEventHook)
provide(FieldsInj, fields)
provide('navDrawerOpen', ref(false))
provide('navDrawerOpen', ref(true))
watch(
() => tabMeta && tabMeta?.id,
@ -41,15 +41,21 @@ watch(
<div class="nc-container flex h-full">
<div class="flex flex-col h-full flex-1 min-w-0">
<SmartsheetToolbar />
<template v-if="meta">
<div class="flex flex-1 min-h-0">
<div v-if="activeView" class="h-full flex-grow min-w-0 min-h-0">
<SmartsheetGrid v-if="activeView.type === ViewTypes.GRID" :ref="el" />
<SmartsheetGallery v-else-if="activeView.type === ViewTypes.GALLERY" />
<SmartsheetForm v-else-if="activeView.type === ViewTypes.FORM" />
</div>
<SmartsheetSidebar />
</div>
<teleport to="#sidebar-right">
<SmartsheetSidebar />
</teleport>
</template>
</div>
</div>

63
packages/nc-gui-v2/composables/useApi.ts

@ -0,0 +1,63 @@
import type { AxiosError, AxiosResponse } from 'axios'
import type { Api } from 'nocodb-sdk'
import type { Ref } from 'vue'
import type { EventHook } from '@vueuse/core'
import { createEventHook, ref, useNuxtApp } from '#imports'
interface UseApiReturn<D = any, R = any> {
api: Api<any>
isLoading: Ref<boolean>
error: Ref<AxiosError<D, R> | null>
response: Ref<AxiosResponse<D, R> | null>
onError: EventHook<AxiosError<D, R>>['on']
onResponse: EventHook<AxiosResponse<D, R>>['on']
}
/** todo: add props? */
type UseApiProps = never
export function useApi<Data = any, RequestConfig = any>(_?: UseApiProps): UseApiReturn<Data, RequestConfig> {
const isLoading = ref(false)
const error = ref(null)
const response = ref<any>(null)
const errorHook = createEventHook<AxiosError<Data, RequestConfig>>()
const responseHook = createEventHook<AxiosResponse<Data, RequestConfig>>()
const { $api } = useNuxtApp()
$api.instance.interceptors.request.use(
(config) => {
error.value = null
response.value = null
isLoading.value = true
return config
},
(requestError) => {
errorHook.trigger(requestError)
error.value = requestError
response.value = null
isLoading.value = false
},
)
$api.instance.interceptors.response.use(
(apiResponse) => {
responseHook.trigger(apiResponse as AxiosResponse<Data, RequestConfig>)
// can't properly typecast
response.value = apiResponse
isLoading.value = false
},
(apiError) => {
errorHook.trigger(apiError)
error.value = apiError
isLoading.value = false
},
)
return { api: $api, isLoading, response, error, onError: errorHook.on, onResponse: responseHook.on }
}

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

@ -2301,9 +2301,9 @@
"dev": true
},
"node_modules/@types/papaparse": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.2.tgz",
"integrity": "sha512-BNbCHJkTE4RwmAFkCxEalET4mDvGr/1ld7ZtQ4i/laWI/iiVt+GL07stdvufle4KfywyvloqqpIiJscXNCrKxA==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.3.tgz",
"integrity": "sha512-i7fV8u843vb7nIGpcwdCsG3WjfBONeytRHK1mQL9d5KQAvFeAK2rRisDHicreYpoQ0MXocUDEqunKHTeXdvibg==",
"dev": true,
"dependencies": {
"@types/node": "*"
@ -16180,9 +16180,9 @@
"dev": true
},
"@types/papaparse": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.2.tgz",
"integrity": "sha512-BNbCHJkTE4RwmAFkCxEalET4mDvGr/1ld7ZtQ4i/laWI/iiVt+GL07stdvufle4KfywyvloqqpIiJscXNCrKxA==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.3.tgz",
"integrity": "sha512-i7fV8u843vb7nIGpcwdCsG3WjfBONeytRHK1mQL9d5KQAvFeAK2rRisDHicreYpoQ0MXocUDEqunKHTeXdvibg==",
"dev": true,
"requires": {
"@types/node": "*"

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

@ -36,107 +36,111 @@ function openQuickImportDialog(type: string) {
</script>
<template>
<div class="nc-container flex flex-col">
<div>
<a-tabs v-model:activeKey="activeTabIndex" type="editable-card" @edit="closeTab">
<a-tab-pane v-for="(tab, i) in tabs" :key="i" :tab="tab.title" />
<div class="flex w-full h-full">
<div class="nc-container flex flex-col">
<div>
<a-tabs v-model:activeKey="activeTabIndex" type="editable-card" @edit="closeTab">
<a-tab-pane v-for="(tab, i) in tabs" :key="i" :tab="tab.title" />
<template #leftExtra>
<a-menu v-model:selectedKeys="currentMenu" class="border-0" mode="horizontal">
<a-sub-menu key="addORImport">
<template #title>
<div class="text-sm flex items-center gap-2">
<MdiPlusIcon />
Add / Import
</div>
</template>
<a-menu-item-group v-if="isUIAllowed('addTable')">
<a-menu-item key="add-new-table" v-t="['a:actions:create-table']" @click="tableCreateDialog = true">
<span class="flex items-center gap-2">
<MdiTableIcon class="text-primary" />
<!-- Add new table -->
{{ $t('tooltip.addTable') }}
</span>
</a-menu-item>
</a-menu-item-group>
<a-menu-item-group title="QUICK IMPORT FROM">
<a-menu-item
v-if="isUIAllowed('airtableImport')"
key="quick-import-airtable"
v-t="['a:actions:import-airtable']"
@click="airtableImportDialog = true"
>
<span class="flex items-center gap-2">
<MdiAirTableIcon class="text-primary" />
<!-- TODO: i18n -->
Airtable
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('csvImport')"
key="quick-import-csv"
v-t="['a:actions:import-csv']"
@click="openQuickImportDialog('csv')"
>
<span class="flex items-center gap-2">
<MdiCsvIcon class="text-primary" />
<!-- TODO: i18n -->
CSV file
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('jsonImport')"
key="quick-import-json"
v-t="['a:actions:import-json']"
@click="openQuickImportDialog('json')"
>
<span class="flex items-center gap-2">
<MdiJSONIcon class="text-primary" />
<!-- TODO: i18n -->
JSON file
</span>
</a-menu-item>
<template #leftExtra>
<a-menu v-model:selectedKeys="currentMenu" class="border-0" mode="horizontal">
<a-sub-menu key="addORImport">
<template #title>
<div class="text-sm flex items-center gap-2">
<MdiPlusIcon />
Add / Import
</div>
</template>
<a-menu-item-group v-if="isUIAllowed('addTable')">
<a-menu-item key="add-new-table" v-t="['a:actions:create-table']" @click="tableCreateDialog = true">
<span class="flex items-center gap-2">
<MdiTableIcon class="text-primary" />
<!-- Add new table -->
{{ $t('tooltip.addTable') }}
</span>
</a-menu-item>
</a-menu-item-group>
<a-menu-item-group title="QUICK IMPORT FROM">
<a-menu-item
v-if="isUIAllowed('airtableImport')"
key="quick-import-airtable"
v-t="['a:actions:import-airtable']"
@click="airtableImportDialog = true"
>
<span class="flex items-center gap-2">
<MdiAirTableIcon class="text-primary" />
<!-- TODO: i18n -->
Airtable
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('csvImport')"
key="quick-import-csv"
v-t="['a:actions:import-csv']"
@click="openQuickImportDialog('csv')"
>
<span class="flex items-center gap-2">
<MdiCsvIcon class="text-primary" />
<!-- TODO: i18n -->
CSV file
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('jsonImport')"
key="quick-import-json"
v-t="['a:actions:import-json']"
@click="openQuickImportDialog('json')"
>
<span class="flex items-center gap-2">
<MdiJSONIcon class="text-primary" />
<!-- TODO: i18n -->
JSON file
</span>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport')"
key="quick-import-excel"
v-t="['a:actions:import-excel']"
@click="openQuickImportDialog('excel')"
>
<span class="flex items-center gap-2">
<MdiExcelIcon class="text-primary" />
<!-- TODO: i18n -->
Microsoft Excel
</span>
</a-menu-item>
</a-menu-item-group>
<a-divider class="ma-0 mb-2" />
<a-menu-item
v-if="isUIAllowed('excelImport')"
key="quick-import-excel"
v-t="['a:actions:import-excel']"
@click="openQuickImportDialog('excel')"
v-if="isUIAllowed('importRequest')"
key="add-new-table"
v-t="['e:datasource:import-request']"
class="ma-0 mt-3"
>
<span class="flex items-center gap-2">
<MdiExcelIcon class="text-primary" />
<!-- TODO: i18n -->
Microsoft Excel
</span>
<a href="https://github.com/nocodb/nocodb/issues/2052" target="_blank" class="prose-sm pa-0">
<span class="flex items-center gap-2">
<MdiRequestDataSourceIcon class="text-primary" />
<!-- TODO: i18n -->
Request a data source you need?
</span>
</a>
</a-menu-item>
</a-menu-item-group>
<a-divider class="ma-0 mb-2" />
<a-menu-item
v-if="isUIAllowed('importRequest')"
key="add-new-table"
v-t="['e:datasource:import-request']"
class="ma-0 mt-3"
>
<a href="https://github.com/nocodb/nocodb/issues/2052" target="_blank" class="prose-sm pa-0">
<span class="flex items-center gap-2">
<MdiRequestDataSourceIcon class="text-primary" />
<!-- TODO: i18n -->
Request a data source you need?
</span>
</a>
</a-menu-item>
</a-sub-menu>
</a-menu>
</template>
</a-tabs>
</div>
</a-sub-menu>
</a-menu>
</template>
</a-tabs>
</div>
<div class="flex-1 min-h-0">
<NuxtPage />
</div>
<div class="flex-1 min-h-0">
<NuxtPage />
<DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" />
<DlgQuickImport v-if="quickImportDialog" v-model="quickImportDialog" :import-type="importType" />
<DlgAirtableImport v-if="airtableImportDialog" v-model="airtableImportDialog" />
</div>
<DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" />
<DlgQuickImport v-if="quickImportDialog" v-model="quickImportDialog" :import-type="importType" />
<DlgAirtableImport v-if="airtableImportDialog" v-model="airtableImportDialog" />
<div id="sidebar-right" class="h-full" />
</div>
</template>
@ -144,6 +148,7 @@ function openQuickImportDialog(type: string) {
.nc-container {
height: calc(100vh - var(--header-height) - 8px);
@apply overflow-hidden;
flex: 1 1 100%;
}
:deep(.ant-tabs-nav) {

Loading…
Cancel
Save