diff --git a/e2e/creatingASimpleGraph.spec.ts b/e2e/creatingASimpleGraph.spec.ts index f3402a3d..47fb0b56 100644 --- a/e2e/creatingASimpleGraph.spec.ts +++ b/e2e/creatingASimpleGraph.spec.ts @@ -50,6 +50,10 @@ test('Creating a Simple Graph', async ({ page }) => { //click on the input port of the file to open the parameter table modal and highlight the port await page.locator('#hello .inputPort').click(); + + // set 'changeable' on the port to true + await page.locator('.highlighted .column_Flags button.changeableFlag').click(); + //rename the port await page.locator('.highlighted .tableFieldDisplayName').fill('testInput'); diff --git a/src/Daliuge.ts b/src/Daliuge.ts index 030a3884..467e1ec1 100644 --- a/src/Daliuge.ts +++ b/src/Daliuge.ts @@ -51,6 +51,7 @@ export namespace Daliuge { EXECUTION_TIME = "execution_time", GROUP_START = "group_start", GROUP_END = "group_end", + FILE_PATH = "filepath", INPUT_ERROR_RATE = "input_error_threshold", NUM_OF_COPIES = "num_of_copies", @@ -171,8 +172,8 @@ export namespace Daliuge { export const groupStartField = new Field(null, FieldName.GROUP_START, "true", "true", "Is this node the start of a group?", false, DataType.Boolean, false, [], false, FieldType.Component, FieldUsage.NoPort); export const groupEndField = new Field(null, FieldName.GROUP_END, "true", "true", "Is this node the end of a group?", false, DataType.Boolean, false, [], false, FieldType.Component, FieldUsage.NoPort); - export const branchYesField = new Field(null, FieldName.TRUE, "", "", "The affirmative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort); - export const branchNoField = new Field(null, FieldName.FALSE, "", "", "he negative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort); + export const branchTrueField = new Field(null, FieldName.TRUE, "", "", "The affirmative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort); + export const branchFalseField = new Field(null, FieldName.FALSE, "", "", "The negative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort); export const dropClassField = new Field(null, FieldName.DROP_CLASS, "", "", "", false, DataType.String, false, [], false, FieldType.Component, FieldUsage.NoPort); @@ -264,8 +265,8 @@ export namespace Daliuge { Category.Branch ], fields: [ - Daliuge.branchYesField, - Daliuge.branchNoField, + Daliuge.branchTrueField, + Daliuge.branchFalseField, Daliuge.dropClassField ] }, diff --git a/src/Eagle.ts b/src/Eagle.ts index 470478d2..b2d50408 100644 --- a/src/Eagle.ts +++ b/src/Eagle.ts @@ -3824,7 +3824,9 @@ export class Eagle { const realSourceNode: Node = RightClick.edgeDropSrcNode; const realSourcePort: Field = RightClick.edgeDropSrcPort; const realDestNode: Node = nodes[0]; - let realDestPort = realDestNode.findPortByMatchingType(realSourcePort.getType(), !RightClick.edgeDropSrcIsInput); + + const usages: Daliuge.FieldUsage[] = [RightClick.edgeDropSrcIsInput ? Daliuge.FieldUsage.OutputPort : Daliuge.FieldUsage.InputPort, Daliuge.FieldUsage.InputOutput]; + let realDestPort = realDestNode.findPortByMatchingType(realSourcePort.getType(), usages); // if no dest port was found, just use first input port on dest node if (realDestPort === null){ @@ -4571,7 +4573,8 @@ export class Eagle { if (typeof intermediaryComponent === 'undefined'){ intermediaryComponent = new Node("Data", "Data Component", "", Category.Data); } else { - intermediaryComponent = Utils.duplicateNode(intermediaryComponent); + //intermediaryComponent = Utils.duplicateNode(intermediaryComponent); + intermediaryComponent = intermediaryComponent.clone().setId(Utils.generateNodeId()); } // if edge DOES NOT connect two applications, process normally @@ -4588,14 +4591,20 @@ export class Eagle { const newName = srcPort.getDisplayText(); const newDescription = srcPort.getDescription(); destNode.setName(newName); - destPort.setDisplayText(newName); - destPort.setDescription(newDescription); + + if (destPort.isChangeable()){ + destPort.setDisplayText(newName); + destPort.setDescription(newDescription); + } } else { const newName = destPort.getDisplayText(); const newDescription = destPort.getDescription(); srcNode.setName(newName); - srcPort.setDisplayText(newName); - srcPort.setDescription(newDescription); + + if (srcPort.isChangeable()){ + srcPort.setDisplayText(newName); + srcPort.setDescription(newDescription); + } } } @@ -4629,18 +4638,29 @@ export class Eagle { }; // Add the intermediary component to the graph - const newNode : Node = this.logicalGraph().addDataComponentToGraph(intermediaryComponent, dataComponentPosition); + //const newNode : Node = this.logicalGraph().addDataComponentToGraph(intermediaryComponent, dataComponentPosition); // DOESN't WORK!! io port is not rendered + const newNode = (await this.addNodeToLogicalGraph(intermediaryComponent, Utils.generateNodeId(), Eagle.AddNodeMode.Default))[0]; // WORKS!! (just location is not used) + + newNode.setPosition(dataComponentPosition.x, dataComponentPosition.y); // set name of new node (use user-facing name) newNode.setName(srcPort.getDisplayText()); - // remove existing ports from the memory node - newNode.removeAllInputPorts(); - newNode.removeAllOutputPorts(); - // add InputOutput port for dataType - const newInputOutputPort = new Field(Utils.generateFieldId(), srcPort.getDisplayText(), "", "", srcPort.getDescription(), false, srcPort.getType(), false, [], false, Daliuge.FieldType.Application, Daliuge.FieldUsage.InputOutput); - newNode.addField(newInputOutputPort); + + // find InputOutput port on node, which matches the source port dataType + const inputOutputPort = newNode.findPortByMatchingType(srcPort.getType(), [Daliuge.FieldUsage.InputOutput]); + if (inputOutputPort === null){ + Utils.showNotification("Add Edge Error", "Unable to find suitable port on intermediary component", "danger"); + reject("Unable to find suitable port on intermediary component"); + return; + } + + // if the port is changeable, set its display text and description to match the source port + if (inputOutputPort.isChangeable()){ + inputOutputPort.setDisplayText(srcPort.getDisplayText()); + inputOutputPort.setDescription(srcPort.getDescription()); + } // set the parent of the new node // by default, set parent to parent of dest node, @@ -4657,8 +4677,8 @@ export class Eagle { } // create TWO edges, one from src to data component, one from data component to dest - const firstEdge : Edge = new Edge('', srcNode, srcPort, newNode, newInputOutputPort, loopAware, closesLoop, false); - const secondEdge : Edge = new Edge('', newNode, newInputOutputPort, destNode, destPort, loopAware, closesLoop, false); + const firstEdge : Edge = new Edge('', srcNode, srcPort, newNode, inputOutputPort, loopAware, closesLoop, false); + const secondEdge : Edge = new Edge('', newNode, inputOutputPort, destNode, destPort, loopAware, closesLoop, false); this.logicalGraph().addEdgeComplete(firstEdge); this.logicalGraph().addEdgeComplete(secondEdge); diff --git a/src/Field.ts b/src/Field.ts index acdd4e59..1d9eece5 100644 --- a/src/Field.ts +++ b/src/Field.ts @@ -32,6 +32,9 @@ export class Field { private node : ko.Observable; private edges: ko.Observable>; + // run-time only attributes + private changeable : ko.Observable; + // graph related attributes private inputX : ko.Observable; private inputY : ko.Observable; @@ -65,6 +68,9 @@ export class Field { this.node = ko.observable(null); this.edges = ko.observable(new Map()); + // run-time only attributes + this.changeable = ko.observable(true); // whether the field can be renamed or not + //graph related things this.inputX = ko.observable(0); this.inputY = ko.observable(0); @@ -240,6 +246,15 @@ export class Field { return this.precious(); } + isChangeable = () : boolean => { + return this.changeable(); + } + + toggleChangeable = () : Field => { + this.changeable(!this.changeable()); + return this; + } + updateEdgeId(oldId: EdgeId, newId: EdgeId): void { const edge = this.edges().get(oldId); @@ -465,6 +480,7 @@ export class Field { this.isEvent(false); this.node(null); this.edges().clear(); + this.changeable(true); return this; } @@ -478,6 +494,7 @@ export class Field { const f = new Field(this.id(), this.displayText(), this.value(), this.defaultValue(), this.description(), this.readonly(), this.type(), this.precious(), options, this.positional(), this.parameterType(), this.usage()); f.encoding(this.encoding()); f.isEvent(this.isEvent()); + f.changeable(this.changeable()); f.node(this.node()); f.edges(new Map()); for (const edge of this.edges().values()) { @@ -506,6 +523,7 @@ export class Field { f.isEvent = this.isEvent; f.node = this.node; f.edges = this.edges; + f.changeable = this.changeable; return f; } @@ -544,6 +562,7 @@ export class Field { this.usage(src.usage()); this.encoding(src.encoding()); this.isEvent(src.isEvent()); + this.changeable(src.changeable()); // NOTE: these two are not copied from the src, but come from the function's parameters this.id(id); @@ -791,7 +810,7 @@ export class Field { }; } - static fromOJSJson(data : any) : Field { + static fromOJSJson(data : any, changeable: boolean) : Field { let id: FieldId = Utils.generateFieldId(); let name: string = ""; let description: string = ""; @@ -879,10 +898,11 @@ export class Field { const result = new Field(id, name, value, defaultValue, description, readonly, type, precious, options, positional, parameterType, usage); result.isEvent(isEvent); result.encoding(encoding); + result.changeable(changeable); return result; } - static fromOJSJsonPort(data : any) : Field { + static fromOJSJsonPort(data : any, changeable: boolean) : Field { let name: string = ""; let event: boolean = false; let type: Daliuge.DataType = Daliuge.DataType.Unknown; @@ -908,10 +928,11 @@ export class Field { const f = new Field(data.Id, name, "", "", description, false, type, false, [], false, Daliuge.FieldType.Unknown, Daliuge.FieldUsage.NoPort); f.isEvent(event); f.encoding(encoding); + f.changeable(changeable); return f; } - static fromV4Json(data: any): Field { + static fromV4Json(data: any, changeable: boolean): Field { let id: FieldId; let name: string; let value: string; @@ -961,6 +982,7 @@ export class Field { const f = new Field(id, name, value, defaultValue, description, readonly, type, precious, options, positional, parameterType, usage); f.isEvent(event); f.encoding(encoding); + f.changeable(changeable); return f; } diff --git a/src/Node.ts b/src/Node.ts index 81bb2d10..13b8926e 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -933,21 +933,10 @@ export class Node { return null; } - - findPortByMatchingType = (type: string, input: boolean) : Field | null => { - if (input){ - // check input ports - for (const inputPort of this.getInputPorts()){ - if (Utils.typesMatch(inputPort.getType(), type)){ - return inputPort; - } - } - } else { - // check output ports - for (const outputPort of this.getOutputPorts()){ - if (Utils.typesMatch(outputPort.getType(), type)){ - return outputPort; - } + findPortByMatchingType = (type: string, usages: Daliuge.FieldUsage[]) : Field | null => { + for (const port of this.getFields()){ + if (usages.includes(port.getUsage()) && Utils.typesMatch(port.getType(), type)){ + return port; } } return null; @@ -1596,7 +1585,7 @@ export class Node { // add fields if (typeof nodeData.fields !== 'undefined'){ for (const fieldData of nodeData.fields){ - const field = Field.fromOJSJson(fieldData); + const field = Field.fromOJSJson(fieldData, !isPaletteNode); // if the parameter type is not specified, assume it is a ComponentParameter if (field.getParameterType() === Daliuge.FieldType.Unknown){ @@ -1610,7 +1599,7 @@ export class Node { // add application params if (typeof nodeData.applicationArgs !== 'undefined'){ for (const paramData of nodeData.applicationArgs){ - const field = Field.fromOJSJson(paramData); + const field = Field.fromOJSJson(paramData, !isPaletteNode); field.setParameterType(Daliuge.FieldType.Application); node.addField(field); } @@ -1620,7 +1609,7 @@ export class Node { if (typeof nodeData.inputAppFields !== 'undefined'){ for (const fieldData of nodeData.inputAppFields){ if (node.hasInputApplication()){ - const field = Field.fromOJSJson(fieldData); + const field = Field.fromOJSJson(fieldData, !isPaletteNode); node.inputApplication().addField(field); } else { errorsWarnings.errors.push(Errors.Message("Can't add input app field " + fieldData.text + " to node " + node.getName() + ". No input application.")); @@ -1632,7 +1621,7 @@ export class Node { if (typeof nodeData.outputAppFields !== 'undefined'){ for (const fieldData of nodeData.outputAppFields){ if (node.hasOutputApplication()){ - const field = Field.fromOJSJson(fieldData); + const field = Field.fromOJSJson(fieldData, !isPaletteNode); node.outputApplication().addField(field); } else { errorsWarnings.errors.push(Errors.Message("Can't add output app field " + fieldData.text + " to node " + node.getName() + ". No output application.")); @@ -1643,7 +1632,7 @@ export class Node { // add input ports if (typeof nodeData.inputPorts !== 'undefined'){ for (const inputPort of nodeData.inputPorts){ - const port = Field.fromOJSJsonPort(inputPort); + const port = Field.fromOJSJsonPort(inputPort, !isPaletteNode); port.setParameterType(Daliuge.FieldType.Application); port.setUsage(Daliuge.FieldUsage.InputPort); @@ -1662,7 +1651,7 @@ export class Node { // add output ports if (typeof nodeData.outputPorts !== 'undefined'){ for (const outputPort of nodeData.outputPorts){ - const port = Field.fromOJSJsonPort(outputPort); + const port = Field.fromOJSJsonPort(outputPort, !isPaletteNode); port.setParameterType(Daliuge.FieldType.Application); port.setUsage(Daliuge.FieldUsage.OutputPort); @@ -1682,7 +1671,7 @@ export class Node { if (typeof nodeData.inputLocalPorts !== 'undefined'){ for (const inputLocalPort of nodeData.inputLocalPorts){ if (node.hasInputApplication()){ - const port = Field.fromOJSJsonPort(inputLocalPort); + const port = Field.fromOJSJsonPort(inputLocalPort, !isPaletteNode); port.setParameterType(Daliuge.FieldType.Application); port.setUsage(Daliuge.FieldUsage.OutputPort); @@ -1696,7 +1685,7 @@ export class Node { // add output local ports if (typeof nodeData.outputLocalPorts !== 'undefined'){ for (const outputLocalPort of nodeData.outputLocalPorts){ - const port = Field.fromOJSJsonPort(outputLocalPort); + const port = Field.fromOJSJsonPort(outputLocalPort, !isPaletteNode); port.setParameterType(Daliuge.FieldType.Application); port.setUsage(Daliuge.FieldUsage.InputPort); @@ -1750,7 +1739,7 @@ export class Node { // add fields for (const [id, fieldData] of Object.entries(nodeData.fields)){ - const field = Field.fromV4Json(fieldData); + const field = Field.fromV4Json(fieldData, !isPaletteNode); node.addField(field); } diff --git a/src/Palette.ts b/src/Palette.ts index a278e87a..dc8f333c 100644 --- a/src/Palette.ts +++ b/src/Palette.ts @@ -122,7 +122,7 @@ export class Palette { // add nodes for (const [nodeId, nodeData] of Object.entries(dataObject.nodes)){ - const node = Node.fromV4Json(nodeData, errorsWarnings, false); + const node = Node.fromV4Json(nodeData, errorsWarnings, true); result.nodes().set(nodeId as NodeId, node); result.nodes.valueHasMutated(); diff --git a/static/tables.css b/static/tables.css index 26dfb23d..4c4e57fa 100644 --- a/static/tables.css +++ b/static/tables.css @@ -83,7 +83,7 @@ } .parameter_table_flags{ - width: 101px; + width: 121px; } .parameter_table_actions{ diff --git a/templates/node_parameter_table.html b/templates/node_parameter_table.html index 933a5c00..4dfb3948 100644 --- a/templates/node_parameter_table.html +++ b/templates/node_parameter_table.html @@ -184,7 +184,7 @@
Node Fields Table: (No selection) - + + + + + +