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