💄 Improve editor-ui by displaying clearer what each node does

This commit is contained in:
Jan Oberhauser 2019-07-25 18:50:45 +02:00
parent 0c89445682
commit 8482e1b363
5 changed files with 108 additions and 139 deletions

View file

@ -9,33 +9,32 @@
<el-badge v-else :hidden="workflowDataItems === 0" class="node-info-icon data-count" :value="workflowDataItems"></el-badge>
<div class="node-executing-info" title="Node is executing">
<font-awesome-icon icon="spinner" spin />
</div>
<div class="node-execute" v-if="!isReadOnly && !workflowRunning">
<font-awesome-icon class="execute-icon" @click.stop.left="executeNode" icon="play-circle" title="Execute Node"/>
<font-awesome-icon icon="sync-alt" spin />
</div>
<div class="node-options" v-if="!isReadOnly">
<div @click.stop.left="deleteNode" class="option indent" title="Delete Node" >
<div @click.stop.left="deleteNode" class="option" title="Delete Node" >
<font-awesome-icon icon="trash" />
</div>
<div @click.stop.left="disableNode" class="option" title="Activate/Deactivate Node" >
<font-awesome-icon :icon="nodeDisabledIcon" />
</div>
<div @click.stop.left="duplicateNode" class="option" title="Duplicate Node" >
<font-awesome-icon icon="clone" />
</div>
<div @click.stop.left="disableNode" class="option indent" title="Activate/Deactivate Node" >
<font-awesome-icon :icon="nodeDisabledIcon" />
<div @click.stop.left="executeNode" class="option" title="Execute Node" v-if="!isReadOnly && !workflowRunning">
<font-awesome-icon class="execute-icon" icon="play-circle" />
</div>
</div>
<div class="node-description">
<div class="node-name" :title="data.name">
{{data.name}}
</div>
<div v-if="nodeSubtitle !== undefined" class="node-subtitle" :title="nodeSubtitle">
{{nodeSubtitle}}
</div>
</div>
<NodeIcon class="node-icon" :nodeType="nodeType" :style="nodeIconStyle"/>
<div class="node-name" :title="data.name">
{{data.name}}
</div>
<div v-if="nodeSubtitle !== undefined" class="node-subtitle" :title="nodeSubtitle">
{{nodeSubtitle}}
</div>
<div class="node-edit" @click.left.stop="setNodeActive" title="Edit Node">
<font-awesome-icon icon="pen" />
</div>
<NodeIcon class="node-icon" :nodeType="nodeType" size="60" :style="nodeIconStyle"/>
</div>
</template>
@ -90,10 +89,6 @@ export default mixins(nodeBase, workflowHelpers).extend({
classes.push('disabled');
}
if (this.nodeSubtitle) {
classes.push('has-subtitle');
}
if (this.isExecuting) {
classes.push('executing');
}
@ -203,35 +198,20 @@ export default mixins(nodeBase, workflowHelpers).extend({
.node-default {
position: absolute;
width: 160px;
height: 50px;
width: 100px;
height: 100px;
background-color: #fff;
border-radius: 25px;
text-align: center;
z-index: 24;
cursor: pointer;
color: #444;
line-height: 50px;
font-size: 0.8em;
font-weight: 600;
border: 1px dashed grey;
&.has-data {
border-style: solid;
}
&.has-subtitle {
line-height: 38px;
.node-info-icon {
top: -22px;
&.data-count {
top: -15px;
}
}
}
&.disabled {
color: #a0a0a0;
text-decoration: line-through;
@ -244,7 +224,7 @@ export default mixins(nodeBase, workflowHelpers).extend({
border-color: $--color-primary !important;
.node-executing-info {
display: initial;
display: inline-block;
}
}
@ -258,72 +238,44 @@ export default mixins(nodeBase, workflowHelpers).extend({
}
}
.node-edit {
.node-description {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 100%;
font-size: 1.1em;
color: #ccc;
border-radius: 0 25px 25px 0;
&:hover {
color: #00cc00;
}
.svg-inline--fa {
height: 100%;
}
}
.node-execute {
display: none;
position: absolute;
right: -25px;
width: 45px;
line-height: 50px;
font-size: 1.5em;
text-align: right;
z-index: 10;
color: #aaa;
.execute-icon:hover {
color: $--color-primary;
}
bottom: -70px;
left: -50px;
width: 200px;
height: 60px;
text-align: center;
}
.node-executing-info {
display: none;
position: absolute;
right: -35px;
top: 8px;
left: 0px;
top: 0px;
z-index: 12;
width: 30px;
height: 30px;
line-height: 30px;
font-size: 18px;
width: 100%;
height: 100%;
font-size: 3.75em;
line-height: 1.65em;
text-align: center;
border-radius: 15px;
background-color: $--color-primary-light;
color: $--color-primary;
color: rgba($--color-primary, 0.7);
}
.node-icon {
position: absolute;
top: 0;
height: 30px;
margin: 10px;
top: calc(50% - 30px);
left: calc(50% - 30px);
}
.node-info-icon {
position: absolute;
top: -28px;
right: 18px;
top: -18px;
right: 12px;
z-index: 10;
&.data-count {
top: -22px;
font-weight: 600;
top: -12px;
}
}
@ -338,14 +290,13 @@ export default mixins(nodeBase, workflowHelpers).extend({
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 37px;
font-weight: 500;
}
.node-subtitle {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: -23px 20px 0 20px;
font-weight: 400;
color: $--custom-font-light;
font-size: 0.9em;
@ -354,24 +305,29 @@ export default mixins(nodeBase, workflowHelpers).extend({
.node-options {
display: none;
position: absolute;
left: -28px;
width: 45px;
top: -8px;
line-height: 1.8em;
font-size: 12px;
top: -35px;
left: -10px;
width: 120px;
height: 45px;
font-size: 1em;
text-align: left;
z-index: 10;
color: #aaa;
text-align: center;
.option {
width: 20px;
text-align: center;
display: inline-block;
padding: 0 0.3em;
&:hover {
color: $--color-primary;
}
&.indent {
margin-left: 7px;
.execute-icon {
position: relative;
top: 2px;
font-size: 1.2em;
}
}
}

View file

@ -1,5 +1,5 @@
<template>
<div class="node-icon-wrapper">
<div class="node-icon-wrapper" :style="iconStyleData">
<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'" />
@ -23,8 +23,24 @@ export default Vue.extend({
name: 'NodeIcon',
props: [
'nodeType',
'size',
],
computed: {
iconStyleData (): object {
if (!this.size) {
return {};
}
let size = parseInt(this.size, 10);
return {
width: size + 'px',
height: size + 'px',
'font-size': Math.floor(parseInt(this.size, 10) * 0.6) + 'px',
'line-height': size + 'px',
'border-radius': Math.ceil(size / 2) + 'px',
}
},
nodeIconData (): null | NodeIconData {
if (this.nodeType === null) {
return null;
@ -57,22 +73,17 @@ export default Vue.extend({
.node-icon-wrapper {
width: 30px;
height: 30px;
border-radius: 20px;
border-radius: 15px;
color: #444;
line-height: 30px;
font-size: 1.1em;
overflow: hidden;
background-color: #fff;
text-align: center;
font-size: 12px;
font-weight: bold;
.icon {
font-size: 1.6em;
}
font-size: 20px;
.node-icon-placeholder {
font-size: 1.4em;
text-align: center;
}
}

View file

@ -64,17 +64,17 @@ export const nodeBase = mixins(nodeIndex).extend({
const nodeConnectors: IConnectionsUi = {
main: {
input: {
uuid: '-top',
uuid: '-input',
maxConnections: -1,
endpoint: 'Rectangle',
endpointStyle: { width: 24, height: 12, fill: '#555', stroke: '#555', strokeWidth: 0 },
endpointStyle: { width: 12, height: 24, fill: '#555', stroke: '#555', strokeWidth: 0 },
dragAllowedWhenFull: true,
},
output: {
uuid: '-bottom',
uuid: '-output',
maxConnections: -1,
endpoint: 'Dot',
endpointStyle: { radius: 9, fill: '#555', outlineStroke: 'none' },
endpointStyle: { radius: 11, fill: '#555', outlineStroke: 'none' },
dragAllowedWhenFull: true,
},
},
@ -94,30 +94,30 @@ export const nodeBase = mixins(nodeIndex).extend({
} = {
input: {
1: [
'Top',
'Left',
],
2: [
[0.3, 0, 0, -1],
[0.7, 0, 0, -1],
[0, 0.3, -1, 0],
[0, 0.7, -1, 0],
],
3: [
[0.25, 0, 0, -1],
[0.5, 0, 0, -1],
[0.75, 0, 0, -1],
[0, 0.25, -1, 0],
[0, 0.5, -1, 0],
[0, 0.75, -1, 0],
],
},
output: {
1: [
'Bottom',
'Right',
],
2: [
[0.3, 1, 0, 1],
[0.7, 1, 0, 1],
[1, 0.3, 1, 0],
[1, 0.7, 1, 0],
],
3: [
[0.25, 1, 0, 1],
[0.5, 1, 0, 1],
[0.75, 1, 0, 1],
[1, 0.25, 1, 0],
[1, 0.5, 1, 0],
[1, 0.75, 1, 0],
],
},
};

View file

@ -59,6 +59,7 @@ import {
faStop,
faSun,
faSync,
faSyncAlt,
faTable,
faTasks,
faTerminal,
@ -117,6 +118,7 @@ library.add(faSpinner);
library.add(faStop);
library.add(faSun);
library.add(faSync);
library.add(faSyncAlt);
library.add(faTable);
library.add(faTasks);
library.add(faTerminal);

View file

@ -428,13 +428,13 @@ export default mixins(
if (lastSelectedNode !== null) {
this.$store.commit('setActiveNode', lastSelectedNode.name);
}
} else if (e.key === 'ArrowDown' && e.shiftKey === true) {
} else if (e.key === 'ArrowRight' && e.shiftKey === true) {
// Select all downstream nodes
e.stopPropagation();
e.preventDefault();
this.callDebounced('selectDownstreamNodes', 1000);
} else if (e.key === 'ArrowDown') {
} else if (e.key === 'ArrowRight') {
// Set child node active
const lastSelectedNode = this.$store.getters.lastSelectedNode;
if (lastSelectedNode === null) {
@ -448,13 +448,13 @@ export default mixins(
}
this.callDebounced('nodeSelectedByName', 100, connections.main[0][0].node, false, true);
} else if (e.key === 'ArrowUp' && e.shiftKey === true) {
} else if (e.key === 'ArrowLeft' && e.shiftKey === true) {
// Select all downstream nodes
e.stopPropagation();
e.preventDefault();
this.callDebounced('selectUpstreamNodes', 1000);
} else if (e.key === 'ArrowUp') {
} else if (e.key === 'ArrowLeft') {
// Set parent node active
const lastSelectedNode = this.$store.getters.lastSelectedNode;
if (lastSelectedNode === null) {
@ -474,7 +474,7 @@ export default mixins(
}
this.callDebounced('nodeSelectedByName', 100, connections.main[0][0].node, false, true);
} else if (['ArrowLeft', 'ArrowRight'].includes(e.key)) {
} else if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
// Set sibling node as active
// Check first if it has a parent node
@ -504,7 +504,7 @@ export default mixins(
// Get all the sibling nodes and their x positions to know which one to set active
let siblingNode: INodeUi;
let lastCheckedNodePosition = e.key === 'ArrowLeft' ? -99999999 : 99999999;
let lastCheckedNodePosition = e.key === 'ArrowUp' ? -99999999 : 99999999;
let nextSelectNode: string | null = null;
for (const ouputConnections of connectionsParent.main) {
for (const ouputConnection of ouputConnections) {
@ -514,17 +514,17 @@ export default mixins(
}
siblingNode = this.$store.getters.nodeByName(ouputConnection.node);
if (e.key === 'ArrowLeft') {
if (e.key === 'ArrowUp') {
// Get the next node on the left
if (siblingNode.position[0] <= lastSelectedNode.position[0] && siblingNode.position[0] > lastCheckedNodePosition) {
if (siblingNode.position[1] <= lastSelectedNode.position[1] && siblingNode.position[1] > lastCheckedNodePosition) {
nextSelectNode = siblingNode.name;
lastCheckedNodePosition = siblingNode.position[0];
lastCheckedNodePosition = siblingNode.position[1];
}
} else {
// Get the next node on the right
if (siblingNode.position[0] >= lastSelectedNode.position[0] && siblingNode.position[0] < lastCheckedNodePosition) {
if (siblingNode.position[1] >= lastSelectedNode.position[1] && siblingNode.position[1] < lastCheckedNodePosition) {
nextSelectNode = siblingNode.name;
lastCheckedNodePosition = siblingNode.position[0];
lastCheckedNodePosition = siblingNode.position[1];
}
}
}
@ -929,8 +929,8 @@ 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], lastSelectedNode.position[1] + 50],
[0, 55]
[lastSelectedNode.position[0] + 180, lastSelectedNode.position[1]],
[110, 0]
);
} else {
// If no node is active find a free spot
@ -1068,8 +1068,8 @@ export default mixins(
const sourceNode = this.$store.getters.nodeByName(sourceNodeName);
// TODO: That should happen after each move (only the setConnector part)
if (info.sourceEndpoint.anchor.lastReturnValue[1] >= info.targetEndpoint.anchor.lastReturnValue[1]) {
// When the source is underneath the target it will make sure that
if (info.sourceEndpoint.anchor.lastReturnValue[0] >= info.targetEndpoint.anchor.lastReturnValue[0]) {
// When the source is before the target it will make sure that
// the connection is clearer visible
// Use the Flowchart connector if the source is underneath the target
@ -1088,7 +1088,7 @@ export default mixins(
// Change also the color to give an additional visual hint
info.connection.setPaintStyle({ strokeWidth: 2, stroke: '#334455' });
} else if (Math.abs(info.sourceEndpoint.anchor.lastReturnValue[0] - info.targetEndpoint.anchor.lastReturnValue[0]) < 30) {
} else if (Math.abs(info.sourceEndpoint.anchor.lastReturnValue[1] - info.targetEndpoint.anchor.lastReturnValue[1]) < 30) {
info.connection.setConnector(['Straight']);
}
@ -1274,8 +1274,8 @@ export default mixins(
__addConnection (connection: [IConnection, IConnection], addVisualConnection = false) {
if (addVisualConnection === true) {
const uuid: [string, string] = [
`${this.getNodeIndex(connection[0].node)}-bottom${connection[0].index}`,
`${this.getNodeIndex(connection[1].node)}-top${connection[1].index}`,
`${this.getNodeIndex(connection[0].node)}-output${connection[0].index}`,
`${this.getNodeIndex(connection[1].node)}-input${connection[1].index}`,
];
// Create connections in DOM
@ -1348,7 +1348,7 @@ export default mixins(
newNodeData.position = this.getNewNodePosition(
[node.position[0] + 180, node.position[1]],
[90, 0]
[0, 110]
);
await this.addNodes([newNodeData]);