mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
feat(editor, core, cli): implement new workflow experience (#4358)
* feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node (#4108) * feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node * feat(editor): Do not show duplicate button if canvas contains `maxNodes` amount of nodes * feat(ManualTrigger node): Implement ManualTrigger node (#4110) * feat(ManualTrigger node): Implement ManualTrigger node * 📝 Remove generics doc items from ManualTrigger node * feat(editor-ui): Trigger tab redesign (#4150) * 🚧 Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory * 🚧 Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations * ✨ Implement MainPanel background scrim * ♻️ Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType` * 🐛 Fix SlideTransition for all the NodeCreato panels * 💄 Fix cursos for CategoryItem and NodeItem * 🐛 Make sure ALL_NODE_FILTER is always set when MainPanel is mounted * 🎨 Address PR comments * label: Use Array type for CategorizedItems props * 🏷️ Add proper types for Vue props * 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel * 🎨 Use kebab case for main-panel and icon component * 🏷️ Improve types * feat(editor-ui): Redesign search input inside node creator panel (#4204) * 🚧 Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory * 🚧 Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations * ✨ Implement MainPanel background scrim * ♻️ Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType` * 🐛 Fix SlideTransition for all the NodeCreato panels * 💄 Fix cursos for CategoryItem and NodeItem * 🐛 Make sure ALL_NODE_FILTER is always set when MainPanel is mounted * 🎨 Address PR comments * label: Use Array type for CategorizedItems props * 🏷️ Add proper types for Vue props * 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel * ✨ Redesign search input and unify usage of categorized items * 🏷️ Use lowercase "Boolean" as `isSearchVisible` computed return type * 🔥 Remove useless emit * ✨ Implement no result view based on subcategory, minor fixes * 🎨 Remove unused properties * feat(node-email): Change EmailReadImap display name and name (#4239) * feat(editor-ui): Implement "Choose a Triger" action and related behaviour (#4226) * ✨ Implement "Choose a Triger" action and related behaviour * 🔇 Lint fix * ♻️ Remove PlaceholderTrigger node, add a button instead * 🎨 Merge onMouseEnter and onMouseLeave to a single function * 💡 Add comment * 🔥 Remove PlaceholderNode registration * 🎨 Rename TriggerPlaceholderButton to CanvasAddButton * ✨ Add method to unregister custom action and rework CanvasAddButton centering logic * 🎨 Run `setRecenteredCanvasAddButtonPosition` on `CanvasAddButton` mount * fix(editor): Fix selecting of node from node-creator panel by clicking * 🔀 Merge fixes * fix(editor): Show execute workflow trigger instead of workflow trigger in the trigger helper panel * feat(editor): Fix node creator panel slide transition (#4261) * fix(editor): Fix node creator panel slide-in/slide-out transitions * 🎨 Fix naming * 🎨 Use kebab-case for transition component name * feat(editor): Disable execution and show notice when user tries to run workflow without enabled triggers * fix(editor): Address first batch of new WF experience review (#4279) * fix(editor): Fix first batch of review items * bug(editor): Fix nodeview canvas add button centering * 🔇 Fix linter errors * bug(ManualTrigger Node): Fix manual trigger node execution * fix(editor): Do not show canvas add button in execution or demo mode and prevent clicking if creator is open * fix(editor): do not show pin data tooltip for manual trigger node * fix(editor): do not use nodeViewOffset on zoomToFit * 💄 Add margin for last node creator item and set font-weight to 700 for category title * ✨ Position welcome note next to the added trigger node * 🐛 Remve always true welcome note * feat(editor): Minor UI and UX tweaks (#4328) * 💄 Make top viewport buttons less prominent * ✨ Allow user to switch to all tabs if it contains filter results, move nodecreator state props to its own module * 🔇 Fix linting errors * 🔇 Fix linting errors * 🔇 Fix linting errors * chore(build): Ping Turbo version to 1.5.5 * 💄 Minor traigger panel and node view style changes * 💬 Update display name of execute workflow trigger * feat(core, editor): Update subworkflow execution logic (#4269) * ✨ Implement `findWorkflowStart` * ⚡ Extend `WorkflowOperationError` * ⚡ Add `WorkflowOperationError` to toast * 📘 Extend interface * ✨ Add `subworkflowExecutionError` to store * ✨ Create `SubworkflowOperationError` * ⚡ Render subworkflow error as node error * 🚚 Move subworkflow start validation to `cli` * ⚡ Reset subworkflow execution error state * 🔥 Remove unused import * ⚡ Adjust CLI commands * 🔥 Remove unneeded check * 🔥 Remove stray log * ⚡ Simplify syntax * ⚡ Sort in case both Start and EWT present * ♻️ Address Omar's feedback * 🔥 Remove unneeded lint exception * ✏️ Fix copy * 👕 Fix lint * fix: moved find start node function to catchable place Co-authored-by: Omar Ajoue <krynble@gmail.com> * 💄 Change ExecuteWorkflow node to primary * ✨ Allow user to navigate to all tab if it contains search results * 🐛 Fixed canvas control button while in demo, disable workflow activation for non-activavle nodes and revert zoomToFit bottom offset * :fix: Do not chow request text if there's results * 💬 Update noResults text Co-authored-by: Iván Ovejero <ivov.src@gmail.com> Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
@@ -28,6 +28,7 @@ const MIN_X_TO_SHOW_OUTPUT_LABEL = 90;
|
||||
const MIN_Y_TO_SHOW_OUTPUT_LABEL = 100;
|
||||
|
||||
export const NODE_SIZE = 100;
|
||||
export const PLACEHOLDER_TRIGGER_NODE_SIZE = 100;
|
||||
export const DEFAULT_START_POSITION_X = 180;
|
||||
export const DEFAULT_START_POSITION_Y = 240;
|
||||
export const HEADER_HEIGHT = 65;
|
||||
@@ -38,6 +39,7 @@ export const PUSH_NODES_OFFSET = NODE_SIZE * 2 + GRID_SIZE;
|
||||
const LOOPBACK_MINIMUM = 140;
|
||||
export const INPUT_UUID_KEY = '-input';
|
||||
export const OUTPUT_UUID_KEY = '-output';
|
||||
export const PLACEHOLDER_BUTTON = 'PlaceholderTriggerButton';
|
||||
|
||||
export const DEFAULT_START_NODE = {
|
||||
name: 'Start',
|
||||
@@ -50,13 +52,24 @@ export const DEFAULT_START_NODE = {
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
export const DEFAULT_PLACEHOLDER_TRIGGER_BUTTON = {
|
||||
name: 'Choose a Trigger...',
|
||||
type: PLACEHOLDER_BUTTON,
|
||||
typeVersion: 1,
|
||||
position: [],
|
||||
parameters: {
|
||||
height: PLACEHOLDER_TRIGGER_NODE_SIZE,
|
||||
width: PLACEHOLDER_TRIGGER_NODE_SIZE,
|
||||
},
|
||||
};
|
||||
|
||||
export const WELCOME_STICKY_NODE = {
|
||||
name: QUICKSTART_NOTE_NAME,
|
||||
type: STICKY_NODE_TYPE,
|
||||
typeVersion: 1,
|
||||
position: [
|
||||
-260,
|
||||
200,
|
||||
0,
|
||||
0,
|
||||
] as XYPosition,
|
||||
parameters: {
|
||||
height: 300,
|
||||
@@ -233,8 +246,10 @@ export const getLeftmostTopNode = (nodes: INodeUi[]): INodeUi => {
|
||||
|
||||
export const getWorkflowCorners = (nodes: INodeUi[]): IBounds => {
|
||||
return nodes.reduce((accu: IBounds, node: INodeUi) => {
|
||||
const xOffset = node.type === STICKY_NODE_TYPE && isNumber(node.parameters.width) ? node.parameters.width : NODE_SIZE;
|
||||
const yOffset = node.type === STICKY_NODE_TYPE && isNumber(node.parameters.height) ? node.parameters.height : NODE_SIZE;
|
||||
const hasCustomDimensions = [STICKY_NODE_TYPE, PLACEHOLDER_BUTTON].includes(node.type);
|
||||
const xOffset = hasCustomDimensions && isNumber(node.parameters.width) ? node.parameters.width : NODE_SIZE;
|
||||
const yOffset = hasCustomDimensions && isNumber(node.parameters.height) ? node.parameters.height : NODE_SIZE;
|
||||
|
||||
const x = node.position[0];
|
||||
const y = node.position[1];
|
||||
|
||||
@@ -429,11 +444,29 @@ const canUsePosition = (position1: XYPosition, position2: XYPosition) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
function closestNumberDivisibleBy(inputNumber: number, divisibleBy: number) {
|
||||
const quotient = Math.ceil(inputNumber / divisibleBy);
|
||||
|
||||
// 1st possible closest number
|
||||
const inputNumber1 = divisibleBy * quotient;
|
||||
|
||||
// 2nd possible closest number
|
||||
const inputNumber2 = (inputNumber * divisibleBy) > 0
|
||||
? (divisibleBy * (quotient + 1))
|
||||
: (divisibleBy * (quotient - 1));
|
||||
|
||||
// if true, then inputNumber1 is the required closest number
|
||||
if (Math.abs(inputNumber - inputNumber1) < Math.abs(inputNumber - inputNumber2)) return inputNumber1;
|
||||
|
||||
// else inputNumber2 is the required closest number
|
||||
return inputNumber2;
|
||||
}
|
||||
|
||||
export const getNewNodePosition = (nodes: INodeUi[], newPosition: XYPosition, movePosition?: XYPosition): XYPosition => {
|
||||
const targetPosition: XYPosition = [...newPosition];
|
||||
|
||||
targetPosition[0] = targetPosition[0] - (targetPosition[0] % GRID_SIZE);
|
||||
targetPosition[1] = targetPosition[1] - (targetPosition[1] % GRID_SIZE);
|
||||
targetPosition[0] = closestNumberDivisibleBy(targetPosition[0], GRID_SIZE);
|
||||
targetPosition[1] = closestNumberDivisibleBy(targetPosition[1], GRID_SIZE);
|
||||
|
||||
if (!movePosition) {
|
||||
movePosition = [40, 40];
|
||||
@@ -478,7 +511,8 @@ export const getRelativePosition = (x: number, y: number, scale: number, offset:
|
||||
|
||||
export const getMidCanvasPosition = (scale: number, offset: XYPosition): XYPosition => {
|
||||
const { editorWidth, editorHeight } = getContentDimensions();
|
||||
return getRelativePosition((editorWidth - SIDEBAR_WIDTH) / 2, (editorHeight - HEADER_HEIGHT) / 2, scale, offset);
|
||||
|
||||
return getRelativePosition(editorWidth / 2, (editorHeight - HEADER_HEIGHT) / 2, scale, offset);
|
||||
};
|
||||
|
||||
export const getBackgroundStyles = (scale: number, offsetPosition: XYPosition) => {
|
||||
@@ -630,16 +664,14 @@ const getContentDimensions = (): { editorWidth: number, editorHeight: number } =
|
||||
};
|
||||
};
|
||||
|
||||
export const getZoomToFit = (nodes: INodeUi[], addComponentPadding = true): {offset: XYPosition, zoomLevel: number} => {
|
||||
const {minX, minY, maxX, maxY} = getWorkflowCorners(nodes);
|
||||
export const getZoomToFit = (nodes: INodeUi[], addFooterPadding = true): {offset: XYPosition, zoomLevel: number} => {
|
||||
const { minX, minY, maxX, maxY } = getWorkflowCorners(nodes);
|
||||
const { editorWidth, editorHeight } = getContentDimensions();
|
||||
const sidebarWidth = addComponentPadding ? SIDEBAR_WIDTH : 0;
|
||||
const headerHeight = addComponentPadding ? HEADER_HEIGHT: 0;
|
||||
const footerHeight = addComponentPadding ? 200 : 100;
|
||||
const footerHeight = addFooterPadding ? 200 : 100;
|
||||
|
||||
const PADDING = NODE_SIZE * 4;
|
||||
|
||||
const diffX = maxX - minX + sidebarWidth + PADDING;
|
||||
const diffX = maxX - minX + PADDING;
|
||||
const scaleX = editorWidth / diffX;
|
||||
|
||||
const diffY = maxY - minY + PADDING;
|
||||
@@ -648,14 +680,14 @@ export const getZoomToFit = (nodes: INodeUi[], addComponentPadding = true): {off
|
||||
const zoomLevel = Math.min(scaleX, scaleY, 1);
|
||||
|
||||
let xOffset = (minX * -1) * zoomLevel; // find top right corner
|
||||
xOffset += (editorWidth - sidebarWidth - (maxX - minX) * zoomLevel) / 2; // add padding to center workflow
|
||||
xOffset += (editorWidth - (maxX - minX) * zoomLevel) / 2; // add padding to center workflow
|
||||
|
||||
let yOffset = (minY * -1) * zoomLevel + headerHeight; // find top right corner
|
||||
yOffset += (editorHeight - headerHeight - (maxY - minY + footerHeight) * zoomLevel) / 2; // add padding to center workflow
|
||||
let yOffset = (minY * -1) * zoomLevel; // find top right corner
|
||||
yOffset += (editorHeight - (maxY - minY + footerHeight) * zoomLevel) / 2; // add padding to center workflow
|
||||
|
||||
return {
|
||||
zoomLevel,
|
||||
offset: [xOffset, yOffset - headerHeight],
|
||||
offset: [closestNumberDivisibleBy(xOffset, GRID_SIZE), closestNumberDivisibleBy(yOffset, GRID_SIZE)],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user