feat(editor): Update new canvas node handle label rendering mechanism and design (no-changelog) (#10611)

This commit is contained in:
Alex Grozav 2024-08-29 17:56:50 +03:00 committed by GitHub
parent 402a8b40c0
commit 38eb00a643
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 135 additions and 38 deletions

View file

@ -72,6 +72,12 @@
--color-canvas-read-only-line: var(--prim-gray-800);
--color-canvas-selected: var(--prim-gray-0-alpha-025);
--color-canvas-selected-transparent: var(--color-canvas-selected);
--color-canvas-label-background: hsla(
var(--color-canvas-background-h),
var(--color-canvas-background-s),
var(--color-canvas-background-l),
0.85
);
// Nodes
--color-node-background: var(--prim-gray-740);

View file

@ -80,6 +80,12 @@
--color-canvas-read-only-line: var(--prim-gray-30);
--color-canvas-selected: var(--prim-gray-70);
--color-canvas-selected-transparent: hsla(var(--prim-gray-h), 47%, 30%, 0.1);
--color-canvas-label-background: hsla(
var(--color-canvas-background-h),
var(--color-canvas-background-s),
var(--color-canvas-background-l),
0.85
);
// Nodes
--color-node-background: var(--color-background-xlight);

View file

@ -22,13 +22,14 @@ const handleClasses = 'target';
.label {
position: absolute;
top: 20px;
left: 50%;
transform: translate(-50%, 0);
top: 50%;
left: calc(var(--spacing-xs) * -1);
transform: translate(-100%, -50%);
font-size: var(--font-size-2xs);
color: var(--color-foreground-xdark);
background: var(--color-background-light);
background: var(--color-canvas-label-background);
z-index: 1;
text-align: center;
white-space: nowrap;
}
</style>

View file

@ -1,17 +1,31 @@
<script lang="ts" setup>
import { useCanvasNodeHandle } from '@/composables/useCanvasNodeHandle';
import { useCanvasNode } from '@/composables/useCanvasNode';
import { computed, ref } from 'vue';
import type { CanvasNodeDefaultRender } from '@/types';
const emit = defineEmits<{
add: [];
}>();
const { render } = useCanvasNode();
const { label, isConnected, isConnecting } = useCanvasNodeHandle();
const handleClasses = 'source';
const isHovered = ref(false);
const renderOptions = computed(() => render.value.options as CanvasNodeDefaultRender['options']);
const isHandlePlusVisible = computed(() => !isConnecting.value || isHovered.value);
const isHovered = ref(false);
const plusLineSize = computed(
() =>
({
small: 46,
medium: 66,
large: 80,
})[renderOptions.value.outputs?.labelSize ?? 'small'],
);
function onMouseEnter() {
isHovered.value = true;
@ -33,6 +47,7 @@ function onClickAdd() {
<CanvasHandlePlus
v-if="!isConnected"
v-show="isHandlePlusVisible"
:line-size="plusLineSize"
:handle-classes="handleClasses"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
@ -53,12 +68,16 @@ function onClickAdd() {
.label {
position: absolute;
top: 50%;
left: var(--spacing-s);
left: var(--spacing-m);
transform: translate(0, -50%);
font-size: var(--font-size-2xs);
color: var(--color-foreground-xdark);
background: var(--color-background-light);
background: var(--color-canvas-label-background);
z-index: 1;
max-width: calc(100% - var(--spacing-m) - 24px);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View file

@ -67,9 +67,10 @@ function onClickAdd() {
transform: translate(-50%, 0);
font-size: var(--font-size-2xs);
color: var(--color-foreground-xdark);
background: var(--color-background-light);
background: var(--color-canvas-label-background);
z-index: 1;
text-align: center;
white-space: nowrap;
}
</style>

View file

@ -27,8 +27,9 @@ const handleClasses = 'source';
transform: translate(0%, 0);
font-size: var(--font-size-2xs);
color: var(--color-foreground-xdark);
background: var(--color-background-light);
background: var(--color-canvas-label-background);
z-index: 0;
white-space: nowrap;
}
:global(.vue-flow__handle:not(.connectionindicator)) .plus {

View file

@ -5,10 +5,14 @@ const props = withDefaults(
defineProps<{
position?: 'top' | 'right' | 'bottom' | 'left';
handleClasses?: string;
plusSize?: number;
lineSize?: number;
}>(),
{
position: 'right',
handleClasses: undefined,
plusSize: 24,
lineSize: 46,
},
);
@ -20,46 +24,48 @@ const style = useCssModule();
const classes = computed(() => [style.wrapper, style[props.position], props.handleClasses]);
const plusSize = 24;
const lineSize = 46;
const viewBox = computed(() => {
switch (props.position) {
case 'bottom':
case 'top':
return {
width: plusSize,
height: lineSize + plusSize,
width: props.plusSize,
height: props.lineSize + props.plusSize,
};
default:
return {
width: lineSize + plusSize,
height: plusSize,
width: props.lineSize + props.plusSize,
height: props.plusSize,
};
}
});
const styles = computed(() => ({
width: `${viewBox.value.width}px`,
height: `${viewBox.value.height}px`,
}));
const linePosition = computed(() => {
switch (props.position) {
case 'top':
return [
[viewBox.value.width / 2, viewBox.value.height - lineSize + 1],
[viewBox.value.width / 2, viewBox.value.height - props.lineSize + 1],
[viewBox.value.width / 2, viewBox.value.height],
];
case 'bottom':
return [
[viewBox.value.width / 2, 0],
[viewBox.value.width / 2, lineSize + 1],
[viewBox.value.width / 2, props.lineSize + 1],
];
case 'left':
return [
[viewBox.value.width - lineSize - 1, viewBox.value.height / 2],
[viewBox.value.width - props.lineSize - 1, viewBox.value.height / 2],
[viewBox.value.width, viewBox.value.height / 2],
];
default:
return [
[0, viewBox.value.height / 2],
[lineSize + 1, viewBox.value.height / 2],
[props.lineSize + 1, viewBox.value.height / 2],
];
}
});
@ -67,13 +73,13 @@ const linePosition = computed(() => {
const plusPosition = computed(() => {
switch (props.position) {
case 'bottom':
return [0, viewBox.value.height - plusSize];
return [0, viewBox.value.height - props.plusSize];
case 'top':
return [0, 0];
case 'left':
return [0, 0];
default:
return [viewBox.value.width - plusSize, 0];
return [viewBox.value.width - props.plusSize, 0];
}
});
@ -83,7 +89,7 @@ function onClick(event: MouseEvent) {
</script>
<template>
<svg :class="classes" :viewBox="`0 0 ${viewBox.width} ${viewBox.height}`">
<svg :class="classes" :viewBox="`0 0 ${viewBox.width} ${viewBox.height}`" :style="styles">
<line
:class="handleClasses"
:x1="linePosition[0][0]"
@ -121,18 +127,6 @@ function onClick(event: MouseEvent) {
<style lang="scss" module>
.wrapper {
position: relative;
&.top,
&.bottom {
width: 24px;
height: 70px;
}
&.left,
&.right {
width: 70px;
height: 24px;
}
}
.plus {

View file

@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`CanvasHandleDiamond > should render with default props 1`] = `
"<svg class="wrapper right" viewBox="0 0 70 24">
"<svg class="wrapper right" viewBox="0 0 70 24" style="width: 70px; height: 24px;">
<line class="" x1="0" y1="12" x2="47" y2="12" stroke="var(--color-foreground-xdark)" stroke-width="2"></line>
<g class="plus clickable" transform="translate(46, 0)">
<rect class="clickable" x="2" y="2" width="20" height="20" stroke="var(--color-foreground-xdark)" stroke-width="2" rx="4" fill="#ffffff"></rect>

View file

@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`CanvasHandlePlus > should render with default props 1`] = `
"<svg class="wrapper right" viewBox="0 0 70 24">
"<svg class="wrapper right" viewBox="0 0 70 24" style="width: 70px; height: 24px;">
<line class="" x1="0" y1="12" x2="47" y2="12" stroke="var(--color-foreground-xdark)" stroke-width="2"></line>
<g class="plus clickable" transform="translate(46, 0)">
<rect class="clickable" x="2" y="2" width="20" height="20" stroke="var(--color-foreground-xdark)" stroke-width="2" rx="4" fill="#ffffff"></rect>

View file

@ -133,6 +133,12 @@ describe('useCanvasMapping', () => {
configurable: false,
configuration: false,
trigger: true,
inputs: {
labelSize: 'small',
},
outputs: {
labelSize: 'small',
},
},
},
},
@ -264,6 +270,12 @@ describe('useCanvasMapping', () => {
configurable: false,
configuration: false,
trigger: true,
inputs: {
labelSize: 'small',
},
outputs: {
labelSize: 'small',
},
},
});
});

View file

@ -16,6 +16,7 @@ import type {
CanvasNodeAddNodesRender,
CanvasNodeData,
CanvasNodeDefaultRender,
CanvasNodeDefaultRenderLabelSize,
CanvasNodeStickyNoteRender,
} from '@/types';
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
@ -31,7 +32,7 @@ import type {
ITaskData,
Workflow,
} from 'n8n-workflow';
import { NodeHelpers } from 'n8n-workflow';
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow';
import type { INodeUi } from '@/Interface';
import { CUSTOM_API_CALL_KEY, STICKY_NODE_TYPE, WAIT_TIME_UNLIMITED } from '@/constants';
import { sanitizeHtml } from '@/utils/htmlUtils';
@ -78,6 +79,12 @@ export function useCanvasMapping({
trigger: nodeTypesStore.isTriggerNode(node.type),
configuration: nodeTypesStore.isConfigNode(workflowObject.value, node, node.type),
configurable: nodeTypesStore.isConfigurableNode(workflowObject.value, node, node.type),
inputs: {
labelSize: nodeInputLabelSizeById.value[node.id],
},
outputs: {
labelSize: nodeOutputLabelSizeById.value[node.id],
},
},
};
}
@ -142,6 +149,48 @@ export function useCanvasMapping({
}, {}),
);
function getLabelSize(label: string = ''): number {
if (label.length <= 2) {
return 0;
} else if (label.length <= 6) {
return 1;
} else {
return 2;
}
}
function getMaxNodePortsLabelSize(
ports: CanvasConnectionPort[],
): CanvasNodeDefaultRenderLabelSize {
const labelSizes: CanvasNodeDefaultRenderLabelSize[] = ['small', 'medium', 'large'];
const labelSizeIndexes = ports.reduce<number[]>(
(sizeAcc, input) => {
if (input.type === NodeConnectionType.Main) {
sizeAcc.push(getLabelSize(input.label ?? ''));
}
return sizeAcc;
},
[0],
);
return labelSizes[Math.max(...labelSizeIndexes)];
}
const nodeInputLabelSizeById = computed(() =>
nodes.value.reduce<Record<string, CanvasNodeDefaultRenderLabelSize>>((acc, node) => {
acc[node.id] = getMaxNodePortsLabelSize(nodeInputsById.value[node.id]);
return acc;
}, {}),
);
const nodeOutputLabelSizeById = computed(() =>
nodes.value.reduce<Record<string, CanvasNodeDefaultRenderLabelSize>>((acc, node) => {
acc[node.id] = getMaxNodePortsLabelSize(nodeOutputsById.value[node.id]);
return acc;
}, {}),
);
const nodeOutputsById = computed(() =>
nodes.value.reduce<Record<string, CanvasConnectionPort[]>>((acc, node) => {
const nodeTypeDescription = nodeTypesStore.getNodeType(node.type, node.typeVersion);

View file

@ -44,12 +44,20 @@ export const enum CanvasNodeRenderType {
AddNodes = 'n8n-nodes-internal.addNodes',
}
export type CanvasNodeDefaultRenderLabelSize = 'small' | 'medium' | 'large';
export type CanvasNodeDefaultRender = {
type: CanvasNodeRenderType.Default;
options: Partial<{
configurable: boolean;
configuration: boolean;
trigger: boolean;
inputs: {
labelSize: CanvasNodeDefaultRenderLabelSize;
};
outputs: {
labelSize: CanvasNodeDefaultRenderLabelSize;
};
}>;
};