fix(editor): fix performance issues when opening node or editing code node with a lot of data (#4388)

* debounce clicks

* debounce correctly

* debounce resize

* if initialized avoid

* set watcher fixes

* add deboucne for setting values

* increase debounce

* reset workspace for memory issues

* address comment

* decrease debounce time

* decrease debounce time

* clean up

* revert back to trailing

* support dbl
This commit is contained in:
Mutasem Aldmour
2022-10-20 15:45:58 +02:00
committed by GitHub
parent e83b9bd983
commit 356a42a187
4 changed files with 45 additions and 8 deletions

View File

@@ -12,7 +12,7 @@
:width="relativeWidthToPx(mainPanelDimensions.relativeWidth)" :width="relativeWidthToPx(mainPanelDimensions.relativeWidth)"
:minWidth="MIN_PANEL_WIDTH" :minWidth="MIN_PANEL_WIDTH"
:gridSize="20" :gridSize="20"
@resize="onResize" @resize="onResizeDebounced"
@resizestart="onResizeStart" @resizestart="onResizeStart"
@resizeend="onResizeEnd" @resizeend="onResizeEnd"
:supportedDirections="supportedResizeDirections" :supportedDirections="supportedResizeDirections"
@@ -47,6 +47,8 @@ import {
LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH, LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH,
MAIN_NODE_PANEL_WIDTH, MAIN_NODE_PANEL_WIDTH,
} from '@/constants'; } from '@/constants';
import mixins from 'vue-typed-mixins';
import { debounceHelper } from './mixins/debounce';
const SIDE_MARGIN = 24; const SIDE_MARGIN = 24;
@@ -63,7 +65,7 @@ const initialMainPanelWidth:{ [key: string]: number } = {
wide: MAIN_NODE_PANEL_WIDTH * 2, wide: MAIN_NODE_PANEL_WIDTH * 2,
}; };
export default Vue.extend({ export default mixins(debounceHelper).extend({
name: 'NDVDraggablePanels', name: 'NDVDraggablePanels',
components: { components: {
PanelDragButton, PanelDragButton,
@@ -83,11 +85,12 @@ export default Vue.extend({
default: () => ({}), default: () => ({}),
}, },
}, },
data(): { windowWidth: number, isDragging: boolean, MIN_PANEL_WIDTH: number} { data(): { windowWidth: number, isDragging: boolean, MIN_PANEL_WIDTH: number, initialized: boolean} {
return { return {
windowWidth: 1, windowWidth: 1,
isDragging: false, isDragging: false,
MIN_PANEL_WIDTH, MIN_PANEL_WIDTH,
initialized: false,
}; };
}, },
mounted() { mounted() {
@@ -105,6 +108,9 @@ export default Vue.extend({
window.addEventListener('resize', this.setTotalWidth); window.addEventListener('resize', this.setTotalWidth);
this.$emit('init', { position: this.mainPanelDimensions.relativeLeft }); this.$emit('init', { position: this.mainPanelDimensions.relativeLeft });
setTimeout(() => {
this.initialized = true;
}, 0);
}, },
destroyed() { destroyed() {
window.removeEventListener('resize', this.setTotalWidth); window.removeEventListener('resize', this.setTotalWidth);
@@ -295,6 +301,11 @@ export default Vue.extend({
onResizeEnd() { onResizeEnd() {
this.storePositionData(); this.storePositionData();
}, },
onResizeDebounced(data: { direction: string, x: number, width: number}) {
if (this.initialized) {
this.callDebounced('onResize', { debounceTime: 10, trailing: true }, data);
}
},
onResize({ direction, x, width }: { direction: string, x: number, width: number}) { onResize({ direction, x, width }: { direction: string, x: number, width: number}) {
const relativeDistance = this.pxToRelativeWidth(x); const relativeDistance = this.pxToRelativeWidth(x);
const relativeWidth = this.pxToRelativeWidth(width); const relativeWidth = this.pxToRelativeWidth(width);

View File

@@ -2,7 +2,7 @@
<div class="node-wrapper" :style="nodePosition" :id="nodeId"> <div class="node-wrapper" :style="nodePosition" :id="nodeId">
<div class="select-background" v-show="isSelected"></div> <div class="select-background" v-show="isSelected"></div>
<div :class="{'node-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}" :data-name="data.name" :ref="data.name"> <div :class="{'node-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}" :data-name="data.name" :ref="data.name">
<div :class="nodeClass" :style="nodeStyle" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd"> <div :class="nodeClass" :style="nodeStyle" @click.left="onClick" v-touch:start="touchStart" v-touch:end="touchEnd">
<div v-if="!data.disabled" :class="{'node-info-icon': true, 'shift-icon': shiftOutputCount}"> <div v-if="!data.disabled" :class="{'node-info-icon': true, 'shift-icon': shiftOutputCount}">
<div v-if="hasIssues" class="node-issues"> <div v-if="hasIssues" class="node-issues">
<n8n-tooltip placement="bottom" > <n8n-tooltip placement="bottom" >
@@ -112,6 +112,7 @@ import mixins from 'vue-typed-mixins';
import { get } from 'lodash'; import { get } from 'lodash';
import { getStyleTokenValue, getTriggerNodeServiceName } from './helpers'; import { getStyleTokenValue, getTriggerNodeServiceName } from './helpers';
import { INodeUi, XYPosition } from '@/Interface'; import { INodeUi, XYPosition } from '@/Interface';
import { debounceHelper } from './mixins/debounce';
export default mixins( export default mixins(
externalHooks, externalHooks,
@@ -119,6 +120,7 @@ export default mixins(
nodeHelpers, nodeHelpers,
workflowHelpers, workflowHelpers,
pinData, pinData,
debounceHelper,
).extend({ ).extend({
name: 'Node', name: 'Node',
components: { components: {
@@ -426,6 +428,19 @@ export default mixins(
}); });
}, },
onClick(event: MouseEvent) {
this.callDebounced('onClickDebounced', { debounceTime: 300, trailing: true }, event);
},
onClickDebounced(event: MouseEvent) {
const isDoubleClick = event.detail >= 2;
if (isDoubleClick) {
this.setNodeActive();
} else {
this.mouseLeftClick(event);
}
},
setNodeActive () { setNodeActive () {
this.$store.commit('setActiveNode', this.data.name); this.$store.commit('setActiveNode', this.data.name);
this.pinDataDiscoveryTooltipVisible = false; this.pinDataDiscoveryTooltipVisible = false;

View File

@@ -352,7 +352,9 @@ export default mixins(
this.avgOutputRowHeight = 0; this.avgOutputRowHeight = 0;
this.avgInputRowHeight = 0; this.avgInputRowHeight = 0;
this.$store.commit('ui/setNDVSessionId'); setTimeout(() => {
this.$store.commit('ui/setNDVSessionId');
}, 0);
this.$externalHooks().run('dataDisplay.nodeTypeChanged', { this.$externalHooks().run('dataDisplay.nodeTypeChanged', {
nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()), nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()),
}); });
@@ -397,10 +399,14 @@ export default mixins(
this.runInputIndex = -1; this.runInputIndex = -1;
}, },
inputNodeName(nodeName: string | undefined) { inputNodeName(nodeName: string | undefined) {
this.$store.commit('ui/setInputNodeName', nodeName); setTimeout(() => {
this.$store.commit('ui/setInputNodeName', nodeName);
}, 0);
}, },
inputRun() { inputRun() {
this.$store.commit('ui/setInputRunIndex', this.inputRun); setTimeout(() => {
this.$store.commit('ui/setInputRunIndex', this.inputRun);
}, 0);
}, },
}, },
methods: { methods: {

View File

@@ -73,7 +73,7 @@
:mode="node.parameters.mode" :mode="node.parameters.mode"
:jsCode="node.parameters.jsCode" :jsCode="node.parameters.jsCode"
:isReadOnly="isReadOnly" :isReadOnly="isReadOnly"
@valueChanged="valueChanged" @valueChanged="valueChangedDebounced"
/> />
<div v-else-if="isEditor === true" class="code-edit clickable ph-no-capture" @click="displayEditDialog()"> <div v-else-if="isEditor === true" class="code-edit clickable ph-no-capture" @click="displayEditDialog()">
@@ -336,12 +336,14 @@ import { CUSTOM_API_CALL_KEY } from '@/constants';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { CODE_NODE_TYPE } from '@/constants'; import { CODE_NODE_TYPE } from '@/constants';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { debounceHelper } from './mixins/debounce';
export default mixins( export default mixins(
externalHooks, externalHooks,
nodeHelpers, nodeHelpers,
showMessage, showMessage,
workflowHelpers, workflowHelpers,
debounceHelper,
) )
.extend({ .extend({
name: 'parameter-input', name: 'parameter-input',
@@ -922,6 +924,9 @@ export default mixins(
this.$emit('textInput', parameterData); this.$emit('textInput', parameterData);
}, },
valueChangedDebounced (value: NodeParameterValueType | {} | Date) {
this.callDebounced('valueChanged', { debounceTime: 100 }, value);
},
valueChanged (value: NodeParameterValueType | {} | Date) { valueChanged (value: NodeParameterValueType | {} | Date) {
if (this.parameter.name === 'nodeCredentialType') { if (this.parameter.name === 'nodeCredentialType') {
this.activeCredentialType = value as string; this.activeCredentialType = value as string;