fix(editor): Fix sidebar logo container layout (#13203)

This commit is contained in:
Csaba Tuncsik 2025-03-03 14:48:27 +01:00 committed by GitHub
parent 1909b74350
commit 850d458858
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 98 additions and 22 deletions

View file

@ -65,9 +65,7 @@ onMounted(() => {
<div :class="containerClasses" data-test-id="n8n-logo">
<LogoIcon :class="$style.logo" ref="logo" />
<LogoText v-if="showLogoText" :class="$style.logoText" />
<div v-if="showReleaseChannelTag" size="small" round :class="$style.releaseChannelTag">
{{ releaseChannel }}
</div>
<div v-if="showReleaseChannelTag" :class="$style.releaseChannelTag">{{ releaseChannel }}</div>
<slot />
</div>
</template>
@ -76,6 +74,7 @@ onMounted(() => {
.logoContainer {
display: flex;
justify-content: center;
align-items: center;
}
.logoText {
@ -91,12 +90,11 @@ onMounted(() => {
background-color: var(--color-background-base);
border: 1px solid var(--color-foreground-base);
border-radius: var(--border-radius-base);
font-size: var(--font-size-4xs);
font-size: var(--font-size-3xs);
font-weight: var(--font-weight-bold);
text-transform: capitalize;
line-height: var(--font-line-height-regular);
height: var(--spacing-s);
margin: 10px 0 0 3px;
margin: 8px 0 0 3px;
}
.authView {
@ -104,12 +102,18 @@ onMounted(() => {
margin-bottom: var(--spacing-xl);
}
.sidebar {
.logo,
.logoText {
transform: scale(1.3);
}
.sidebarExpanded .logo {
.logoText {
margin-left: var(--spacing-xs);
margin-right: var(--spacing-3xs);
}
.sidebarExpanded .logo {
margin-left: var(--spacing-3xs);
}
.sidebarCollapsed .logo {

View file

@ -44,6 +44,6 @@ exports[`Logo > renders the releaseChannelTag for non-stable releaseChannel 1`]
<path d="M18.367 21.2h1.624v-3.442c0-1.131.685-1.627 1.46-1.627.76 0 1.357.509 1.357 1.55v3.52h1.624V17.35c0-1.664-.964-2.63-2.474-2.63-.952 0-1.485.381-1.865.877h-.102l-.14-.75h-1.484zm-14.376 0H2.367v-6.352h1.485l.14.75h.1c.381-.496.914-.877 1.866-.877 1.51 0 2.474.966 2.474 2.63v3.85H6.808V17.68c0-1.041-.596-1.55-1.358-1.55-.774 0-1.459.496-1.459 1.627z"></path>
</g>
</svg>
<div size="small" round="" class="releaseChannelTag">dev</div>
<div class="releaseChannelTag">dev</div>
</div>"
`;

View file

@ -0,0 +1,54 @@
import { reactive } from 'vue';
import { createComponentRenderer } from '@/__tests__/render';
import { createTestingPinia } from '@pinia/testing';
import { type MockedStore, mockedStore } from '@/__tests__/utils';
import { defaultSettings } from '@/__tests__/defaults';
import MainSidebar from '@/components/MainSidebar.vue';
import { useSettingsStore } from '@/stores/settings.store';
import { useUIStore } from '@/stores/ui.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
vi.mock('vue-router', () => ({
useRouter: () => ({}),
useRoute: () => reactive({}),
RouterLink: vi.fn(),
}));
let renderComponent: ReturnType<typeof createComponentRenderer>;
let settingsStore: MockedStore<typeof useSettingsStore>;
let uiStore: MockedStore<typeof useUIStore>;
let sourceControlStore: MockedStore<typeof useSourceControlStore>;
describe('MainSidebar', () => {
beforeEach(() => {
renderComponent = createComponentRenderer(MainSidebar, {
pinia: createTestingPinia(),
});
settingsStore = mockedStore(useSettingsStore);
uiStore = mockedStore(useUIStore);
sourceControlStore = mockedStore(useSourceControlStore);
settingsStore.settings = defaultSettings;
});
it('renders the sidebar without error', () => {
expect(() => renderComponent()).not.toThrow();
});
test.each([
[false, true, true],
[true, false, false],
[true, true, false],
[false, false, false],
])(
'should render readonly tooltip when is opened %s and the environment is readonly %s',
(sidebarMenuCollapsed, branchReadOnly, shouldRender) => {
uiStore.sidebarMenuCollapsed = sidebarMenuCollapsed;
sourceControlStore.preferences.branchReadOnly = branchReadOnly;
const { queryByTestId } = renderComponent();
expect(queryByTestId('read-only-env-icon') !== null).toBe(shouldRender);
},
);
});

View file

@ -339,7 +339,12 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
</template>
</i18n-t>
</template>
<N8nIcon icon="lock" size="xsmall" :class="$style.readOnlyEnvironmentIcon" />
<N8nIcon
data-test-id="read-only-env-icon"
icon="lock"
size="xsmall"
:class="$style.readOnlyEnvironmentIcon"
/>
</N8nTooltip>
</Logo>
<N8nNavigationDropdown
@ -368,7 +373,18 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
</N8nTooltip>
</template>
<template #[createProjectAppendSlotName]="{ item }">
<N8nTooltip v-if="item.disabled" placement="right" :content="projectsLimitReachedMessage">
<N8nTooltip
v-if="sourceControlStore.preferences.branchReadOnly"
placement="right"
:content="i18n.baseText('readOnlyEnv.cantAdd.project')"
>
<N8nIcon style="margin-left: auto; margin-right: 5px" icon="lock" size="xsmall" />
</N8nTooltip>
<N8nTooltip
v-else-if="item.disabled"
placement="right"
:content="projectsLimitReachedMessage"
>
<N8nButton
:size="'mini'"
style="margin-left: auto"
@ -462,19 +478,17 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
<style lang="scss" module>
.sideMenu {
display: grid;
position: relative;
height: 100%;
grid-template-rows: auto 1fr auto;
border-right: var(--border-width-base) var(--border-style-base) var(--color-foreground-base);
transition: width 150ms ease-in-out;
width: $sidebar-expanded-width;
padding-top: 54px;
min-width: $sidebar-expanded-width;
max-width: 244px;
background-color: var(--menu-background, var(--color-background-xlight));
.logo {
position: absolute;
top: 0;
left: 0;
width: 100%;
display: flex;
align-items: center;
padding: var(--spacing-xs);
@ -489,7 +503,7 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
&.sideMenuCollapsed {
width: $sidebar-width;
padding-top: 100px;
min-width: auto;
.logo {
flex-direction: column;
@ -589,6 +603,6 @@ onClickOutside(createBtn as Ref<VueInstance>, () => {
align-self: center;
padding: 2px;
border-radius: var(--border-radius-small);
margin: 5px 5px 0;
margin: 7px 12px 0 5px;
}
</style>

View file

@ -836,7 +836,7 @@ onBeforeUnmount(() => {
}
.data-display-wrapper {
height: calc(100% - var(--spacing-l)) !important;
height: 100%;
margin-top: var(--spacing-xl) !important;
margin-bottom: var(--spacing-xl) !important;
width: 100%;

View file

@ -911,6 +911,7 @@
"readOnlyEnv.tooltip.link": "More info.",
"readOnlyEnv.cantAdd.workflow": "You can't add new workflows to a protected n8n instance",
"readOnlyEnv.cantAdd.credential": "You can't add new credentials to a protected n8n instance",
"readOnlyEnv.cantAdd.project": "You can't add new projects to a protected n8n instance",
"readOnlyEnv.cantAdd.any": "You can't create new workflows or credentials on a protected n8n instance",
"readOnlyEnv.cantEditOrRun": "This workflow can't be edited or run manually because it's on a protected instance",
"mainSidebar.aboutN8n": "About n8n",

View file

@ -15,6 +15,7 @@ import { STORES } from '@/constants';
import { useUsersStore } from '@/stores/users.store';
import { getResourcePermissions } from '@/permissions';
import type { CreateProjectDto, UpdateProjectDto } from '@n8n/api-types';
import { useSourceControlStore } from '@/stores/sourceControl.store';
export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
const route = useRoute();
@ -22,6 +23,7 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
const settingsStore = useSettingsStore();
const credentialsStore = useCredentialsStore();
const usersStore = useUsersStore();
const sourceControlStore = useSourceControlStore();
const projects = ref<ProjectListItem[]>([]);
const myProjects = ref<ProjectListItem[]>([]);
@ -60,8 +62,9 @@ export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
);
const canCreateProjects = computed<boolean>(
() =>
hasUnlimitedProjects.value ||
(isTeamProjectFeatureEnabled.value && !isTeamProjectLimitExceeded.value),
(hasUnlimitedProjects.value ||
(isTeamProjectFeatureEnabled.value && !isTeamProjectLimitExceeded.value)) &&
!sourceControlStore.preferences.branchReadOnly,
);
const hasPermissionToCreateProjects = computed(() =>
hasPermission(['rbac'], { rbac: { scope: 'project:create' } }),