diff --git a/cypress/e2e/49-folders.cy.ts b/cypress/e2e/49-folders.cy.ts index 69d5a6a4ea..8591daaaa0 100644 --- a/cypress/e2e/49-folders.cy.ts +++ b/cypress/e2e/49-folders.cy.ts @@ -13,15 +13,11 @@ import { getFolderCard, getFolderCardActionItem, getFolderCardActionToggle, - getFolderCardBreadCrumbsEllipsis, - getFolderCardCurrentBreadcrumb, - getFolderCardHomeProjectBreadcrumb, getFolderCards, getHomeProjectBreadcrumb, getListBreadcrumbs, getMainBreadcrumbsEllipsis, getMainBreadcrumbsEllipsisMenuItems, - getOpenHiddenItemsTooltip, getOverviewMenuItem, getPersonalProjectMenuItem, getVisibleListBreadcrumbs, @@ -62,7 +58,7 @@ describe('Folders', () => { createFolderFromListHeaderButton('My Folder 2'); getFolderCards().should('have.length.greaterThan', 0); // Clicking on the success toast should navigate to the folder - successToast().find('a').contains('My Folder 2').click(); + successToast().contains('My Folder 2').find('a').contains('Open folder').click(); getCurrentBreadcrumb().should('contain.text', 'My Folder 2'); }); @@ -113,29 +109,21 @@ describe('Folders', () => { createFolderFromProjectHeader('Multi-level Test'); createFolderInsideFolder('Child Folder', 'Multi-level Test'); // One level deep: - // - Both main breadcrumbs & card breadcrumbs should only show home project and current folder + // - Breadcrumbs should only show home project and current folder getHomeProjectBreadcrumb().should('exist'); getCurrentBreadcrumb().should('contain.text', 'Multi-level Test'); getFolderCard('Child Folder').should('exist'); - getFolderCardHomeProjectBreadcrumb('Child Folder').should('exist'); - getFolderCardCurrentBreadcrumb('Child Folder').should('contain.text', 'Multi-level Test'); - // No hidden items at this level - getFolderCardBreadCrumbsEllipsis('Child Folder').should('not.exist'); createFolderInsideFolder('Child Folder 2', 'Child Folder'); // Two levels deep: - // - Main breadcrumbs should also show parent folder, without hidden ellipsis - // - Card breadcrumbs should show home project, parent folder, with hidden ellipsis + // - Breadcrumbs should also show parent folder, without hidden ellipsis getHomeProjectBreadcrumb().should('exist'); getCurrentBreadcrumb().should('contain.text', 'Child Folder'); getVisibleListBreadcrumbs().should('have.length', 1); getMainBreadcrumbsEllipsis().should('not.exist'); - getFolderCardCurrentBreadcrumb('Child Folder 2').should('contain.text', 'Child Folder'); - getFolderCardBreadCrumbsEllipsis('Child Folder 2').should('exist'); // Three levels deep: - // - Main breadcrumbs should show parents up to the grandparent folder, with one hidden element - // - Card breadcrumbs should now show two hidden elements + // - Breadcrumbs should show parents up to the grandparent folder, with one hidden element createFolderInsideFolder('Child Folder 3', 'Child Folder 2'); getVisibleListBreadcrumbs().should('have.length', 1); getMainBreadcrumbsEllipsis().should('exist'); @@ -143,12 +131,6 @@ describe('Folders', () => { getMainBreadcrumbsEllipsis().click(); getMainBreadcrumbsEllipsisMenuItems().first().should('contain.text', 'Multi-level Test'); getMainBreadcrumbsEllipsis().click(); - // Card breadcrumbs should show two hidden elements - getFolderCardBreadCrumbsEllipsis('Child Folder 3').should('exist'); - // Clicking on the ellipsis should show hidden element in card breadcrumbs - getFolderCardBreadCrumbsEllipsis('Child Folder 3').click(); - getOpenHiddenItemsTooltip().should('be.visible'); - getOpenHiddenItemsTooltip().should('contain.text', 'Multi-level Test / Child Folder'); }); // Make sure breadcrumbs and folder card show correct info when landing straight on a folder page @@ -171,13 +153,6 @@ describe('Folders', () => { getMainBreadcrumbsEllipsisMenuItems().first().should('contain.text', 'Landing Test'); // Should load child folder card getFolderCard('Child Folder 3').should('exist'); - // Card breadcrumbs should show home project and parent, with two hidden elements - getFolderCardHomeProjectBreadcrumb('Child Folder 3').should('exist'); - getFolderCardCurrentBreadcrumb('Child Folder 3').should('contain.text', 'Child Folder 2'); - getFolderCardBreadCrumbsEllipsis('Child Folder 3').should('exist'); - getFolderCardBreadCrumbsEllipsis('Child Folder 3').click(); - getOpenHiddenItemsTooltip().should('be.visible'); - getOpenHiddenItemsTooltip().should('contain.text', 'Landing Test / Child Folder'); }); it('should show folders only in projects', () => { @@ -239,9 +214,7 @@ describe('Folders', () => { getPersonalProjectMenuItem().find('li').should('have.class', 'is-active'); }); - // TODO: Once we have a backend endpoint that returns sub-folder count, enable this - // eslint-disable-next-line n8n-local-rules/no-skipped-tests - it.skip('should warn before deleting non-empty folder from card dropdown', () => { + it('should warn before deleting non-empty folder from card dropdown', () => { goToPersonalProject(); createFolderFromProjectHeader('I also have family'); createFolderInsideFolder('Child 1', 'I also have family'); diff --git a/cypress/pages/workflows.ts b/cypress/pages/workflows.ts index f93e3e0103..5e7a298890 100644 --- a/cypress/pages/workflows.ts +++ b/cypress/pages/workflows.ts @@ -39,7 +39,7 @@ export class WorkflowsPage extends BasePage { workflowDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete'), workflowMoveButton: () => - cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Move'), + cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Change owner'), workflowFilterButton: () => cy.getByTestId('resources-list-filters-trigger').filter(':visible'), workflowTagsDropdown: () => cy.getByTestId('tags-dropdown'), workflowTagItem: (tag: string) => cy.getByTestId('tag').contains(tag), diff --git a/packages/frontend/@n8n/design-system/src/components/N8nActionToggle/ActionToggle.vue b/packages/frontend/@n8n/design-system/src/components/N8nActionToggle/ActionToggle.vue index 7f0cd6988f..bd6f3c47ac 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nActionToggle/ActionToggle.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nActionToggle/ActionToggle.vue @@ -20,6 +20,7 @@ interface ActionToggleProps { loading?: boolean; loadingRowCount?: number; disabled?: boolean; + popperClass?: string; } defineOptions({ name: 'N8nActionToggle' }); @@ -33,6 +34,7 @@ withDefaults(defineProps(), { loading: false, loadingRowCount: 3, disabled: false, + popperClass: '', }); const actionToggleRef = ref | null>(null); @@ -62,6 +64,7 @@ defineExpose({ :placement="placement" :size="size" :disabled="disabled" + :popper-class="popperClass" trigger="click" @command="onCommand" @visible-change="onVisibleChange" diff --git a/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/Breadcrumbs.vue b/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/Breadcrumbs.vue index 626a6ef4e4..90d6133751 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/Breadcrumbs.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/Breadcrumbs.vue @@ -153,6 +153,7 @@ const handleTooltipClose = () => { :loading-row-count="loadingSkeletonRows" :disabled="dropdownDisabled" :class="$style['action-toggle']" + :popper-class="$style['hidden-items-menu-popper']" theme="dark" placement="bottom" size="small" @@ -199,6 +200,7 @@ const handleTooltipClose = () => { [$style.item]: true, [$style.current]: props.highlightLastItem && index === items.length - 1, }" + :title="item.label" :data-test-id=" index === items.length - 1 ? 'breadcrumbs-item-current' : 'breadcrumbs-item' " @@ -238,6 +240,13 @@ const handleTooltipClose = () => { align-items: center; } +.item * { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .item.current span { color: var(--color-text-dark); } @@ -272,6 +281,21 @@ const handleTooltipClose = () => { color: var(--color-text-base); } +.hidden-items-menu-popper { + & > div ul { + max-height: 250px; + overflow: auto; + } + + li { + max-width: var(--spacing-5xl); + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + .tooltip-loading { min-width: var(--spacing-3xl); width: 100%; @@ -319,6 +343,10 @@ const handleTooltipClose = () => { gap: var(--spacing-5xs); } + .item { + max-width: var(--spacing-3xl); + } + .item, .item * { color: var(--color-text-base); @@ -331,7 +359,7 @@ const handleTooltipClose = () => { } .separator { - font-size: var(--font-size-m); + font-size: var(--font-size-s); color: var(--color-text-base); } } @@ -345,7 +373,11 @@ const handleTooltipClose = () => { .item, .item * { color: var(--color-text-base); - font-size: var(--font-size-m); + font-size: var(--font-size-s); + } + + .item { + max-width: var(--spacing-5xl); } .item a:hover * { diff --git a/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/__snapshots__/BreadCrumbs.test.ts.snap b/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/__snapshots__/BreadCrumbs.test.ts.snap index ea99152a03..234eddded6 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/__snapshots__/BreadCrumbs.test.ts.snap +++ b/packages/frontend/@n8n/design-system/src/components/N8nBreadcrumbs/__snapshots__/BreadCrumbs.test.ts.snap @@ -6,13 +6,13 @@ exports[`Breadcrumbs > does not highlight last item for "highlightLastItem = fal -
  • Folder 1
  • +
  • Folder 1
  • /
  • -
  • Folder 2
  • +
  • Folder 2
  • /
  • -
  • Folder 3
  • +
  • Folder 3
  • /
  • -
  • Current
  • +
  • Current
  • " @@ -24,13 +24,13 @@ exports[`Breadcrumbs > renders custom separator correctly 1`] = ` -
  • Folder 1
  • +
  • Folder 1
  • -
  • Folder 2
  • +
  • Folder 2
  • -
  • Folder 3
  • +
  • Folder 3
  • -
  • Current
  • +
  • Current
  • " @@ -42,13 +42,13 @@ exports[`Breadcrumbs > renders default version correctly 1`] = ` -
  • Folder 1
  • +
  • Folder 1
  • /
  • -
  • Folder 2
  • +
  • Folder 2
  • /
  • -
  • Folder 3
  • +
  • Folder 3
  • /
  • -
  • Current
  • +
  • Current
  • " @@ -61,13 +61,13 @@ exports[`Breadcrumbs > renders slots correctly 1`] = `
  • /
  • -
  • Folder 1
  • +
  • Folder 1
  • /
  • -
  • Folder 2
  • +
  • Folder 2
  • /
  • -
  • Folder 3
  • +
  • Folder 3
  • /
  • -
  • Current
  • +
  • Current
  • [POST] Custom content
    @@ -80,13 +80,13 @@ exports[`Breadcrumbs > renders small version correctly 1`] = ` -
  • Folder 1
  • +
  • Folder 1
  • /
  • -
  • Folder 2
  • +
  • Folder 2
  • /
  • -
  • Folder 3
  • +
  • Folder 3
  • /
  • -
  • Current
  • +
  • Current
  • " diff --git a/packages/frontend/@n8n/design-system/src/types/icon.ts b/packages/frontend/@n8n/design-system/src/types/icon.ts index 8cd048ab61..91d065818f 100644 --- a/packages/frontend/@n8n/design-system/src/types/icon.ts +++ b/packages/frontend/@n8n/design-system/src/types/icon.ts @@ -1,6 +1,6 @@ import type { TextColor } from '@n8n/design-system/types/text'; -const ICON_SIZE = ['xsmall', 'small', 'medium', 'large'] as const; +const ICON_SIZE = ['xsmall', 'small', 'medium', 'large', 'xlarge'] as const; export type IconSize = (typeof ICON_SIZE)[number]; export type IconColor = TextColor; diff --git a/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue b/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue index 72fca8e7fb..ffed4bc890 100644 --- a/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue +++ b/packages/frontend/editor-ui/src/components/Folders/DeleteFolderModal.vue @@ -80,28 +80,6 @@ const enabled = computed(() => { return false; }); -const folderContentWarningMessage = computed(() => { - const folderCount = props.data.content.subFolderCount ?? 0; - const workflowCount = props.data.content.workflowCount ?? 0; - let folderText = ''; - let workflowText = ''; - if (folderCount > 0) { - folderText = i18n.baseText('folder.count', { interpolate: { count: folderCount } }); - } - if (workflowCount > 0) { - workflowText = i18n.baseText('workflow.count', { interpolate: { count: workflowCount } }); - } - if (folderCount > 0 && workflowCount > 0) { - folderText += ` ${i18n.baseText('folder.and.workflow.separator')} `; - } - return i18n.baseText('folder.delete.modal.confirmation', { - interpolate: { - folders: folderText, - workflows: workflowText, - }, - }); -}); - async function onSubmit() { if (!enabled.value) { return; @@ -161,7 +139,9 @@ onMounted(async () => {
    - {{ folderContentWarningMessage }} + {{ + i18n.baseText('folder.delete.modal.confirmation') + }}
    { diff --git a/packages/frontend/editor-ui/src/components/Folders/FolderCard.test.ts b/packages/frontend/editor-ui/src/components/Folders/FolderCard.test.ts index 7a66fce008..c0d4ddd750 100644 --- a/packages/frontend/editor-ui/src/components/Folders/FolderCard.test.ts +++ b/packages/frontend/editor-ui/src/components/Folders/FolderCard.test.ts @@ -44,25 +44,6 @@ const DEFAULT_FOLDER: FolderResource = { }, } as const satisfies FolderResource; -const PARENT_FOLDER: FolderResource = { - id: '2', - name: 'Folder 2', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - resourceType: 'folder', - readOnly: false, - workflowCount: 0, - subFolderCount: 0, - homeProject: { - id: '1', - name: 'Project 1', - icon: null, - type: 'team', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, -} as const satisfies FolderResource; - const DEFAULT_BREADCRUMBS: { visibleItems: FolderPathItem[]; hiddenItems: FolderPathItem[] } = { visibleItems: [{ id: '1', label: 'Parent 2' }], hiddenItems: [{ id: '2', label: 'Parent 1', parentFolder: '1' }], @@ -122,67 +103,6 @@ describe('FolderCard', () => { expect(queryByTestId('folder-card-folder-count')).not.toBeInTheDocument(); }); - it('should render breadcrumbs with personal folder', () => { - const { getByTestId } = renderComponent(); - expect(getByTestId('folder-card-icon')).toBeInTheDocument(); - expect(getByTestId('folder-card-breadcrumbs')).toHaveTextContent('Personal'); - }); - - it('should render breadcrumbs with team project', () => { - const { getByTestId } = renderComponent({ - props: { - data: { - ...DEFAULT_FOLDER, - homeProject: { - id: '1', - name: 'Project 1', - icon: null, - type: 'team', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, - }, - }, - }); - expect(getByTestId('folder-card-icon')).toBeInTheDocument(); - if (!DEFAULT_FOLDER.homeProject?.name) { - throw new Error('homeProject should be defined for this test'); - } - expect(getByTestId('folder-card-breadcrumbs')).toHaveTextContent( - DEFAULT_FOLDER.homeProject.name, - ); - }); - - it('should render breadcrumbs with home project and parent folder', () => { - const { getByTestId } = renderComponent({ - props: { - data: { - ...DEFAULT_FOLDER, - homeProject: { - id: '1', - name: 'Project 1', - icon: null, - type: 'team', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, - parentFolder: PARENT_FOLDER, - }, - breadcrumbs: { - visibleItems: [{ id: PARENT_FOLDER.id, label: PARENT_FOLDER.name, parentFolder: '1' }], - hiddenItems: [], - }, - }, - }); - expect(getByTestId('folder-card-icon')).toBeInTheDocument(); - if (!DEFAULT_FOLDER.homeProject?.name) { - throw new Error('homeProject should be defined for this test'); - } - expect(getByTestId('folder-card-breadcrumbs')).toHaveTextContent( - `${DEFAULT_FOLDER.homeProject.name}/.../${PARENT_FOLDER.name}`, - ); - }); - it('should not render action dropdown if no actions are provided', () => { const { queryByTestId } = renderComponent({ props: { diff --git a/packages/frontend/editor-ui/src/components/Folders/FolderCard.vue b/packages/frontend/editor-ui/src/components/Folders/FolderCard.vue index 951fcf0220..cb52587221 100644 --- a/packages/frontend/editor-ui/src/components/Folders/FolderCard.vue +++ b/packages/frontend/editor-ui/src/components/Folders/FolderCard.vue @@ -6,20 +6,17 @@ import { type ProjectIcon, ProjectTypes } from '@/types/projects.types'; import { useI18n } from '@/composables/useI18n'; import { useRoute, useRouter } from 'vue-router'; import { VIEWS } from '@/constants'; -import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue'; -import type { FolderPathItem, UserAction } from '@/Interface'; +import type { UserAction } from '@/Interface'; type Props = { data: FolderResource; actions: UserAction[]; - breadcrumbs: { - visibleItems: FolderPathItem[]; - hiddenItems: FolderPathItem[]; - }; + readOnly?: boolean; }; const props = withDefaults(defineProps(), { actions: () => [], + readOnly: true, }); const i18n = useI18n(); @@ -71,12 +68,6 @@ const onAction = async (action: string) => { } emit('action', { action, folderId: props.data.id }); }; - -const onBreadcrumbsItemClick = async (item: PathItem) => { - if (item.href) { - await router.push(item.href); - } -};