Files
nocodb/packages/nc-gui/components/dashboard/MiniSidebar.vue
2025-06-03 11:24:46 +00:00

291 lines
9.2 KiB
Vue

<script lang="ts" setup>
provide(IsMiniSidebarInj, ref(true))
const router = useRouter()
const route = router.currentRoute
const { appInfo, navigateToProject, isMobileMode } = useGlobal()
const { meta: metaKey, control } = useMagicKeys()
const { commandPalette } = useCommandPalette()
const workspaceStore = useWorkspace()
const { activeWorkspaceId, isWorkspaceSettingsPageOpened, isIntegrationsPageOpened, isWorkspacesLoading } =
storeToRefs(workspaceStore)
const { navigateToWorkspaceSettings, navigateToIntegrations: _navigateToIntegrations } = workspaceStore
const { basesList, showProjectList } = storeToRefs(useBases())
const { isSharedBase } = storeToRefs(useBase())
const { isUIAllowed } = useRoles()
const isProjectListOrHomePageOpen = computed(() => {
return (
route.value.name?.startsWith('index-typeOrId-baseId-') ||
route.value.name === 'index' ||
route.value.name === 'index-typeOrId'
)
})
const isProjectPageOpen = computed(() => {
return (
(route.value.name?.startsWith('index-typeOrId-baseId-') ||
route.value.name === 'index' ||
route.value.name === 'index-typeOrId') &&
showProjectList.value
)
})
const navigateToProjectPage = () => {
if (route.value.name?.startsWith('index-typeOrId-baseId-')) {
showProjectList.value = !showProjectList.value
return
}
navigateToProject({ workspaceId: isEeUI ? activeWorkspaceId.value : undefined, baseId: basesList.value?.[0]?.id })
}
const navigateToSettings = () => {
const cmdOrCtrl = isMac() ? metaKey.value : control.value
navigateToWorkspaceSettings('', cmdOrCtrl)
}
const navigateToIntegrations = () => {
const cmdOrCtrl = isMac() ? metaKey.value : control.value
_navigateToIntegrations('', cmdOrCtrl)
}
useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
const isBaseSearchInput = e.target instanceof HTMLInputElement && e.target.closest('.nc-base-search-input')
if (
!e.altKey ||
(!isBaseSearchInput &&
(isActiveInputElementExist(e) ||
cmdKActive() ||
isCmdJActive() ||
isNcDropdownOpen() ||
isActiveElementInsideExtension() ||
isDrawerOrModalExist() ||
isExpandedFormOpenExist()))
) {
return
}
switch (e.code) {
case 'KeyB': {
e.preventDefault()
navigateToProjectPage()
break
}
}
})
</script>
<template>
<div class="nc-mini-sidebar" data-testid="nc-mini-sidebar">
<div class="flex flex-col items-center">
<DashboardMiniSidebarItemWrapper size="small">
<div
class="min-h-9 sticky top-0 bg-[var(--mini-sidebar-bg-color)]"
:class="{
'pt-1.5 pb-2.5': isMobileMode,
}"
>
<GeneralLoader v-if="isWorkspacesLoading" size="large" />
<WorkspaceMenu v-else />
</div>
</DashboardMiniSidebarItemWrapper>
<DashboardMiniSidebarItemWrapper>
<NcTooltip placement="right" hide-on-click :arrow="false">
<template #title>
<div class="flex gap-1.5">
{{ $t('objects.projects') }}
<div class="px-1 text-bodySmBold text-white bg-gray-700 rounded">{{ renderAltOrOptlKey(true) }} B</div>
</div>
</template>
<div class="nc-mini-sidebar-btn-full-width" data-testid="nc-sidebar-project-btn" @click="navigateToProjectPage">
<div
class="nc-mini-sidebar-btn"
:class="{
'active': isProjectPageOpen,
'active-base': isProjectListOrHomePageOpen,
}"
>
<GeneralIcon icon="ncBaseOutline" class="h-4 w-4" />
</div>
</div>
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
<template v-if="!isMobileMode">
<DashboardMiniSidebarItemWrapper>
<NcTooltip placement="right" hide-on-click :arrow="false">
<template #title>
<div class="flex items-center gap-1">{{ renderCmdOrCtrlKey(true) }} K</div>
</template>
<div
v-e="['c:quick-actions']"
class="nc-mini-sidebar-btn-full-width"
data-testid="nc-sidebar-cmd-k-btn"
@click="commandPalette?.open()"
>
<div class="nc-mini-sidebar-btn">
<GeneralIcon icon="search" class="h-4 w-4" />
</div>
</div>
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
<div v-if="isUIAllowed('workspaceSettings')" class="px-2 w-full">
<NcDivider class="!my-0 !border-nc-border-gray-dark" />
</div>
<DashboardMiniSidebarItemWrapper v-if="isUIAllowed('workspaceSettings') || isUIAllowed('workspaceCollaborators')">
<NcTooltip :title="$t('title.teamAndSettings')" placement="right" hide-on-click :arrow="false">
<div
v-e="['c:team:settings']"
class="nc-mini-sidebar-btn-full-width"
data-testid="nc-sidebar-team-settings-btn"
@click="navigateToSettings"
>
<div
class="nc-mini-sidebar-btn"
:class="{
active: isWorkspaceSettingsPageOpened,
}"
>
<GeneralIcon icon="ncSettings" class="h-4 w-4" />
</div>
</div>
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
<DashboardMiniSidebarItemWrapper v-if="isUIAllowed('workspaceSettings')">
<NcTooltip :title="$t('general.integrations')" placement="right" hide-on-click :arrow="false">
<div
v-e="['c:integrations']"
class="nc-mini-sidebar-btn-full-width"
data-testid="nc-sidebar-integrations-btn"
@click="navigateToIntegrations"
>
<div
class="nc-mini-sidebar-btn"
:class="{
active: isIntegrationsPageOpened,
}"
>
<GeneralIcon icon="integration" class="h-4 w-4" />
</div>
</div>
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
<div class="px-2 w-full">
<NcDivider class="!my-0 !border-nc-border-gray-dark" />
</div>
<DashboardMiniSidebarItemWrapper>
<NcTooltip :title="$t('general.notification')" placement="right" hide-on-click :arrow="false">
<NotificationMenu />
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
</template>
</div>
<div class="flex flex-col items-center">
<DashboardMiniSidebarItemWrapper>
<NcTooltip :title="$t('general.help')" placement="right" hide-on-click :arrow="false">
<DashboardMiniSidebarHelp />
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
<template v-if="!isMobileMode">
<DashboardMiniSidebarItemWrapper>
<NcTooltip
v-if="appInfo.feedEnabled"
:title="`${$t('title.whatsNew')}!`"
placement="right"
hide-on-click
:arrow="false"
>
<DashboardSidebarFeed />
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
<div class="px-2 w-full">
<NcDivider class="!my-0 !border-nc-border-gray-dark" />
</div>
<DashboardMiniSidebarItemWrapper>
<NcTooltip v-if="!isSharedBase" :title="$t('labels.createNew')" placement="right" hide-on-click :arrow="false">
<DashboardMiniSidebarCreateNewActionMenu />
</NcTooltip>
</DashboardMiniSidebarItemWrapper>
</template>
<div v-else class="px-2 w-full">
<NcDivider class="!my-0 !border-nc-border-gray-dark" />
</div>
<DashboardSidebarUserInfo />
</div>
</div>
</template>
<style lang="scss">
.nc-mini-sidebar {
--mini-sidebar-bg-color: var(--color-grey-100);
@apply w-[var(--mini-sidebar-width)] flex-none bg-[var(--mini-sidebar-bg-color)] flex flex-col justify-between items-center border-r-1 border-nc-border-gray-medium z-12 nc-scrollbar-thin relative;
.nc-mini-sidebar-ws-item {
@apply cursor-pointer h-9 w-8 rounded py-1 flex items-center justify-center children:flex-none text-nc-content-gray-muted transition-all duration-200;
.nc-workspace-avatar {
img {
@apply !cursor-pointer;
}
}
&.nc-small-shadow .nc-workspace-avatar {
box-shadow: 0px 5px 0px -2px rgba(0, 0, 0, 0.4);
}
&.nc-medium-shadow .nc-workspace-avatar {
box-shadow: 0px 4px 0px -2px rgba(0, 0, 0, 0.4), 0px 7px 0px -3px rgba(0, 0, 0, 0.2);
}
}
.nc-mini-sidebar-btn-full-width {
@apply w-[var(--mini-sidebar-width)] h-[var(--mini-sidebar-width)] flex-none flex justify-center items-center cursor-pointer;
&:hover {
.nc-mini-sidebar-btn:not(.active) {
@apply bg-nc-bg-gray-medium;
}
}
}
.nc-mini-sidebar-btn {
@apply cursor-pointer h-7 w-7 rounded !p-1.5 flex items-center justify-center children:flex-none !text-nc-content-gray-muted transition-all duration-200;
&:not(.active) {
@apply hover:bg-nc-bg-gray-medium;
}
&.active {
@apply !bg-brand-100 !text-nc-content-brand;
}
&.active-base {
@apply !text-nc-content-brand;
}
&.hovered {
@apply bg-nc-bg-gray-medium;
}
}
}
</style>