diff --git a/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue b/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue index b3ad9d1daf..6ad44fc7bb 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nNavigationDropdown/NavigationDropdown.vue @@ -5,13 +5,14 @@ import type { RouteLocationRaw } from 'vue-router'; import ConditionalRouterLink from '../ConditionalRouterLink'; import N8nIcon from '../N8nIcon'; -import { type IconName } from '../N8nIcon/icons'; +import type { IconName } from '../N8nIcon/icons'; +import N8nText from '../N8nText'; type BaseItem = { id: string; title: string; disabled?: boolean; - icon?: IconName; + icon?: IconName | { type: 'icon'; value: IconName } | { type: 'emoji'; value: string }; route?: RouteLocationRaw; }; @@ -99,7 +100,21 @@ defineExpose({ :disabled="subitem.disabled" @click="emit('itemClick', $event)" > - + + + {{ subitem.title }} diff --git a/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts b/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts index 43d4e2e362..39b7a07cd2 100644 --- a/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts +++ b/packages/frontend/editor-ui/src/components/layouts/ResourcesListLayout.test.ts @@ -4,8 +4,8 @@ import { createComponentRenderer } from '@/__tests__/render'; import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue'; import type { Resource } from '@/Interface'; import type router from 'vue-router'; -import type { ProjectSharingData } from 'n8n-workflow'; import { waitAllPromises } from '@/__tests__/utils'; +import type { ProjectSharingData } from '@/types/projects.types'; const TEST_HOME_PROJECT: ProjectSharingData = vi.hoisted(() => ({ id: '1', diff --git a/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts b/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts index 071d1220d4..46180ea830 100644 --- a/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts +++ b/packages/frontend/editor-ui/src/composables/useGlobalEntityCreation.ts @@ -12,13 +12,27 @@ import { getResourcePermissions } from '@n8n/permissions'; import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper'; import type { Scope } from '@n8n/permissions'; import type { RouteLocationRaw } from 'vue-router'; -import { type IconName } from '@n8n/design-system/components/N8nIcon/icons'; +import { updatedIconSet, type IconName } from '@n8n/design-system/components/N8nIcon/icons'; + +type ProjectIcon = IconName | { type: 'icon'; value: IconName } | { type: 'emoji'; value: string }; + +const isIconName = (icon: unknown): icon is IconName => + typeof icon === 'string' && Object.keys(updatedIconSet).includes(icon); + +const isProjectIcon = (icon: unknown): icon is ProjectIcon => + isIconName(icon) || + (typeof icon === 'object' && + icon !== null && + 'value' in icon && + typeof icon.value === 'string' && + 'type' in icon && + (icon.type === 'emoji' || (icon.type === 'icon' && isIconName(icon.value)))); type BaseItem = { id: string; title: string; disabled?: boolean; - icon?: IconName; + icon?: ProjectIcon; route?: RouteLocationRaw; }; @@ -30,6 +44,7 @@ export const useGlobalEntityCreation = () => { const CREATE_PROJECT_ID = 'create-project'; const WORKFLOWS_MENU_ID = 'workflow'; const CREDENTIALS_MENU_ID = 'credential'; + const DEFAULT_ICON: IconName = 'layers'; const settingsStore = useSettingsStore(); const cloudPlanStore = useCloudPlanStore(); @@ -117,7 +132,7 @@ export const useGlobalEntityCreation = () => { ...displayProjects.value.map((project) => ({ id: `workflow-${project.id}`, title: project.name as string, - icon: 'layers' as IconName, + icon: isProjectIcon(project.icon) ? project.icon : DEFAULT_ICON, disabled: disabledWorkflow(project.scopes), route: { name: VIEWS.NEW_WORKFLOW, @@ -141,7 +156,7 @@ export const useGlobalEntityCreation = () => { { id: 'credential-personal', title: i18n.baseText('projects.menu.personal'), - icon: 'user' as IconName, + icon: 'user', disabled: disabledCredential(projectsStore.personalProject?.scopes), route: { name: VIEWS.PROJECTS_CREDENTIALS, @@ -151,7 +166,7 @@ export const useGlobalEntityCreation = () => { ...displayProjects.value.map((project) => ({ id: `credential-${project.id}`, title: project.name as string, - icon: 'layers' as IconName, + icon: isProjectIcon(project.icon) ? project.icon : DEFAULT_ICON, disabled: disabledCredential(project.scopes), route: { name: VIEWS.PROJECTS_CREDENTIALS, @@ -166,7 +181,7 @@ export const useGlobalEntityCreation = () => { title: 'Project', disabled: !projectsStore.canCreateProjects || !projectsStore.hasPermissionToCreateProjects, }, - ]; + ] satisfies Item[]; }); const createProject = async () => { @@ -175,7 +190,7 @@ export const useGlobalEntityCreation = () => { try { const newProject = await projectsStore.createProject({ name: i18n.baseText('projects.settings.newProjectName'), - icon: { type: 'icon', value: 'layers' }, + icon: { type: 'icon', value: DEFAULT_ICON }, }); await router.push({ name: VIEWS.PROJECT_SETTINGS, params: { projectId: newProject.id } }); toast.showMessage({