mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 19:32:15 +00:00
Merge branch 'master' into save-changes-warning
This commit is contained in:
@@ -126,6 +126,7 @@ export interface IRestApi {
|
||||
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>; // tslint:disable-line:no-any
|
||||
getSettings(): Promise<IN8nUISettings>;
|
||||
getNodeTypes(): Promise<INodeTypeDescription[]>;
|
||||
getNodesInformation(nodeList: string[]): Promise<INodeTypeDescription[]>;
|
||||
getNodeParameterOptions(nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]>;
|
||||
removeTestWebhook(workflowId: string): Promise<boolean>;
|
||||
runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>;
|
||||
@@ -399,6 +400,10 @@ export interface IN8nUISettings {
|
||||
timezone: string;
|
||||
executionTimeout: number;
|
||||
maxExecutionTimeout: number;
|
||||
oauthCallbackUrls: {
|
||||
oauth1: string;
|
||||
oauth2: string;
|
||||
};
|
||||
urlBaseWebhook: string;
|
||||
versionCli: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div name="title" class="title-container" slot="title">
|
||||
<div class="title-left">{{title}}</div>
|
||||
<div class="title-right">
|
||||
<div v-if="credentialType" class="docs-container">
|
||||
<div v-if="credentialType && documentationUrl" class="docs-container">
|
||||
<svg class="help-logo" target="_blank" width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Node Documentation</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
@@ -20,7 +20,7 @@
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<span v-if="credentialType" class="doc-link-text">Need help? <a class="doc-hyperlink" :href="'https://docs.n8n.io/credentials/' + documentationUrl + '/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal'" target="_blank">Open credential docs</a></span>
|
||||
<span class="doc-link-text">Need help? <a class="doc-hyperlink" :href="'https://docs.n8n.io/credentials/' + documentationUrl + '/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal'" target="_blank">Open credential docs</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,27 +109,19 @@ export default mixins(
|
||||
}
|
||||
}
|
||||
},
|
||||
documentationUrl (): string {
|
||||
documentationUrl (): string | undefined {
|
||||
let credentialTypeName = '';
|
||||
if (this.editCredentials) {
|
||||
const credentialType = this.$store.getters.credentialType(this.editCredentials.type);
|
||||
if (credentialType.documentationUrl === undefined) {
|
||||
return credentialType.name;
|
||||
} else {
|
||||
return `${credentialType.documentationUrl}`;
|
||||
}
|
||||
credentialTypeName = this.editCredentials.type as string;
|
||||
} else {
|
||||
if (this.credentialType) {
|
||||
const credentialType = this.$store.getters.credentialType(this.credentialType);
|
||||
|
||||
if (credentialType.documentationUrl === undefined) {
|
||||
return credentialType.name;
|
||||
} else {
|
||||
return `${credentialType.documentationUrl}`;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
credentialTypeName = this.credentialType as string;
|
||||
}
|
||||
|
||||
const credentialType = this.$store.getters.credentialType(credentialTypeName);
|
||||
if (credentialType.documentationUrl !== undefined) {
|
||||
return `${credentialType.documentationUrl}`;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
node (): INodeUi {
|
||||
return this.$store.getters.activeNode;
|
||||
|
||||
@@ -235,7 +235,7 @@ export default mixins(
|
||||
oAuthCallbackUrl (): string {
|
||||
const types = this.parentTypes(this.credentialTypeData.name);
|
||||
const oauthType = (this.credentialTypeData.name === 'oAuth2Api' || types.includes('oAuth2Api')) ? 'oauth2' : 'oauth1';
|
||||
return this.$store.getters.getWebhookBaseUrl + `rest/${oauthType}-credential/callback`;
|
||||
return this.$store.getters.oauthCallbackUrls[oauthType];
|
||||
},
|
||||
requiredPropertiesFilled (): boolean {
|
||||
for (const property of this.credentialProperties) {
|
||||
@@ -404,10 +404,11 @@ export default mixins(
|
||||
message: 'Connected successfully!',
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
// Make sure that the event gets removed again
|
||||
window.removeEventListener('message', receiveMessage, false);
|
||||
}
|
||||
|
||||
// Make sure that the event gets removed again
|
||||
window.removeEventListener('message', receiveMessage, false);
|
||||
};
|
||||
|
||||
window.addEventListener('message', receiveMessage, false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="node-wrapper" :style="nodePosition">
|
||||
<div class="node-default" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick">
|
||||
<div class="node-default" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd">
|
||||
<div v-if="hasIssues" class="node-info-icon node-issues">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<div slot="content" v-html="nodeIssues"></div>
|
||||
@@ -13,19 +13,19 @@
|
||||
<font-awesome-icon icon="sync-alt" spin />
|
||||
</div>
|
||||
<div class="node-options" v-if="!isReadOnly">
|
||||
<div @click.stop.left="deleteNode" class="option" title="Delete Node" >
|
||||
<div v-touch:tap="deleteNode" class="option" title="Delete Node" >
|
||||
<font-awesome-icon icon="trash" />
|
||||
</div>
|
||||
<div @click.stop.left="disableNode" class="option" title="Activate/Deactivate Node" >
|
||||
<div v-touch:tap="disableNode" class="option" title="Activate/Deactivate Node" >
|
||||
<font-awesome-icon :icon="nodeDisabledIcon" />
|
||||
</div>
|
||||
<div @click.stop.left="duplicateNode" class="option" title="Duplicate Node" >
|
||||
<div v-touch:tap="duplicateNode" class="option" title="Duplicate Node" >
|
||||
<font-awesome-icon icon="clone" />
|
||||
</div>
|
||||
<div @click.stop.left="setNodeActive" class="option touch" title="Edit Node" v-if="!isReadOnly">
|
||||
<div v-touch:tap="setNodeActive" class="option touch" title="Edit Node" v-if="!isReadOnly">
|
||||
<font-awesome-icon class="execute-icon" icon="cog" />
|
||||
</div>
|
||||
<div @click.stop.left="executeNode" class="option" title="Execute Node" v-if="!isReadOnly && !workflowRunning">
|
||||
<div v-touch:tap="executeNode" class="option" title="Execute Node" v-if="!isReadOnly && !workflowRunning">
|
||||
<font-awesome-icon class="execute-icon" icon="play-circle" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,6 +110,10 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
classes.push('is-touch-device');
|
||||
}
|
||||
|
||||
if (this.isTouchActive) {
|
||||
classes.push('touch-active');
|
||||
}
|
||||
|
||||
return classes;
|
||||
},
|
||||
nodeIssues (): string {
|
||||
@@ -134,7 +138,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
}
|
||||
|
||||
if (this.nodeType !== null && this.nodeType.subtitle !== undefined) {
|
||||
return this.workflow.getSimpleParameterValue(this.data as INode, this.nodeType.subtitle) as string | undefined;
|
||||
return this.workflow.expression.getSimpleParameterValue(this.data as INode, this.nodeType.subtitle) as string | undefined;
|
||||
}
|
||||
|
||||
if (this.data.parameters.operation !== undefined) {
|
||||
@@ -174,7 +178,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints,
|
||||
isTouchActive: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -199,6 +203,14 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
setNodeActive () {
|
||||
this.$store.commit('setActiveNode', this.data.name);
|
||||
},
|
||||
touchStart () {
|
||||
if (this.isTouchDevice === true && this.isMacOs === false && this.isTouchActive === false) {
|
||||
this.isTouchActive = true;
|
||||
setTimeout(() => {
|
||||
this.isTouchActive = false;
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -268,6 +280,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
}
|
||||
}
|
||||
|
||||
&.touch-active,
|
||||
&:hover {
|
||||
.node-execute {
|
||||
display: initial;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="node-icon-wrapper" :style="iconStyleData">
|
||||
<div class="node-icon-wrapper" :style="iconStyleData" :class="{full: isSvgIcon}">
|
||||
<div v-if="nodeIconData !== null" class="icon">
|
||||
<img :src="nodeIconData.path" style="width: 100%; height: 100%;" v-if="nodeIconData.type === 'file'"/>
|
||||
<font-awesome-icon :icon="nodeIconData.path" v-else-if="nodeIconData.type === 'fa'" />
|
||||
@@ -17,6 +17,7 @@ import Vue from 'vue';
|
||||
interface NodeIconData {
|
||||
type: string;
|
||||
path: string;
|
||||
fileExtension?: string;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
@@ -41,6 +42,12 @@ export default Vue.extend({
|
||||
'border-radius': Math.ceil(size / 2) + 'px',
|
||||
};
|
||||
},
|
||||
isSvgIcon (): boolean {
|
||||
if (this.nodeIconData && this.nodeIconData.type === 'file' && this.nodeIconData.fileExtension === 'svg') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
nodeIconData (): null | NodeIconData {
|
||||
if (this.nodeType === null) {
|
||||
return null;
|
||||
@@ -51,13 +58,14 @@ export default Vue.extend({
|
||||
if (this.nodeType.icon) {
|
||||
let type, path;
|
||||
[type, path] = this.nodeType.icon.split(':');
|
||||
const returnData = {
|
||||
const returnData: NodeIconData = {
|
||||
type,
|
||||
path,
|
||||
};
|
||||
|
||||
if (type === 'file') {
|
||||
returnData.path = restUrl + '/node-icon/' + this.nodeType.name;
|
||||
returnData.fileExtension = path.split('.').slice(-1).join();
|
||||
}
|
||||
|
||||
return returnData;
|
||||
@@ -83,6 +91,10 @@ export default Vue.extend({
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
|
||||
&.full .icon {
|
||||
margin: 0.24em;
|
||||
}
|
||||
|
||||
.node-icon-placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ export default mixins(
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If active, the workflow continues even if this node\'s <br /execution fails. When this occurs, the node passes along input data from<br />previous nodes - so your workflow should account for unexpected output data.',
|
||||
description: 'If active, the workflow continues even if this node\'s <br />execution fails. When this occurs, the node passes along input data from<br />previous nodes - so your workflow should account for unexpected output data.',
|
||||
},
|
||||
] as INodeProperties[],
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import Vue from 'vue';
|
||||
export default Vue.extend(
|
||||
{
|
||||
name: 'PageContentWrapper',
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
@@ -250,7 +250,7 @@ export default mixins(
|
||||
* @returns
|
||||
* @memberof Workflow
|
||||
*/
|
||||
getNodeOutputData (runData: IRunData, nodeName: string, filterText: string, itemIndex = 0, runIndex = 0, inputName = 'main', outputIndex = 0): IVariableSelectorOption[] | null {
|
||||
getNodeOutputData (runData: IRunData, nodeName: string, filterText: string, itemIndex = 0, runIndex = 0, inputName = 'main', outputIndex = 0, useShort = false): IVariableSelectorOption[] | null {
|
||||
if (!runData.hasOwnProperty(nodeName)) {
|
||||
// No data found for node
|
||||
return null;
|
||||
@@ -291,9 +291,12 @@ export default mixins(
|
||||
|
||||
// Get json data
|
||||
if (outputData.hasOwnProperty('json')) {
|
||||
|
||||
const jsonPropertyPrefix = useShort === true ? '$json' : `$node["${nodeName}"].json`;
|
||||
|
||||
const jsonDataOptions: IVariableSelectorOption[] = [];
|
||||
for (const propertyName of Object.keys(outputData.json)) {
|
||||
jsonDataOptions.push.apply(jsonDataOptions, this.jsonDataToFilterOption(outputData.json[propertyName], `$node["${nodeName}"].json`, propertyName, filterText));
|
||||
jsonDataOptions.push.apply(jsonDataOptions, this.jsonDataToFilterOption(outputData.json[propertyName], jsonPropertyPrefix, propertyName, filterText));
|
||||
}
|
||||
|
||||
if (jsonDataOptions.length) {
|
||||
@@ -308,6 +311,9 @@ export default mixins(
|
||||
|
||||
// Get binary data
|
||||
if (outputData.hasOwnProperty('binary')) {
|
||||
|
||||
const binaryPropertyPrefix = useShort === true ? '$binary' : `$node["${nodeName}"].binary`;
|
||||
|
||||
const binaryData = [];
|
||||
let binaryPropertyData = [];
|
||||
|
||||
@@ -326,7 +332,7 @@ export default mixins(
|
||||
binaryPropertyData.push(
|
||||
{
|
||||
name: propertyName,
|
||||
key: `$node["${nodeName}"].binary.${dataPropertyName}.${propertyName}`,
|
||||
key: `${binaryPropertyPrefix}.${dataPropertyName}.${propertyName}`,
|
||||
value: outputData.binary![dataPropertyName][propertyName],
|
||||
},
|
||||
);
|
||||
@@ -336,7 +342,7 @@ export default mixins(
|
||||
binaryData.push(
|
||||
{
|
||||
name: dataPropertyName,
|
||||
key: `$node["${nodeName}"].binary.${dataPropertyName}`,
|
||||
key: `${binaryPropertyPrefix}.${dataPropertyName}`,
|
||||
options: this.sortOptions(binaryPropertyData),
|
||||
allowParentSelect: true,
|
||||
},
|
||||
@@ -347,7 +353,7 @@ export default mixins(
|
||||
returnData.push(
|
||||
{
|
||||
name: 'Binary',
|
||||
key: `$node["${nodeName}"].binary`,
|
||||
key: binaryPropertyPrefix,
|
||||
options: this.sortOptions(binaryData),
|
||||
allowParentSelect: true,
|
||||
},
|
||||
@@ -474,7 +480,7 @@ export default mixins(
|
||||
// (example "IF" node. If node is connected to "true" or to "false" output)
|
||||
const outputIndex = this.workflow.getNodeConnectionOutputIndex(activeNode.name, parentNode[0], 'main');
|
||||
|
||||
tempOutputData = this.getNodeOutputData(runData, parentNode[0], filterText, itemIndex, 0, 'main', outputIndex) as IVariableSelectorOption[];
|
||||
tempOutputData = this.getNodeOutputData(runData, parentNode[0], filterText, itemIndex, 0, 'main', outputIndex, true) as IVariableSelectorOption[];
|
||||
|
||||
if (tempOutputData) {
|
||||
if (JSON.stringify(tempOutputData).length < 102400) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export const deviceSupportHelpers = Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints,
|
||||
isMacOs: /(ipad|iphone|ipod|mac)/i.test(navigator.platform),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// TODO: Check if used anywhere
|
||||
controlKeyCode(): string {
|
||||
if (this.isMacOs) {
|
||||
return 'Meta';
|
||||
}
|
||||
return 'Control';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isCtrlKeyPressed(e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isTouchDevice === true) {
|
||||
return true;
|
||||
}
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
}
|
||||
return e.ctrlKey;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -2,20 +2,19 @@ import { INodeUi } from '@/Interface';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
|
||||
export const mouseSelect = mixins(nodeIndex).extend({
|
||||
export const mouseSelect = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
data () {
|
||||
return {
|
||||
selectActive: false,
|
||||
selectBox: document.createElement('span'),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isMacOs (): boolean {
|
||||
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.createSelectBox();
|
||||
},
|
||||
@@ -34,6 +33,9 @@ export const mouseSelect = mixins(nodeIndex).extend({
|
||||
this.$el.appendChild(this.selectBox);
|
||||
},
|
||||
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isTouchDevice === true) {
|
||||
return true;
|
||||
}
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
}
|
||||
@@ -125,6 +127,13 @@ export const mouseSelect = mixins(nodeIndex).extend({
|
||||
},
|
||||
mouseUpMouseSelect (e: MouseEvent) {
|
||||
if (this.selectActive === false) {
|
||||
if (this.isTouchDevice === true) {
|
||||
// @ts-ignore
|
||||
if (e.target && e.target.id.includes('node-view')) {
|
||||
// Deselect all nodes
|
||||
this.deselectAllNodes();
|
||||
}
|
||||
}
|
||||
// If it is not active return direcly.
|
||||
// Else normal node dragging will not work.
|
||||
return;
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
// @ts-ignore
|
||||
import normalizeWheel from 'normalize-wheel';
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
|
||||
export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
export const moveNodeWorkflow = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
data () {
|
||||
return {
|
||||
moveLastPosition: [0, 0],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
controlKeyCode (): string {
|
||||
if (this.isMacOs) {
|
||||
return 'Meta';
|
||||
}
|
||||
return 'Control';
|
||||
},
|
||||
isMacOs (): boolean {
|
||||
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
}
|
||||
return e.ctrlKey;
|
||||
getMousePosition(e: MouseEvent | TouchEvent) {
|
||||
// @ts-ignore
|
||||
const x = e.pageX !== undefined ? e.pageX : (e.touches && e.touches[0] && e.touches[0].pageX ? e.touches[0].pageX : 0);
|
||||
// @ts-ignore
|
||||
const y = e.pageY !== undefined ? e.pageY : (e.touches && e.touches[0] && e.touches[0].pageY ? e.touches[0].pageY : 0);
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
};
|
||||
},
|
||||
moveWorkflow (e: MouseEvent) {
|
||||
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
|
||||
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] + (e.pageX - this.moveLastPosition[0]);
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] + (e.pageY - this.moveLastPosition[1]);
|
||||
const position = this.getMousePosition(e);
|
||||
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] + (position.x - this.moveLastPosition[0]);
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] + (position.y - this.moveLastPosition[1]);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY], setStateDirty: true});
|
||||
|
||||
// Update the last position
|
||||
this.moveLastPosition[0] = e.pageX;
|
||||
this.moveLastPosition[1] = e.pageY;
|
||||
this.moveLastPosition[0] = position.x;
|
||||
this.moveLastPosition[1] = position.y;
|
||||
},
|
||||
mouseDownMoveWorkflow (e: MouseEvent) {
|
||||
if (this.isCtrlKeyPressed(e) === false) {
|
||||
@@ -51,8 +53,10 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
|
||||
this.$store.commit('setNodeViewMoveInProgress', true);
|
||||
|
||||
this.moveLastPosition[0] = e.pageX;
|
||||
this.moveLastPosition[1] = e.pageY;
|
||||
const position = this.getMousePosition(e);
|
||||
|
||||
this.moveLastPosition[0] = position.x;
|
||||
this.moveLastPosition[1] = position.y;
|
||||
|
||||
// @ts-ignore
|
||||
this.$el.addEventListener('mousemove', this.mouseMoveNodeWorkflow);
|
||||
@@ -72,6 +76,15 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
// Nothing else to do. Simply leave the node view at the current offset
|
||||
},
|
||||
mouseMoveNodeWorkflow (e: MouseEvent) {
|
||||
// @ts-ignore
|
||||
if (e.target && !e.target.id.includes('node-view')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$store.getters.isActionActive('dragActive')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.buttons === 0) {
|
||||
// Mouse button is not pressed anymore so stop selection mode
|
||||
// Happens normally when mouse leave the view pressed and then
|
||||
@@ -84,9 +97,10 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
|
||||
this.moveWorkflow(e);
|
||||
},
|
||||
wheelMoveWorkflow (e: WheelEvent) {
|
||||
const normalized = normalizeWheel(e);
|
||||
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] - e.deltaX;
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] - e.deltaY;
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] - normalized.pixelX;
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] - normalized.pixelY;
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY], setStateDirty: true});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,20 +2,20 @@ import { IConnectionsUi, IEndpointOptions, INodeUi, XYPositon } from '@/Interfac
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
import { NODE_NAME_PREFIX } from '@/constants';
|
||||
|
||||
export const nodeBase = mixins(nodeIndex).extend({
|
||||
export const nodeBase = mixins(
|
||||
deviceSupportHelpers,
|
||||
nodeIndex,
|
||||
).extend({
|
||||
mounted () {
|
||||
// Initialize the node
|
||||
if (this.data !== null) {
|
||||
this.__addNode(this.data);
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
data (): INodeUi {
|
||||
return this.$store.getters.nodeByName(this.name);
|
||||
@@ -26,9 +26,6 @@ export const nodeBase = mixins(nodeIndex).extend({
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isMacOs (): boolean {
|
||||
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
|
||||
},
|
||||
nodeName (): string {
|
||||
return NODE_NAME_PREFIX + this.nodeIndex;
|
||||
},
|
||||
@@ -336,26 +333,27 @@ export const nodeBase = mixins(nodeIndex).extend({
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
|
||||
if (this.isMacOs) {
|
||||
return e.metaKey;
|
||||
}
|
||||
return e.ctrlKey;
|
||||
},
|
||||
|
||||
mouseLeftClick (e: MouseEvent) {
|
||||
if (this.$store.getters.isActionActive('dragActive')) {
|
||||
this.$store.commit('removeActiveAction', 'dragActive');
|
||||
} else {
|
||||
if (this.isCtrlKeyPressed(e) === false) {
|
||||
this.$emit('deselectAllNodes');
|
||||
touchEnd(e: MouseEvent) {
|
||||
if (this.isTouchDevice) {
|
||||
if (this.$store.getters.isActionActive('dragActive')) {
|
||||
this.$store.commit('removeActiveAction', 'dragActive');
|
||||
}
|
||||
|
||||
if (this.$store.getters.isNodeSelected(this.data.name)) {
|
||||
this.$emit('deselectNode', this.name);
|
||||
}
|
||||
},
|
||||
mouseLeftClick (e: MouseEvent) {
|
||||
if (!this.isTouchDevice) {
|
||||
if (this.$store.getters.isActionActive('dragActive')) {
|
||||
this.$store.commit('removeActiveAction', 'dragActive');
|
||||
} else {
|
||||
this.$emit('nodeSelected', this.name);
|
||||
if (this.isCtrlKeyPressed(e) === false) {
|
||||
this.$emit('deselectAllNodes');
|
||||
}
|
||||
|
||||
if (this.$store.getters.isNodeSelected(this.data.name)) {
|
||||
this.$emit('deselectNode', this.name);
|
||||
} else {
|
||||
this.$emit('nodeSelected', this.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -152,6 +152,10 @@ export const restApi = Vue.extend({
|
||||
return self.restApi().makeRestApiRequest('GET', `/node-types`);
|
||||
},
|
||||
|
||||
getNodesInformation: (nodeList: string[]): Promise<INodeTypeDescription[]> => {
|
||||
return self.restApi().makeRestApiRequest('POST', `/node-types`, {nodeNames: nodeList});
|
||||
},
|
||||
|
||||
// Returns all the parameter options from the server
|
||||
getNodeParameterOptions: (nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]> => {
|
||||
const sendData = {
|
||||
|
||||
@@ -360,7 +360,7 @@ export const workflowHelpers = mixins(
|
||||
connectionInputData = [];
|
||||
}
|
||||
|
||||
return workflow.getParameterValue(expression, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, true);
|
||||
return workflow.expression.getParameterValue(expression, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, true);
|
||||
},
|
||||
|
||||
// Saves the currently loaded workflow to the database.
|
||||
|
||||
@@ -5,6 +5,7 @@ import Vue from 'vue';
|
||||
import 'prismjs';
|
||||
import 'prismjs/themes/prism.css';
|
||||
import 'vue-prism-editor/dist/VuePrismEditor.css';
|
||||
import Vue2TouchEvents from 'vue2-touch-events';
|
||||
|
||||
import * as ElementUI from 'element-ui';
|
||||
// @ts-ignore
|
||||
@@ -91,6 +92,9 @@ import {
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
|
||||
import { store } from './store';
|
||||
|
||||
Vue.use(Vue2TouchEvents);
|
||||
|
||||
Vue.use(ElementUI, { locale });
|
||||
|
||||
library.add(faAngleDoubleLeft);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
IConnection,
|
||||
IConnections,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
INodeConnections,
|
||||
INodeIssueData,
|
||||
INodeTypeDescription,
|
||||
@@ -56,6 +57,7 @@ export const store = new Vuex.Store({
|
||||
executionTimeout: -1,
|
||||
maxExecutionTimeout: Number.MAX_SAFE_INTEGER,
|
||||
versionCli: '0.0.0',
|
||||
oauthCallbackUrls: {},
|
||||
workflowExecutionData: null as IExecutionResponse | null,
|
||||
lastSelectedNode: null as string | null,
|
||||
lastSelectedNodeOutputIndex: null as number | null,
|
||||
@@ -535,6 +537,9 @@ export const store = new Vuex.Store({
|
||||
setVersionCli (state, version: string) {
|
||||
Vue.set(state, 'versionCli', version);
|
||||
},
|
||||
setOauthCallbackUrls(state, urls: IDataObject) {
|
||||
Vue.set(state, 'oauthCallbackUrls', urls);
|
||||
},
|
||||
|
||||
addNodeType (state, typeData: INodeTypeDescription) {
|
||||
if (!typeData.hasOwnProperty('name')) {
|
||||
@@ -602,6 +607,14 @@ export const store = new Vuex.Store({
|
||||
Vue.set(state.workflow, 'settings', {});
|
||||
}
|
||||
},
|
||||
|
||||
updateNodeTypes (state, nodeTypes: INodeTypeDescription[]) {
|
||||
const updatedNodeNames = nodeTypes.map(node => node.name) as string[];
|
||||
const oldNodesNotChanged = state.nodeTypes.filter(node => !updatedNodeNames.includes(node.name));
|
||||
const updatedNodes = [...oldNodesNotChanged, ...nodeTypes];
|
||||
Vue.set(state, 'nodeTypes', updatedNodes);
|
||||
state.nodeTypes = updatedNodes;
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
|
||||
@@ -658,6 +671,9 @@ export const store = new Vuex.Store({
|
||||
versionCli: (state): string => {
|
||||
return state.versionCli;
|
||||
},
|
||||
oauthCallbackUrls: (state): object => {
|
||||
return state.oauthCallbackUrls;
|
||||
},
|
||||
|
||||
// Push Connection
|
||||
pushConnectionActive: (state): boolean => {
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
<div
|
||||
class="node-view-wrapper"
|
||||
:class="workflowClasses"
|
||||
@touchstart="mouseDown"
|
||||
@touchend="mouseUp"
|
||||
@touchmove="mouseMoveNodeWorkflow"
|
||||
@mousedown="mouseDown"
|
||||
v-touch:tap="touchTap"
|
||||
@mouseup="mouseUp"
|
||||
@wheel="wheelScroll"
|
||||
>
|
||||
<div class="node-view-background" :style="backgroundStyle"></div>
|
||||
<div id="node-view-background" class="node-view-background" :style="backgroundStyle"></div>
|
||||
<div id="node-view" class="node-view" :style="workflowStyle">
|
||||
<node
|
||||
v-for="nodeData in nodes"
|
||||
@@ -356,14 +360,20 @@ export default mixins(
|
||||
|
||||
return data;
|
||||
},
|
||||
mouseDown (e: MouseEvent) {
|
||||
touchTap (e: MouseEvent | TouchEvent) {
|
||||
if (this.isTouchDevice) {
|
||||
this.mouseDown(e);
|
||||
}
|
||||
},
|
||||
mouseDown (e: MouseEvent | TouchEvent) {
|
||||
// Save the location of the mouse click
|
||||
const position = this.getMousePosition(e);
|
||||
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
|
||||
this.lastClickPosition[0] = e.pageX - offsetPosition[0];
|
||||
this.lastClickPosition[1] = e.pageY - offsetPosition[1];
|
||||
this.lastClickPosition[0] = position.x - offsetPosition[0];
|
||||
this.lastClickPosition[1] = position.y - offsetPosition[1];
|
||||
|
||||
this.mouseDownMouseSelect(e);
|
||||
this.mouseDownMoveWorkflow(e);
|
||||
this.mouseDownMouseSelect(e as MouseEvent);
|
||||
this.mouseDownMoveWorkflow(e as MouseEvent);
|
||||
|
||||
// Hide the node-creator
|
||||
this.createNodeActive = false;
|
||||
@@ -962,7 +972,7 @@ export default mixins(
|
||||
// If a node is active then add the new node directly after the current one
|
||||
// newNodeData.position = [activeNode.position[0], activeNode.position[1] + 60];
|
||||
newNodeData.position = this.getNewNodePosition(
|
||||
[lastSelectedNode.position[0] + 150, lastSelectedNode.position[1]],
|
||||
[lastSelectedNode.position[0] + 200, lastSelectedNode.position[1]],
|
||||
[100, 0],
|
||||
);
|
||||
} else {
|
||||
@@ -1456,6 +1466,11 @@ export default mixins(
|
||||
[0, 150],
|
||||
);
|
||||
|
||||
if (newNodeData.webhookId) {
|
||||
// Make sure that the node gets a new unique webhook-ID
|
||||
newNodeData.webhookId = uuidv4();
|
||||
}
|
||||
|
||||
await this.addNodes([newNodeData]);
|
||||
|
||||
// Automatically deselect all nodes and select the current one and also active
|
||||
@@ -1593,6 +1608,11 @@ export default mixins(
|
||||
return;
|
||||
}
|
||||
|
||||
// Before proceeding we must check if all nodes contain the `properties` attribute.
|
||||
// Nodes are loaded without this information so we must make sure that all nodes
|
||||
// being added have this information.
|
||||
await this.loadNodesProperties(nodes.map(node => node.type));
|
||||
|
||||
// Add the node to the node-list
|
||||
let nodeType: INodeTypeDescription | null;
|
||||
let foundNodeIssues: INodeIssues | null;
|
||||
@@ -1703,6 +1723,9 @@ export default mixins(
|
||||
let oldName: string;
|
||||
let newName: string;
|
||||
const createNodes: INode[] = [];
|
||||
|
||||
await this.loadNodesProperties(data.nodes.map(node => node.type));
|
||||
|
||||
data.nodes.forEach(node => {
|
||||
if (nodeTypesCount[node.type] !== undefined) {
|
||||
if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) {
|
||||
@@ -1745,6 +1768,10 @@ export default mixins(
|
||||
for (type of Object.keys(currentConnections[sourceNode])) {
|
||||
connection[type] = [];
|
||||
for (sourceIndex = 0; sourceIndex < currentConnections[sourceNode][type].length; sourceIndex++) {
|
||||
if (!currentConnections[sourceNode][type][sourceIndex]) {
|
||||
// There is so something wrong with the data so ignore
|
||||
continue;
|
||||
}
|
||||
const nodeSourceConnections = [];
|
||||
for (connectionIndex = 0; connectionIndex < currentConnections[sourceNode][type][sourceIndex].length; connectionIndex++) {
|
||||
const nodeConnection: NodeInputConnections = [];
|
||||
@@ -1908,6 +1935,7 @@ export default mixins(
|
||||
this.$store.commit('setExecutionTimeout', settings.executionTimeout);
|
||||
this.$store.commit('setMaxExecutionTimeout', settings.maxExecutionTimeout);
|
||||
this.$store.commit('setVersionCli', settings.versionCli);
|
||||
this.$store.commit('setOauthCallbackUrls', settings.oauthCallbackUrls);
|
||||
},
|
||||
async loadNodeTypes (): Promise<void> {
|
||||
const nodeTypes = await this.restApi().getNodeTypes();
|
||||
@@ -1921,6 +1949,17 @@ export default mixins(
|
||||
const credentials = await this.restApi().getAllCredentials();
|
||||
this.$store.commit('setCredentials', credentials);
|
||||
},
|
||||
async loadNodesProperties(nodeNames: string[]): Promise<void> {
|
||||
const allNodes = this.$store.getters.allNodeTypes;
|
||||
const nodesToBeFetched = allNodes.filter((node: INodeTypeDescription) => nodeNames.includes(node.name) && !node.hasOwnProperty('properties')).map((node: INodeTypeDescription) => node.name) as string[];
|
||||
if (nodesToBeFetched.length > 0) {
|
||||
// Only call API if node information is actually missing
|
||||
this.startLoading();
|
||||
const nodeInfo = await this.restApi().getNodesInformation(nodesToBeFetched);
|
||||
this.$store.commit('updateNodeTypes', nodeInfo);
|
||||
this.stopLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
|
||||
Reference in New Issue
Block a user