🔀 Merge branch 'master' into oauth-support

This commit is contained in:
Jan Oberhauser
2020-05-24 20:37:59 +02:00
18 changed files with 2491 additions and 247 deletions

View File

@@ -19,7 +19,16 @@
<div class="header">
<div class="title-text">
<strong>Results: {{ dataCount }}</strong>&nbsp;
<strong v-if="dataCount < this.MAX_DISPLAY_ITEMS_AUTO_ALL && dataSize < MAX_DISPLAY_DATA_SIZE">
Results: {{ dataCount }}
</strong>
<strong v-else>Results:
<el-select v-model="maxDisplayItems" @click.stop>
<el-option v-for="option in maxDisplayItemsOptions" :label="option" :value="option" :key="option" />
</el-select>&nbsp;/
{{ dataCount }}
</strong>
&nbsp;
<el-popover
v-if="runMetadata"
placement="right"
@@ -184,6 +193,11 @@ import {
ITableData,
} from '@/Interface';
import {
MAX_DISPLAY_DATA_SIZE,
MAX_DISPLAY_ITEMS_AUTO_ALL,
} from '@/constants';
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
import { genericHelpers } from '@/components/mixins/genericHelpers';
@@ -211,8 +225,12 @@ export default mixins(
runIndex: 0,
showData: false,
outputIndex: 0,
maxDisplayItems: 25 as number | null,
binaryDataDisplayVisible: false,
binaryDataDisplayData: null as IBinaryDisplayData | null,
MAX_DISPLAY_DATA_SIZE,
MAX_DISPLAY_ITEMS_AUTO_ALL,
};
},
computed: {
@@ -229,6 +247,9 @@ export default mixins(
const executionData: IRunExecutionData = this.workflowExecution.data;
return executionData.resultData.runData;
},
maxDisplayItemsOptions (): number[] {
return [25, 50, 100, 250, 500, 1000, this.dataCount].filter(option => option <= this.dataCount);
},
node (): INodeUi | null {
return this.$store.getters.activeNode;
},
@@ -323,19 +344,27 @@ export default mixins(
return 0;
},
jsonData (): IDataObject[] {
const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
let inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
if (inputData.length === 0 || !Array.isArray(inputData)) {
return [];
}
if (this.maxDisplayItems !== null) {
inputData = inputData.slice(0, this.maxDisplayItems);
}
return this.convertToJson(inputData);
},
tableData (): ITableData | undefined {
const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
let inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
if (inputData.length === 0) {
return undefined;
}
if (this.maxDisplayItems !== null) {
inputData = inputData.slice(0,this.maxDisplayItems);
}
return this.convertToTable(inputData);
},
binaryData (): IBinaryKeyData[] {
@@ -450,9 +479,12 @@ export default mixins(
// Check how much data there is to display
const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
this.dataSize = JSON.stringify(inputData).length;
if (this.dataSize < 204800) {
const jsonItems = inputData.slice(0, this.maxDisplayItems || inputData.length).map(item => item.json);
this.dataSize = JSON.stringify(jsonItems).length;
if (this.dataSize < this.MAX_DISPLAY_DATA_SIZE) {
// Data is reasonable small (< 200kb) so display it directly
this.showData = true;
}
@@ -466,6 +498,7 @@ export default mixins(
node (newNode, oldNode) {
// Reset the selected output index every time another node gets selected
this.outputIndex = 0;
this.maxDisplayItems = 25;
this.refreshDataSize();
},
jsonData () {

View File

@@ -65,6 +65,7 @@ export const nodeBase = mixins(nodeIndex).extend({
'name',
'nodeId',
'instance',
'isReadOnly',
],
methods: {
__addNode (node: INodeUi) {
@@ -182,7 +183,7 @@ export const nodeBase = mixins(nodeIndex).extend({
endpoint: inputData.endpoint,
endpointStyle: inputData.endpointStyle,
isSource: false,
isTarget: true,
isTarget: !this.isReadOnly,
parameters: {
nodeIndex: this.nodeIndex,
type: inputName,
@@ -246,7 +247,7 @@ export const nodeBase = mixins(nodeIndex).extend({
maxConnections: inputData.maxConnections,
endpoint: inputData.endpoint,
endpointStyle: inputData.endpointStyle,
isSource: true,
isSource: !this.isReadOnly,
isTarget: false,
parameters: {
nodeIndex: this.nodeIndex,
@@ -275,61 +276,63 @@ export const nodeBase = mixins(nodeIndex).extend({
this.instance.addEndpoint(this.nodeName, newEndpointData);
});
// Make nodes draggable
this.instance.draggable(this.nodeName, {
grid: [10, 10],
start: (params: { e: MouseEvent }) => {
if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) {
// Only the node which gets dragged directly gets an event, for all others it is
// undefined. So check if the currently dragged node is selected and if not clear
// the drag-selection.
this.instance.clearDragSelection();
this.$store.commit('resetSelectedNodes');
}
this.$store.commit('addActiveAction', 'dragActive');
},
stop: (params: { e: MouseEvent}) => {
if (this.$store.getters.isActionActive('dragActive')) {
const moveNodes = this.$store.getters.getSelectedNodes.slice();
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
if (!selectedNodeNames.includes(this.data.name)) {
// If the current node is not in selected add it to the nodes which
// got moved manually
moveNodes.push(this.data);
if (this.isReadOnly === false) {
// Make nodes draggable
this.instance.draggable(this.nodeName, {
grid: [10, 10],
start: (params: { e: MouseEvent }) => {
if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) {
// Only the node which gets dragged directly gets an event, for all others it is
// undefined. So check if the currently dragged node is selected and if not clear
// the drag-selection.
this.instance.clearDragSelection();
this.$store.commit('resetSelectedNodes');
}
// This does for some reason just get called once for the node that got clicked
// even though "start" and "drag" gets called for all. So lets do for now
// some dirty DOM query to get the new positions till I have more time to
// create a proper solution
let newNodePositon: XYPositon;
moveNodes.forEach((node: INodeUi) => {
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
const element = document.getElementById(nodeElement);
if (element === null) {
return;
this.$store.commit('addActiveAction', 'dragActive');
},
stop: (params: { e: MouseEvent }) => {
if (this.$store.getters.isActionActive('dragActive')) {
const moveNodes = this.$store.getters.getSelectedNodes.slice();
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
if (!selectedNodeNames.includes(this.data.name)) {
// If the current node is not in selected add it to the nodes which
// got moved manually
moveNodes.push(this.data);
}
newNodePositon = [
parseInt(element.style.left!.slice(0, -2), 10),
parseInt(element.style.top!.slice(0, -2), 10),
];
// This does for some reason just get called once for the node that got clicked
// even though "start" and "drag" gets called for all. So lets do for now
// some dirty DOM query to get the new positions till I have more time to
// create a proper solution
let newNodePositon: XYPositon;
moveNodes.forEach((node: INodeUi) => {
const nodeElement = `node-${this.getNodeIndex(node.name)}`;
const element = document.getElementById(nodeElement);
if (element === null) {
return;
}
const updateInformation = {
name: node.name,
properties: {
// @ts-ignore, draggable does not have definitions
position: newNodePositon,
},
};
newNodePositon = [
parseInt(element.style.left!.slice(0, -2), 10),
parseInt(element.style.top!.slice(0, -2), 10),
];
this.$store.commit('updateNodeProperties', updateInformation);
});
}
},
filter: '.node-description, .node-description .node-name, .node-description .node-subtitle',
});
const updateInformation = {
name: node.name,
properties: {
// @ts-ignore, draggable does not have definitions
position: newNodePositon,
},
};
this.$store.commit('updateNodeProperties', updateInformation);
});
}
},
filter: '.node-description, .node-description .node-name, .node-description .node-subtitle',
});
}
},
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {

View File

@@ -1,2 +1,4 @@
export const MAX_DISPLAY_DATA_SIZE = 204800;
export const MAX_DISPLAY_ITEMS_AUTO_ALL = 250;
export const NODE_NAME_PREFIX = 'node-';
export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__';

View File

@@ -20,6 +20,7 @@
:id="'node-' + getNodeIndex(nodeData.name)"
:key="getNodeIndex(nodeData.name)"
:name="nodeData.name"
:isReadOnly="isReadOnly"
:instance="instance"
></node>
</div>
@@ -102,6 +103,9 @@
<script lang="ts">
import Vue from 'vue';
import {
OverlaySpec,
} from 'jsplumb';
import { MessageBoxInputData } from 'element-ui/types/message-box';
import { jsPlumb, Endpoint, OnConnectionBindInfo } from 'jsplumb';
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
@@ -1013,21 +1017,9 @@ export default mixins(
}
},
initNodeView () {
this.instance.importDefaults({
// notice the 'curviness' argument to this Bezier curve.
// the curves on this page are far smoother
// than the curves on the first demo, which use the default curviness value.
// Connector: ["Bezier", { curviness: 80 }],
Connector: ['Bezier', { curviness: 40 }],
// @ts-ignore
Endpoint: ['Dot', { radius: 5 }],
DragOptions: { cursor: 'pointer', zIndex: 5000 },
PaintStyle: { strokeWidth: 2, stroke: '#334455' },
EndpointStyle: { radius: 9, fill: '#acd', stroke: 'red' },
// EndpointStyle: {},
HoverPaintStyle: { stroke: '#ff6d5a', lineWidth: 4 },
EndpointHoverStyle: { fill: '#ff6d5a', stroke: '#acd' },
ConnectionOverlays: [
const connectionOverlays: OverlaySpec[] = [];
if (this.isReadOnly === false) {
connectionOverlays.push.apply(connectionOverlays, [
[
'Arrow',
{
@@ -1045,7 +1037,24 @@ export default mixins(
location: 0.5,
},
],
],
]);
}
this.instance.importDefaults({
// notice the 'curviness' argument to this Bezier curve.
// the curves on this page are far smoother
// than the curves on the first demo, which use the default curviness value.
// Connector: ["Bezier", { curviness: 80 }],
Connector: ['Bezier', { curviness: 40 }],
// @ts-ignore
Endpoint: ['Dot', { radius: 5 }],
DragOptions: { cursor: 'pointer', zIndex: 5000 },
PaintStyle: { strokeWidth: 2, stroke: '#334455' },
EndpointStyle: { radius: 9, fill: '#acd', stroke: 'red' },
// EndpointStyle: {},
HoverPaintStyle: { stroke: '#ff6d5a', lineWidth: 4 },
EndpointHoverStyle: { fill: '#ff6d5a', stroke: '#acd' },
ConnectionOverlays: connectionOverlays,
Container: '#node-view',
});
@@ -1100,41 +1109,43 @@ export default mixins(
info.connection.setConnector(['Straight']);
}
// Display the connection-delete button only on hover
let timer: NodeJS.Timeout | undefined;
info.connection.bind('mouseover', (connection: IConnection) => {
if (timer !== undefined) {
clearTimeout(timer);
}
const overlay = info.connection.getOverlay('remove-connection');
overlay.setVisible(true);
});
info.connection.bind('mouseout', (connection: IConnection) => {
timer = setTimeout(() => {
const overlay = info.connection.getOverlay('remove-connection');
overlay.setVisible(false);
timer = undefined;
}, 500);
});
// @ts-ignore
info.connection.removeOverlay('drop-add-node');
// @ts-ignore
info.connection.addOverlay([
'Label',
{
id: 'remove-connection',
label: '<span class="delete-connection clickable" title="Delete Connection">x</span>',
cssClass: 'remove-connection-label',
visible: false,
events: {
mousedown: () => {
this.__removeConnectionByConnectionInfo(info, true);
if (this.isReadOnly === false) {
// Display the connection-delete button only on hover
let timer: NodeJS.Timeout | undefined;
info.connection.bind('mouseover', (connection: IConnection) => {
if (timer !== undefined) {
clearTimeout(timer);
}
const overlay = info.connection.getOverlay('remove-connection');
overlay.setVisible(true);
});
info.connection.bind('mouseout', (connection: IConnection) => {
timer = setTimeout(() => {
const overlay = info.connection.getOverlay('remove-connection');
overlay.setVisible(false);
timer = undefined;
}, 500);
});
// @ts-ignore
info.connection.addOverlay([
'Label',
{
id: 'remove-connection',
label: '<span class="delete-connection clickable" title="Delete Connection">x</span>',
cssClass: 'remove-connection-label',
visible: false,
events: {
mousedown: () => {
this.__removeConnectionByConnectionInfo(info, true);
},
},
},
},
]);
]);
}
// Display input names if they exist on connection
const targetNodeTypeData: INodeTypeDescription = this.$store.getters.nodeType(targetNode.type);
@@ -1329,6 +1340,7 @@ export default mixins(
// @ts-ignore
this.instance.connect({
uuids: uuid,
detachable: !this.isReadOnly,
});
} else {
// When nodes get connected it gets saved automatically to the storage