Browse Source

chore(gui-v2): rebase and resolve conflicts

pull/3087/head
Wing-Kam Wong 2 years ago
parent
commit
adc3de7fbc
  1. 5
      packages/nc-gui-v2/components.d.ts
  2. 12
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  3. 2
      packages/nc-gui-v2/components/general/MiniSidebar.vue
  4. 2
      packages/nc-gui-v2/components/smartsheet-header/VirtualCell.vue
  5. 69
      packages/nc-gui-v2/components/smartsheet/Gallery.vue
  6. 13
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  7. 17
      packages/nc-gui-v2/components/smartsheet/Pagination.vue
  8. 10
      packages/nc-gui-v2/components/smartsheet/Toolbar.vue
  9. 2
      packages/nc-gui-v2/components/smartsheet/expanded-form/Header.vue
  10. 2
      packages/nc-gui-v2/components/smartsheet/expanded-form/index.vue
  11. 2
      packages/nc-gui-v2/components/tabs/auth/user-management/ShareBase.vue
  12. 3
      packages/nc-gui-v2/composables/useApi/interceptors.ts
  13. 58
      packages/nc-gui-v2/composables/useMetas.ts
  14. 32
      packages/nc-gui-v2/composables/useProject.ts
  15. 18
      packages/nc-gui-v2/composables/useTabs.ts
  16. 1
      packages/nc-gui-v2/context/index.ts
  17. 4
      packages/nc-gui-v2/layouts/base.vue
  18. 3
      packages/nc-gui-v2/middleware/auth.global.ts
  19. 26
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  20. 7
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index.vue
  21. 0
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue
  22. 0
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/auth.vue
  23. 0
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/index.vue

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

@ -84,12 +84,14 @@ declare module '@vue/runtime-core' {
MdiAccountIcon: typeof import('~icons/mdi/account-icon')['default'] MdiAccountIcon: typeof import('~icons/mdi/account-icon')['default']
MdiAccountOutline: typeof import('~icons/mdi/account-outline')['default'] MdiAccountOutline: typeof import('~icons/mdi/account-outline')['default']
MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default'] MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default']
MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default']
MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default'] MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default']
MdiApi: typeof import('~icons/mdi/api')['default'] MdiApi: typeof import('~icons/mdi/api')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default'] MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default'] MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiAt: typeof import('~icons/mdi/at')['default'] MdiAt: typeof import('~icons/mdi/at')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default'] MdiCalculator: typeof import('~icons/mdi/calculator')['default']
MdiCalendarMonth: typeof import('~icons/mdi/calendar-month')['default']
MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default'] MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default']
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default'] MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default'] MdiChat: typeof import('~icons/mdi/chat')['default']
@ -116,6 +118,7 @@ declare module '@vue/runtime-core' {
MdiFolder: typeof import('~icons/mdi/folder')['default'] MdiFolder: typeof import('~icons/mdi/folder')['default']
MdiFunction: typeof import('~icons/mdi/function')['default'] MdiFunction: typeof import('~icons/mdi/function')['default']
MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default'] MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default']
MdiGithub: typeof import('~icons/mdi/github')['default']
MdiHeart: typeof import('~icons/mdi/heart')['default'] MdiHeart: typeof import('~icons/mdi/heart')['default']
MdiHook: typeof import('~icons/mdi/hook')['default'] MdiHook: typeof import('~icons/mdi/hook')['default']
MdiInformation: typeof import('~icons/mdi/information')['default'] MdiInformation: typeof import('~icons/mdi/information')['default']
@ -139,6 +142,7 @@ declare module '@vue/runtime-core' {
MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default'] MdiShieldLockOutline: typeof import('~icons/mdi/shield-lock-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default'] MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiStar: typeof import('~icons/mdi/star')['default'] MdiStar: typeof import('~icons/mdi/star')['default']
MdiStarOutline: typeof import('~icons/mdi/star-outline')['default']
MdiStore: typeof import('~icons/mdi/store')['default'] MdiStore: typeof import('~icons/mdi/store')['default']
MdiTable: typeof import('~icons/mdi/table')['default'] MdiTable: typeof import('~icons/mdi/table')['default']
MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default'] MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default']
@ -146,6 +150,7 @@ declare module '@vue/runtime-core' {
MdiText: typeof import('~icons/mdi/text')['default'] MdiText: typeof import('~icons/mdi/text')['default']
MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default'] MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default']
MdiTrashCan: typeof import('~icons/mdi/trash-can')['default'] MdiTrashCan: typeof import('~icons/mdi/trash-can')['default']
MdiTwitter: typeof import('~icons/mdi/twitter')['default']
MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default'] MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default']
MdiXml: typeof import('~icons/mdi/xml')['default'] MdiXml: typeof import('~icons/mdi/xml')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']

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

@ -2,7 +2,7 @@
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import { Empty } from 'ant-design-vue' import { Empty } from 'ant-design-vue'
import { useNuxtApp, useRoute } from '#app' import { useNuxtApp } from '#app'
import { computed, useProject, useTable, useTabs, watchEffect } from '#imports' import { computed, useProject, useTable, useTabs, watchEffect } from '#imports'
import { TabType } from '~/composables' import { TabType } from '~/composables'
import MdiView from '~icons/mdi/eye-circle-outline' import MdiView from '~icons/mdi/eye-circle-outline'
@ -14,9 +14,7 @@ const { addTab } = useTabs()
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const route = useRoute() const { tables, loadTables, isSharedBase } = useProject()
const { tables, loadTables } = useProject(route.params.projectId as string)
const { activeTab } = useTabs() const { activeTab } = useTabs()
@ -152,7 +150,11 @@ const activeTable = computed(() => {
</div> </div>
<a-dropdown :trigger="['contextmenu']"> <a-dropdown :trigger="['contextmenu']">
<div class="p-2 flex-1 overflow-y-auto flex flex-column scrollbar-thin-dull" style="direction: rtl"> <div
class="p-2 flex-1 overflow-y-auto flex flex-column scrollbar-thin-dull"
:class="{ 'mb-[20px]': isSharedBase }"
style="direction: rtl"
>
<div <div
style="direction: ltr" style="direction: ltr"
class="py-1 px-3 flex w-full align-center gap-1 cursor-pointer" class="py-1 px-3 flex w-full align-center gap-1 cursor-pointer"

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

@ -106,7 +106,7 @@ const logout = () => {
<div <div
:class="[route.name.includes('nc-projectId') ? 'active' : 'pointer-events-none !text-gray-400']" :class="[route.name.includes('nc-projectId') ? 'active' : 'pointer-events-none !text-gray-400']"
class="nc-mini-sidebar-item" class="nc-mini-sidebar-item"
@click="navigateTo(`/nc/${route.params.projectId}`)" @click="navigateTo(`/${route.params.projectType}/${route.params.projectId}`)"
> >
<MdiDatabase class="cursor-pointer transform hover:scale-105 text-2xl" /> <MdiDatabase class="cursor-pointer transform hover:scale-105 text-2xl" />
</div> </div>

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

@ -5,7 +5,7 @@ import type { Ref } from 'vue'
import { ColumnInj, IsFormInj, MetaInj } from '~/context' import { ColumnInj, IsFormInj, MetaInj } from '~/context'
import { provide, toRef, useMetas, useProvideColumnCreateStore } from '#imports' import { provide, toRef, useMetas, useProvideColumnCreateStore } from '#imports'
const props = defineProps<{ column: ColumnType & { meta: any }; hideMenu?: boolean; required: boolean }>() const props = defineProps<{ column: ColumnType & { meta: any }; hideMenu?: boolean; required?: boolean }>()
const column = toRef(props, 'column') const column = toRef(props, 'column')

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

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { isVirtualCol } from 'nocodb-sdk' import { isVirtualCol } from 'nocodb-sdk'
import { inject, provide, useViewData } from '#imports' import { inject, provide, useViewData } from '#imports'
import Row from '~/components/smartsheet/Row.vue'
import { ActiveViewInj, ChangePageInj, FieldsInj, IsFormInj, IsGridInj, MetaInj, PaginationDataInj, ReadonlyInj } from '~/context' import { ActiveViewInj, ChangePageInj, FieldsInj, IsFormInj, IsGridInj, MetaInj, PaginationDataInj, ReadonlyInj } from '~/context'
import ImageIcon from '~icons/mdi/file-image-box' import ImageIcon from '~icons/mdi/file-image-box'
@ -50,45 +51,46 @@ const attachments = (record: any): Array<Attachment> => {
} }
</script> </script>
<!-- TODO: Fix scrolling -->
<template> <template>
<div class="flex flex-col h-full min-h-0 w-full"> <div class="flex flex-col h-full w-full">
<div class="nc-gallery-container min-h-0 flex-1 grid grid-cols-4 gap-4 my-4 px-3"> <div class="nc-gallery-container min-h-0 flex-1 grid grid-cols-4 gap-4 my-4 px-3 overflow-auto">
<div v-for="(record, recordIndex) in data" :key="recordIndex" class="flex flex-col"> <div v-for="(record, recordIndex) in data" :key="recordIndex" class="flex flex-col">
<a-card hoverable class="!rounded-lg h-full"> <Row :row="record">
<template #cover> <a-card hoverable class="!rounded-lg h-full">
<a-carousel v-if="attachments(record).length !== 0" autoplay> <template #cover>
<img <a-carousel v-if="attachments(record).length !== 0" autoplay>
v-for="(attachment, index) in attachments(record)" <img
:key="index" v-for="(attachment, index) in attachments(record)"
class="h-52 rounded-t-lg" :key="index"
:src="attachment.url" class="h-52 rounded-t-lg"
/> :src="attachment.url"
</a-carousel> />
<ImageIcon v-else class="w-full h-48 my-4 text-cool-gray-200" /> </a-carousel>
</template> <ImageIcon v-else class="w-full h-48 my-4 text-cool-gray-200" />
</template>
<div <div
v-for="(col, colIndex) in fields" v-for="(col, colIndex) in fields"
:key="colIndex" :key="colIndex"
class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full" class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full"
> >
<div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5"> <div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5">
<div class="w-full text-gray-600"> <div class="w-full text-gray-600">
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" /> <SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<SmartsheetHeaderCell v-else :column="col" :hide-menu="true" /> <SmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
</div>
</div> </div>
</div>
<div class="flex flex-row w-full pb-3 pt-2 pl-2 items-center justify-start"> <div class="flex flex-row w-full pb-3 pt-2 pl-2 items-center justify-start">
<div v-if="isRowEmpty(record, col)" class="h-3 bg-gray-200 px-5 rounded-lg"></div> <div v-if="isRowEmpty(record, col)" class="h-3 bg-gray-200 px-5 rounded-lg"></div>
<template v-else> <template v-else>
<SmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="record.row[col.title]" :column="col" /> <SmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="record.row[col.title]" :column="col" />
<SmartsheetCell v-else v-model="record.row[col.title]" :column="col" :edit-enabled="false" /> <SmartsheetCell v-else v-model="record.row[col.title]" :column="col" :edit-enabled="false" />
</template> </template>
</div>
</div> </div>
</div> </a-card>
</a-card> </Row>
</div> </div>
</div> </div>
<SmartsheetPagination /> <SmartsheetPagination />
@ -97,7 +99,6 @@ const attachments = (record: any): Array<Attachment> => {
<style scoped> <style scoped>
.nc-gallery-container { .nc-gallery-container {
height: calc(100vh - 250px);
overflow: auto; overflow: auto;
} }
</style> </style>

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

@ -473,10 +473,7 @@ const expandForm = (row: Row, state: Record<string, any>) => {
<style scoped lang="scss"> <style scoped lang="scss">
.nc-grid-wrapper { .nc-grid-wrapper {
width: 100%; @apply h-full w-full overflow-auto;
// todo : proper height calculation
height: calc(100vh - 215px);
overflow: auto;
td, td,
th { th {
@ -493,10 +490,7 @@ const expandForm = (row: Row, state: Record<string, any>) => {
table, table,
td, td,
th { th {
border-right: 1px solid #f0f0f0 !important; @apply !border-1;
border-left: 1px solid #f0f0f0 !important;
border-bottom: 1px solid #f0f0f0 !important;
border-top: 1px solid #f0f0f0 !important;
border-collapse: collapse; border-collapse: collapse;
} }
@ -522,8 +516,7 @@ const expandForm = (row: Row, state: Record<string, any>) => {
} }
td.active::before { td.active::before {
background: #0040bc; @apply bg-primary/5;
opacity: 0.1;
} }
} }

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

@ -34,21 +34,12 @@ const page = computed({
:show-size-changer="false" :show-size-changer="false"
/> />
<div v-else class="mx-auto d-flex align-center mt-n1" style="max-width: 250px"> <div v-else class="mx-auto d-flex align-center mt-n1" style="max-width: 250px">
<span class="caption" style="white-space: nowrap"> Change page:</span> <span class="text-xs" style="white-space: nowrap"> Change page:</span>
<v-text-field <a-input :value="page" size="small" class="ml-1 !text-xs" type="number" @keydown.enter="changePage(page)">
:value="page" <template #suffix>
class="ml-1 caption"
:full-width="false"
outlined
dense
hide-details
type="number"
@keydown.enter="changePage(page)"
>
<template #append>
<MdiKeyboardIcon class="mt-1" @click="changePage(page)" /> <MdiKeyboardIcon class="mt-1" @click="changePage(page)" />
</template> </template>
</v-text-field> </a-input>
</div> </div>
<div class="flex-1" /> <div class="flex-1" />

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

@ -1,22 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { useSmartsheetStoreOrThrow } from '~/composables' import { useSmartsheetStoreOrThrow } from '~/composables'
const { isGrid, isForm } = useSmartsheetStoreOrThrow() const { isGrid, isForm, isGallery } = useSmartsheetStoreOrThrow()
</script> </script>
<template> <template>
<div class="nc-table-toolbar w-full py-1 flex gap-1 items-center h-[48px] px-2 border-b" style="z-index: 7"> <div class="nc-table-toolbar w-full py-1 flex gap-1 items-center h-[48px] px-2 border-b" style="z-index: 7">
<SmartsheetToolbarFieldsMenu v-if="isGrid" :show-system-fields="false" class="ml-1" /> <SmartsheetToolbarFieldsMenu v-if="isGrid || isGallery" :show-system-fields="false" class="ml-1" />
<SmartsheetToolbarColumnFilterMenu v-if="isGrid" /> <SmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery" />
<SmartsheetToolbarSortListMenu v-if="isGrid" /> <SmartsheetToolbarSortListMenu v-if="isGrid || isGallery" />
<SmartsheetToolbarShareView v-if="isForm || isGrid" /> <SmartsheetToolbarShareView v-if="isForm || isGrid" />
<SmartsheetToolbarMoreActions v-if="isGrid" /> <SmartsheetToolbarMoreActions v-if="isGrid" />
<div class="flex-1" /> <div class="flex-1" />
<SmartsheetToolbarSearchData v-if="isGrid" class="shrink mr-2" /> <SmartsheetToolbarSearchData v-if="isGrid || isGallery" class="shrink mr-2" />
</div> </div>
</template> </template>

2
packages/nc-gui-v2/components/smartsheet/expanded-form/Header.vue

@ -36,7 +36,7 @@ const iconColor = '#1890ff'
<template> <template>
<div class="flex p-2 align-center gap-2 p-4"> <div class="flex p-2 align-center gap-2 p-4">
<h5 class="text-lg font-weight-medium flex align-center gap-1 mb-0"> <h5 class="text-lg font-weight-medium flex align-center gap-1 mb-0 min-w-0 overflow-x-hidden truncate">
<mdi-table-arrow-right :style="{ color: iconColor }" /> <mdi-table-arrow-right :style="{ color: iconColor }" />
<template v-if="meta"> <template v-if="meta">

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

@ -82,7 +82,7 @@ const isExpanded = useVModel(props, 'modelValue', emits)
<template> <template>
<a-modal v-model:visible="isExpanded" :footer="null" width="min(90vw,1000px)" :body-style="{ padding: 0 }" :closable="false"> <a-modal v-model:visible="isExpanded" :footer="null" width="min(90vw,1000px)" :body-style="{ padding: 0 }" :closable="false">
<Header @cancel="isExpanded = false" /> <Header @cancel="isExpanded = false" />
<a-card class="!bg-gray-100"> <a-card class="!bg-gray-100 min-h-[70vh]">
<div class="flex h-full nc-form-wrapper items-stretch"> <div class="flex h-full nc-form-wrapper items-stretch">
<div class="flex-grow overflow-auto scrollbar-thin-primary"> <div class="flex-grow overflow-auto scrollbar-thin-primary">
<div class="w-[500px] mx-auto"> <div class="w-[500px] mx-auto">

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

@ -26,7 +26,7 @@ const { project } = useProject()
const { copy } = useClipboard() const { copy } = useClipboard()
const url = $computed(() => (base && base.uuid ? `${dashboardUrl}/nc/base/${base.uuid}` : null)) const url = $computed(() => (base && base.uuid ? `${dashboardUrl}/base/${base.uuid}` : null))
const loadBase = async () => { const loadBase = async () => {
try { try {

3
packages/nc-gui-v2/composables/useApi/interceptors.ts

@ -18,7 +18,8 @@ export function addAxiosInterceptors(api: Api<any>) {
} }
if (!config.url?.endsWith('/user/me') && !config.url?.endsWith('/admin/roles')) { if (!config.url?.endsWith('/user/me') && !config.url?.endsWith('/admin/roles')) {
if (route && route.params && route.params.shared_base_id) config.headers['xc-shared-base-id'] = route.params.shared_base_id if (route && route.params && route.params.projectType === 'base')
config.headers['xc-shared-base-id'] = route.params.projectId
} }
return config return config

58
packages/nc-gui-v2/composables/useMetas.ts

@ -1,6 +1,8 @@
import { message } from 'ant-design-vue'
import type { WatchStopHandle } from 'vue' import type { WatchStopHandle } from 'vue'
import type { TableInfoType, TableType } from 'nocodb-sdk' import type { TableInfoType, TableType } from 'nocodb-sdk'
import { useProject } from './useProject' import { useProject } from './useProject'
import { extractSdkResponseErrorMsg } from '~/utils'
import { useNuxtApp, useState } from '#app' import { useNuxtApp, useState } from '#app'
export function useMetas() { export function useMetas() {
@ -11,53 +13,63 @@ export function useMetas() {
const loadingState = useState<Record<string, boolean>>('metas-loading-state', () => ({})) const loadingState = useState<Record<string, boolean>>('metas-loading-state', () => ({}))
const getMeta = async (tableIdOrTitle: string, force = false): Promise<TableType | TableInfoType | null> => { const getMeta = async (tableIdOrTitle: string, force = false): Promise<TableType | TableInfoType | null> => {
if (!force && metas.value[tableIdOrTitle]) return metas.value[tableIdOrTitle] if (!tableIdOrTitle) return null
const modelId = (tables.value.find((t) => t.title === tableIdOrTitle || t.id === tableIdOrTitle) || {}).id
if (!modelId) {
console.warn(`Table '${tableIdOrTitle}' is not found in the table list`)
return null
}
/** wait until loading is finished if requesting same meta */ /** wait until loading is finished if requesting same meta */
if (!force) { if (!force && loadingState.value[tableIdOrTitle]) {
await new Promise((resolve) => { await new Promise((resolve) => {
let unwatch: WatchStopHandle let unwatch: WatchStopHandle
// set maximum 20sec timeout to wait loading meta
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
unwatch?.() unwatch?.()
clearTimeout(timeout) clearTimeout(timeout)
resolve(null) resolve(null)
}, 20000) }, 10000)
// watch for loading state change
unwatch = watch( unwatch = watch(
() => loadingState.value[modelId], () => !!loadingState.value[tableIdOrTitle],
(isLoading) => { (isLoading) => {
if (!isLoading) { if (!isLoading) {
clearTimeout(timeout) clearTimeout(timeout)
resolve(null)
unwatch?.() unwatch?.()
resolve(null)
} }
}, },
{ immediate: true }, { immediate: true },
) )
}) })
if (metas.value[modelId]) return metas.value[modelId] if (metas.value[tableIdOrTitle]) {
return metas.value[tableIdOrTitle]
}
} }
loadingState.value[tableIdOrTitle] = true
try {
if (!force && metas.value[tableIdOrTitle]) {
return metas.value[tableIdOrTitle]
}
loadingState.value[modelId] = true const modelId = tableIdOrTitle.startsWith('md_') ? tableIdOrTitle : tables.value.find((t) => t.title === tableIdOrTitle)?.id
const model = await $api.dbTable.read(modelId) if (!modelId) {
metas.value = { console.warn(`Table '${tableIdOrTitle}' is not found in the table list`)
...metas.value, return null
[model.id!]: model, }
[model.title]: model,
}
loadingState.value[modelId] = false const model = await $api.dbTable.read(modelId)
metas.value = {
...metas.value,
[model.id!]: model,
[model.title]: model,
}
return model return model
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
} finally {
delete loadingState.value[tableIdOrTitle]
}
return null
} }
const clearAllMeta = () => { const clearAllMeta = () => {

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

@ -1,17 +1,20 @@
import { SqlUiFactory } from 'nocodb-sdk' import { SqlUiFactory } from 'nocodb-sdk'
import type { OracleUi, ProjectType, TableType } from 'nocodb-sdk' import type { OracleUi, ProjectType, TableType } from 'nocodb-sdk'
import type { MaybeRef } from '@vueuse/core' import type { MaybeRef } from '@vueuse/core'
import { useNuxtApp, useState } from '#app' import { useNuxtApp, useRoute, useState } from '#app'
import { USER_PROJECT_ROLES } from '~/lib' import { USER_PROJECT_ROLES } from '~/lib'
export function useProject(projectId?: MaybeRef<string>) { export function useProject(projectId?: MaybeRef<string>) {
const projectRoles = useState<Record<string, boolean>>(USER_PROJECT_ROLES, () => ({})) const projectRoles = useState<Record<string, boolean>>(USER_PROJECT_ROLES, () => ({}))
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
let _projectId = $ref('')
const _projectId = $computed(() => unref(projectId))
const project = useState<ProjectType>('project') const project = useState<ProjectType>('project')
const tables = useState<TableType[]>('tables', () => [] as TableType[]) const tables = useState<TableType[]>('tables', () => [] as TableType[])
const route = useRoute()
// todo: refactor path param name and variable name
const projectType = $computed(() => route.params.projectType as string)
async function loadProjectRoles() { async function loadProjectRoles() {
projectRoles.value = {} projectRoles.value = {}
@ -29,18 +32,20 @@ export function useProject(projectId?: MaybeRef<string>) {
} }
} }
async function loadProject(id: string) { async function loadProject() {
project.value = await $api.project.read(id) if (unref(projectId)) {
_projectId = unref(projectId)!
} else if (projectType === 'base') {
const baseData = await $api.public.sharedBaseGet(route.params.projectId as string)
_projectId = baseData.project_id!
} else {
_projectId = route.params.projectId as string
}
project.value = await $api.project.read(_projectId!)
await loadProjectRoles() await loadProjectRoles()
await loadTables()
} }
watchEffect(async () => {
if (_projectId) {
await loadProject(_projectId)
await loadTables()
}
})
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '') const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '')
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType)) const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType))
@ -48,6 +53,7 @@ export function useProject(projectId?: MaybeRef<string>) {
const sqlUi = computed( const sqlUi = computed(
() => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>, () => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>,
) )
const isSharedBase = computed(() => projectType === 'base')
return { project, tables, loadProjectRoles, loadProject, loadTables, isMysql, isPg, sqlUi } return { project, tables, loadProjectRoles, loadProject, loadTables, isMysql, isPg, sqlUi, isSharedBase }
} }

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

@ -29,9 +29,11 @@ export function useTabs() {
const router = useRouter() const router = useRouter()
const { tables } = useProject() const { tables } = useProject()
const projectType = $computed(() => route.params.projectType as string)
const activeTabIndex: WritableComputedRef<number> = computed({ const activeTabIndex: WritableComputedRef<number> = computed({
get() { get() {
if ((route.name as string)?.startsWith('nc-projectId-index-index-type-title-viewTitle') && tables.value?.length) { if ((route.name as string)?.startsWith('projectType-projectId-index-index-type-title-viewTitle') && tables.value?.length) {
const tab: Partial<TabItem> = { type: route.params.type as TabType, title: route.params.title as string } const tab: Partial<TabItem> = { type: route.params.type as TabType, title: route.params.title as string }
const id = tables.value?.find((t) => t.title === tab.title)?.id const id = tables.value?.find((t) => t.title === tab.title)?.id
@ -56,7 +58,7 @@ export function useTabs() {
}, },
set(index: number) { set(index: number) {
if (index === -1) { if (index === -1) {
navigateTo(`/nc/${route.params.projectId}`) navigateTo(`/${projectType}/${route.params.projectId}`)
} else { } else {
const tab = tabs.value[index] const tab = tabs.value[index]
@ -91,7 +93,7 @@ export function useTabs() {
let newTabIndex = index - 1 let newTabIndex = index - 1
if (newTabIndex < 0 && tabs.value?.length > 1) newTabIndex = index + 1 if (newTabIndex < 0 && tabs.value?.length > 1) newTabIndex = index + 1
if (newTabIndex === -1) { if (newTabIndex === -1) {
await navigateTo(`/nc/${route.params.projectId}`) await navigateTo(`/${projectType}/${route.params.projectId}`)
} else { } else {
await navigateToTab(tabs.value?.[newTabIndex]) await navigateToTab(tabs.value?.[newTabIndex])
} }
@ -102,11 +104,15 @@ export function useTabs() {
function navigateToTab(tab: TabItem) { function navigateToTab(tab: TabItem) {
switch (tab.type) { switch (tab.type) {
case TabType.TABLE: case TabType.TABLE:
return navigateTo(`/nc/${route.params.projectId}/table/${tab?.title}${tab.viewTitle ? `/${tab.viewTitle}` : ''}`) return navigateTo(
`/${projectType}/${route.params.projectId}/table/${tab?.title}${tab.viewTitle ? `/${tab.viewTitle}` : ''}`,
)
case TabType.VIEW: case TabType.VIEW:
return navigateTo(`/nc/${route.params.projectId}/view/${tab?.title}${tab.viewTitle ? `/${tab.viewTitle}` : ''}`) return navigateTo(
`/${projectType}/${route.params.projectId}/view/${tab?.title}${tab.viewTitle ? `/${tab.viewTitle}` : ''}`,
)
case TabType.AUTH: case TabType.AUTH:
return navigateTo(`/nc/${route.params.projectId}/auth`) return navigateTo(`/${projectType}/${route.params.projectId}/auth`)
} }
} }

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

@ -5,7 +5,6 @@ import type { useViewData } from '#imports'
import type { Row } from '~/composables' import type { Row } from '~/composables'
import type { TabItem } from '~/composables/useTabs' import type { TabItem } from '~/composables/useTabs'
export const EditEnabledInj: InjectionKey<boolean> = Symbol('edit-enabled')
export const ActiveCellInj: InjectionKey<Ref<boolean>> = Symbol('active-cell') export const ActiveCellInj: InjectionKey<Ref<boolean>> = Symbol('active-cell')
export const RowInj: InjectionKey<Ref<Row>> = Symbol('row') export const RowInj: InjectionKey<Ref<Row>> = Symbol('row')
export const ColumnInj: InjectionKey<Ref<ColumnType & { meta: any }>> = Symbol('column-injection') export const ColumnInj: InjectionKey<Ref<ColumnType & { meta: any }>> = Symbol('column-injection')

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

@ -4,6 +4,8 @@ import { computed, useGlobal, useRoute } from '#imports'
const { signOut, signedIn, isLoading, user } = useGlobal() const { signOut, signedIn, isLoading, user } = useGlobal()
const { isSharedBase } = useProject()
const route = useRoute() const route = useRoute()
const email = computed(() => user.value?.email ?? '---') const email = computed(() => user.value?.email ?? '---')
@ -49,7 +51,7 @@ const logout = () => {
</div> </div>
</a-tooltip> </a-tooltip>
<template v-if="signedIn"> <template v-if="signedIn && !isSharedBase">
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<MdiDotsVertical class="md:text-xl cursor-pointer nc-user-menu" @click.prevent /> <MdiDotsVertical class="md:text-xl cursor-pointer nc-user-menu" @click.prevent />

3
packages/nc-gui-v2/middleware/auth.global.ts

@ -23,6 +23,9 @@ import { useGlobal } from '#imports'
export default defineNuxtRouteMiddleware((to, from) => { export default defineNuxtRouteMiddleware((to, from) => {
const state = useGlobal() const state = useGlobal()
/** if shred base allow without validating */
if (to.params?.projectType === 'base') return
/** if auth is required or unspecified (same as required) and user is not signed in, redirect to signin page */ /** if auth is required or unspecified (same as required) and user is not signed in, redirect to signin page */
if ((to.meta.requiresAuth || typeof to.meta.requiresAuth === 'undefined') && !state.signedIn.value) { if ((to.meta.requiresAuth || typeof to.meta.requiresAuth === 'undefined') && !state.signedIn.value) {
return navigateTo('/signin') return navigateTo('/signin')

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

@ -5,7 +5,7 @@ import { openLink } from '~/utils'
const route = useRoute() const route = useRoute()
const { project, loadProject, loadTables } = useProject(route.params.projectId as string) const { project, loadProject, loadTables, isSharedBase } = useProject()
const { addTab, clearTabs } = useTabs() const { addTab, clearTabs } = useTabs()
@ -39,7 +39,7 @@ function toggleDialog(value?: boolean, key?: string) {
openDialogKey.value = key openDialogKey.value = key
} }
await loadProject(route.params.projectId as string) await loadProject()
await loadTables() await loadTables()
</script> </script>
@ -58,14 +58,31 @@ await loadTables()
> >
<div style="height: var(--header-height)" class="flex items-center !bg-primary text-white px-1 pl-5 gap-2"> <div style="height: var(--header-height)" class="flex items-center !bg-primary text-white px-1 pl-5 gap-2">
<div <div
v-if="isOpen" v-if="isOpen && !isSharedBase"
class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105" class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105"
@click="navigateTo('/')" @click="navigateTo('/')"
> >
<img alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" /> <img alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" />
</div> </div>
<a
v-if="isOpen && isSharedBase"
class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105"
href="https://github.com/nocodb/nocodb"
target="_blank"
>
<img alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" />
</a>
<div v-if="isSharedBase">
<template v-if="isOpen">
<div class="text-xl font-semibold truncate">{{ project.title }}</div>
</template>
<template v-else>
<MdiFolder class="text-primary cursor-pointer transform hover:scale-105 text-2xl" />
</template>
</div>
<a-dropdown :trigger="['click']" placement="bottom"> <a-dropdown v-else :trigger="['click']" placement="bottom">
<div <div
:style="{ width: isOpen ? 'calc(100% - 40px) pr-2' : '100%' }" :style="{ width: isOpen ? 'calc(100% - 40px) pr-2' : '100%' }"
:class="[isOpen ? '' : 'justify-center']" :class="[isOpen ? '' : 'justify-center']"
@ -220,7 +237,6 @@ await loadTables()
</template> </template>
<dashboard-settings-modal v-model="dialogOpen" :open-key="openDialogKey" /> <dashboard-settings-modal v-model="dialogOpen" :open-key="openDialogKey" />
<NuxtPage /> <NuxtPage />
<GeneralPreviewAs float /> <GeneralPreviewAs float />

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

@ -144,8 +144,9 @@ const icon = (tab: TabItem) => {
</template> </template>
</a-tabs> </a-tabs>
</div> </div>
<div class="w-full min-h-[300px] grow">
<NuxtPage /> <NuxtPage />
</div>
</div> </div>
<DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" /> <DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" />
@ -156,7 +157,7 @@ const icon = (tab: TabItem) => {
<style scoped lang="scss"> <style scoped lang="scss">
.nc-container { .nc-container {
height: calc(100% - var(--header-height)); height: calc(100vh - var(--header-height));
flex: 1 1 100%; flex: 1 1 100%;
} }

0
packages/nc-gui-v2/pages/nc/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue → packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue

0
packages/nc-gui-v2/pages/nc/[projectId]/index/index/auth.vue → packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/auth.vue

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

Loading…
Cancel
Save