mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor(editor): Migrate header WorkflowDetails to composition api (no-changelog) (#9186)
This commit is contained in:
parent
442aaba116
commit
1c261f85a3
|
@ -61,23 +61,13 @@ import { ref, useCssModule, useAttrs, computed } from 'vue';
|
|||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, type Placement } from 'element-plus';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import { N8nKeyboardShortcut } from '../N8nKeyboardShortcut';
|
||||
import type { KeyboardShortcut } from '../../types';
|
||||
import type { ActionDropdownItem } from '../../types';
|
||||
import type { IconSize } from '@/types/icon';
|
||||
|
||||
interface IActionDropdownItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
divided?: boolean;
|
||||
disabled?: boolean;
|
||||
shortcut?: KeyboardShortcut;
|
||||
customClass?: string;
|
||||
}
|
||||
|
||||
const TRIGGER = ['click', 'hover'] as const;
|
||||
|
||||
interface ActionDropdownProps {
|
||||
items: IActionDropdownItem[];
|
||||
items: ActionDropdownItem[];
|
||||
placement?: Placement;
|
||||
activatorIcon?: string;
|
||||
activatorSize?: IconSize;
|
||||
|
@ -99,7 +89,7 @@ const $attrs = useAttrs();
|
|||
const testIdPrefix = $attrs['data-test-id'];
|
||||
|
||||
const $style = useCssModule();
|
||||
const getItemClasses = (item: IActionDropdownItem): Record<string, boolean> => {
|
||||
const getItemClasses = (item: ActionDropdownItem): Record<string, boolean> => {
|
||||
return {
|
||||
[$style.itemContainer]: true,
|
||||
[$style.disabled]: !!item.disabled,
|
||||
|
|
|
@ -127,6 +127,7 @@
|
|||
border-radius: var.$tag-border-radius;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
|
||||
.el-icon.el-tag__close {
|
||||
border-radius: 50%;
|
||||
|
@ -137,9 +138,8 @@
|
|||
height: 16px;
|
||||
width: 16px;
|
||||
line-height: 16px;
|
||||
vertical-align: middle;
|
||||
top: -1px;
|
||||
right: -5px;
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
|
|
11
packages/design-system/src/types/action-dropdown.ts
Normal file
11
packages/design-system/src/types/action-dropdown.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { KeyboardShortcut } from '@/types/keyboardshortcut';
|
||||
|
||||
export interface ActionDropdownItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
divided?: boolean;
|
||||
disabled?: boolean;
|
||||
shortcut?: KeyboardShortcut;
|
||||
customClass?: string;
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
export * from './action-dropdown';
|
||||
export * from './button';
|
||||
export * from './datatable';
|
||||
export * from './form';
|
||||
|
|
|
@ -19,3 +19,28 @@ Range.prototype.getClientRects = vi.fn(() => ({
|
|||
length: 0,
|
||||
[Symbol.iterator]: vi.fn(),
|
||||
}));
|
||||
|
||||
export class IntersectionObserver {
|
||||
root = null;
|
||||
rootMargin = '';
|
||||
thresholds = [];
|
||||
|
||||
disconnect() {
|
||||
return null;
|
||||
}
|
||||
|
||||
observe() {
|
||||
return null;
|
||||
}
|
||||
|
||||
takeRecords() {
|
||||
return [];
|
||||
}
|
||||
|
||||
unobserve() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
window.IntersectionObserver = IntersectionObserver;
|
||||
global.IntersectionObserver = IntersectionObserver;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<div :class="{ 'main-header': true, expanded: !uiStore.sidebarMenuCollapsed }">
|
||||
<div v-show="!hideMenuBar" class="top-menu">
|
||||
<WorkflowDetails :read-only="readOnly" />
|
||||
<WorkflowDetails v-if="workflow?.name" :workflow="workflow" :read-only="readOnly" />
|
||||
<TabBar
|
||||
v-if="onWorkflowPage"
|
||||
:items="tabBarItems"
|
||||
|
@ -27,7 +27,7 @@ import {
|
|||
STICKY_NODE_TYPE,
|
||||
VIEWS,
|
||||
} from '@/constants';
|
||||
import type { INodeUi, ITabBarItem } from '@/Interface';
|
||||
import type { INodeUi, ITabBarItem, IWorkflowDb } from '@/Interface';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
|
@ -75,6 +75,9 @@ export default defineComponent({
|
|||
hideMenuBar(): boolean {
|
||||
return Boolean(this.activeNode && this.activeNode.type !== STICKY_NODE_TYPE);
|
||||
},
|
||||
workflow(): IWorkflowDb {
|
||||
return this.workflowsStore.workflow;
|
||||
},
|
||||
workflowName(): string {
|
||||
return this.workflowsStore.workflowName;
|
||||
},
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import WorkflowDetails from '@/components/MainHeader/WorkflowDetails.vue';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import { STORES } from '@/constants';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { fireEvent } from '@testing-library/vue';
|
||||
|
||||
vi.mock('vue-router', async () => {
|
||||
const actual = await import('vue-router');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useRoute: () => ({
|
||||
value: {
|
||||
params: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
[STORES.SETTINGS]: {
|
||||
settings: {
|
||||
enterprise: {
|
||||
sharing: true,
|
||||
},
|
||||
},
|
||||
areTagsEnabled: true,
|
||||
},
|
||||
[STORES.TAGS]: {
|
||||
tags: {
|
||||
1: {
|
||||
id: '1',
|
||||
name: 'tag1',
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
name: 'tag2',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = createComponentRenderer(WorkflowDetails, {
|
||||
pinia: createTestingPinia({ initialState }),
|
||||
});
|
||||
|
||||
describe('WorkflowDetails', () => {
|
||||
it('renders workflow name and tags', async () => {
|
||||
const workflow = {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
tags: ['1', '2'],
|
||||
};
|
||||
|
||||
const { getByTestId, getByText } = renderComponent({
|
||||
props: {
|
||||
workflow,
|
||||
readOnly: false,
|
||||
},
|
||||
});
|
||||
|
||||
const workflowName = getByTestId('workflow-name-input');
|
||||
const workflowNameInput = workflowName.querySelector('input');
|
||||
|
||||
expect(workflowNameInput).toHaveValue('Test Workflow');
|
||||
expect(getByText('tag1')).toBeInTheDocument();
|
||||
expect(getByText('tag2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls save function on save button click', async () => {
|
||||
const onSaveButtonClick = vi.fn();
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
workflow: {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
tags: [],
|
||||
},
|
||||
readOnly: false,
|
||||
},
|
||||
global: {
|
||||
mocks: {
|
||||
onSaveButtonClick,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await fireEvent.click(getByTestId('workflow-save-button'));
|
||||
expect(onSaveButtonClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('opens share modal on share button click', async () => {
|
||||
const onShareButtonClick = vi.fn();
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
workflow: {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
tags: [],
|
||||
},
|
||||
readOnly: false,
|
||||
},
|
||||
global: {
|
||||
mocks: {
|
||||
onShareButtonClick,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await fireEvent.click(getByTestId('workflow-share-button'));
|
||||
expect(onShareButtonClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -318,7 +318,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
.el-tag {
|
||||
padding: 1px var(--spacing-4xs);
|
||||
padding: var(--spacing-5xs) var(--spacing-4xs);
|
||||
color: var(--color-text-dark);
|
||||
background-color: var(--color-background-base);
|
||||
border-radius: var(--border-radius-base);
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import type { XYPosition } from '@/Interface';
|
||||
import type { ActionDropdownItem, XYPosition } from '@/Interface';
|
||||
import { NOT_DUPLICATABE_NODE_TYPES, STICKY_NODE_TYPE } from '@/constants';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import type { IActionDropdownItem } from 'n8n-design-system/src/components/N8nActionDropdown/ActionDropdown.vue';
|
||||
import type { INode, INodeTypeDescription } from 'n8n-workflow';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { getMousePosition } from '../utils/nodeViewUtils';
|
||||
|
@ -34,7 +33,7 @@ export type ContextMenuAction =
|
|||
const position = ref<XYPosition>([0, 0]);
|
||||
const isOpen = ref(false);
|
||||
const target = ref<ContextMenuTarget>({ source: 'canvas' });
|
||||
const actions = ref<IActionDropdownItem[]>([]);
|
||||
const actions = ref<ActionDropdownItem[]>([]);
|
||||
const actionCallback = ref<ContextMenuActionCallback>(() => {});
|
||||
|
||||
export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) => {
|
||||
|
@ -147,7 +146,7 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) =
|
|||
...selectionActions,
|
||||
];
|
||||
} else {
|
||||
const menuActions: IActionDropdownItem[] = [
|
||||
const menuActions: ActionDropdownItem[] = [
|
||||
!onlyStickies && {
|
||||
id: 'toggle_activation',
|
||||
label: nodes.every((node) => node.disabled)
|
||||
|
@ -183,7 +182,7 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) =
|
|||
shortcut: { keys: ['Del'] },
|
||||
disabled: isReadOnly.value,
|
||||
},
|
||||
].filter(Boolean) as IActionDropdownItem[];
|
||||
].filter(Boolean) as ActionDropdownItem[];
|
||||
|
||||
if (nodes.length === 1) {
|
||||
const singleNodeActions = onlyStickies
|
||||
|
|
|
@ -354,7 +354,7 @@
|
|||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
|
||||
&.is-closable {
|
||||
overflow-y: hidden;
|
||||
|
@ -363,7 +363,7 @@
|
|||
.el-tag__close {
|
||||
max-height: 15px;
|
||||
max-width: 15px;
|
||||
margin-right: 6px;
|
||||
margin-left: var(--spacing-4xs);
|
||||
|
||||
&:hover {
|
||||
background-color: $tag-close-background-hover-color !important;
|
||||
|
|
Loading…
Reference in a new issue