diff --git a/src/Eagle.ts b/src/Eagle.ts index dea45148a..41e08b603 100644 --- a/src/Eagle.ts +++ b/src/Eagle.ts @@ -652,6 +652,11 @@ export class Eagle { setSelection = (rightWindowMode : Eagle.RightWindowMode, selection : Node | Edge, selectedLocation: Eagle.FileType) : void => { Eagle.selectedLocation(selectedLocation); GraphRenderer.clearPortPeek() + + // if(selection === null && this.selectedObjects().length === 0){ + // return + // } + if (selection === null){ this.selectedObjects([]); this.rightWindow().mode(rightWindowMode); diff --git a/src/Edge.ts b/src/Edge.ts index 5eafbbb50..ce7db80b7 100644 --- a/src/Edge.ts +++ b/src/Edge.ts @@ -42,7 +42,7 @@ export class Edge { private closesLoop : boolean; // indicates that this is a special type of edge that can be drawn in eagle to specify the start/end of groups. private selectionRelative : boolean // indicates if the edge is either selected or attached to a selected node private isShortEdge : ko.Observable; - private issues : {issue:Errors.Issue, validity:Errors.Validity}[] + private issues : ko.ObservableArray<{issue:Errors.Issue, validity:Errors.Validity}> //keeps track of edge errors constructor(srcNodeKey : number, srcPortId : string, destNodeKey : number, destPortId : string, loopAware: boolean, closesLoop: boolean, selectionRelative : boolean){ this._id = Utils.uuidv4(); @@ -56,7 +56,7 @@ export class Edge { this.closesLoop = closesLoop; this.selectionRelative = selectionRelative; this.isShortEdge = ko.observable(false) - this.issues = []; + this.issues = ko.observableArray([]); } getId = () : string => { @@ -169,16 +169,23 @@ export class Edge { return result; } - getErrorsWarnings = (eagle: Eagle): Errors.ErrorsWarnings => { - const result: {warnings: Errors.Issue[], errors: Errors.Issue[]} = {warnings: [], errors: []}; + getErrorsWarnings = (): Errors.ErrorsWarnings => { + const errorsWarnings : Errors.ErrorsWarnings = {warnings: [], errors: []}; + + this.getIssues().forEach(function(error){ + if(error.validity === Errors.Validity.Error || error.validity === Errors.Validity.Unknown){ + errorsWarnings.errors.push(error.issue) + }else{ + errorsWarnings.warnings.push(error.issue) + } + }) - Edge.isValid(eagle,false, this._id, this.srcNodeKey, this.srcPortId, this.destNodeKey, this.destPortId, this.loopAware, this.closesLoop, false, false, result); + return errorsWarnings; - return result; } - getissues = () : {issue:Errors.Issue, validity:Errors.Validity}[] => { - return this.issues; + getIssues = () : {issue:Errors.Issue, validity:Errors.Validity}[] => { + return this.issues(); } static toOJSJson(edge : Edge) : object { @@ -283,11 +290,10 @@ export class Edge { } static isValid(eagle: Eagle,autoSuggestMode:boolean, edgeId: string, sourceNodeKey : number, sourcePortId : string, destinationNodeKey : number, destinationPortId : string, loopAware: boolean, closesLoop: boolean, showNotification : boolean, showConsole : boolean, errorsWarnings: Errors.ErrorsWarnings) : Errors.Validity { - let impossibleEdge : boolean = false; const edge = eagle.logicalGraph().findEdgeById(edgeId) if(edge){ - edge.issues = [] //clear old issues + edge.issues([]) //clear old issues } // check for problems @@ -532,6 +538,6 @@ export class Edge { if (type === "warning" && errorsWarnings !== null){ errorsWarnings.warnings.push(issue); } - Eagle.getInstance().logicalGraph().findEdgeById(edgeId)?.issues.push({issue:issue, validity:linkValid}) + Eagle.getInstance().logicalGraph().findEdgeById(edgeId)?.issues().push({issue:issue, validity:linkValid}) } } diff --git a/src/Field.ts b/src/Field.ts index 5964df4f9..e3c351af5 100644 --- a/src/Field.ts +++ b/src/Field.ts @@ -41,8 +41,7 @@ export class Field { private inputAngle : number; private outputAngle : number; - private issues : {issue:Errors.Issue, validity:Errors.Validity}[] - // private errorsWarnings : ko.Observable; + private issues : ko.ObservableArray<{issue:Errors.Issue, validity:Errors.Validity}>//keeps track of issues on the field constructor(id: string, displayText: string, value: string, defaultValue: string, description: string, readonly: boolean, type: string, precious: boolean, options: string[], positional: boolean, parameterType: Daliuge.FieldType, usage: Daliuge.FieldUsage, keyAttribute: boolean){ this.displayText = ko.observable(displayText); @@ -74,8 +73,7 @@ export class Field { this.inputAngle = 0; this.outputAngle = 0; - this.issues = []; - // this.errorsWarnings = ko.observable({warnings: [], errors: []}); + this.issues = ko.observableArray([]) } getId = () : string => { @@ -361,11 +359,11 @@ export class Field { }, this); getIssues = (): {issue:Errors.Issue, validity:Errors.Validity}[] => { - return this.issues; + return this.issues(); } addError(issue:Errors.Issue, validity:Errors.Validity){ - this.issues.push({issue:issue,validity:validity}) + this.issues().push({issue:issue,validity:validity}) } getBackgroundColor : ko.PureComputed = ko.pureComputed(() => { @@ -819,7 +817,7 @@ export class Field { static isValid(node:Node, field:Field, selectedLocation:Eagle.FileType, fieldIndex:number){ const eagle = Eagle.getInstance() - field.issues = [] //clear old issues + field.issues([]) //clear old issues //checks for input ports if(field.isInputPort()){ @@ -844,7 +842,7 @@ export class Field { } } - field.issues.push({issue:issue,validity:Errors.Validity.Warning}) + field.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } @@ -873,7 +871,7 @@ export class Field { issue = Errors.ShowFix("Node " + node.getKey() + " (" + parentNode.getName() + ") has output application (" + node.getName() + ") with output port (" + field.getDisplayText() + ") whose type is not specified", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixFieldType(eagle, field)}, ""); } } - field.issues.push({issue:issue,validity:Errors.Validity.Warning}) + field.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } @@ -883,28 +881,28 @@ export class Field { //check that the field has an id if (field.getId() === "" || field.getId() === null){ const issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has field (" + field.getDisplayText() + ") with no id", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixFieldId(eagle, field)}, "Generate id for field"); - field.issues.push({issue:issue,validity:Errors.Validity.Error}) + field.issues().push({issue:issue,validity:Errors.Validity.Error}) // errorsWarnings.errors.push(issue); } // check that the field has a default value if (field.getDefaultValue() === "" && !field.isType(Daliuge.DataType.String) && !field.isType(Daliuge.DataType.Password) && !field.isType(Daliuge.DataType.Object) && !field.isType(Daliuge.DataType.Unknown)) { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has a component parameter (" + field.getDisplayText() + ") whose default value is not specified", function(){Utils.showField(eagle, node.getId(),field)}, function(){Utils.fixFieldDefaultValue(eagle, field)}, "Generate default value for parameter"); - field.issues.push({issue:issue,validity:Errors.Validity.Warning}) + field.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } //chack that the field has a known type if (!Utils.validateType(field.getType())) { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has a component parameter (" + field.getDisplayText() + ") whose type (" + field.getType() + ") is unknown", function(){Utils.showField(eagle, node.getId(),field)}, function(){Utils.fixFieldType(eagle, field)}, "Prepend existing type (" + field.getType() + ") with 'Object.'"); - field.issues.push({issue:issue,validity:Errors.Validity.Warning}) + field.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } // check that the fields "key" is the same as the key of the node it belongs to if (field.getNodeKey() !== node.getKey()) { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has a field (" + field.getDisplayText() + ") whose key (" + field.getNodeKey() + ") doesn't match the node (" + node.getKey() + ")", function(){Utils.showField(eagle, node.getId(),field)}, function(){Utils.fixFieldKey(eagle, node, field)}, "Set field node key correctly"); - field.issues.push({issue:issue,validity:Errors.Validity.Error}) + field.issues().push({issue:issue,validity:Errors.Validity.Error}) // errorsWarnings.errors.push(issue); } @@ -918,11 +916,11 @@ export class Field { if (field.getDisplayText() === field1.getDisplayText() && field.getParameterType() === field1.getParameterType()){ if (field.getId() === field1.getId()){ const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has multiple attributes with the same display text and id (" + field.getDisplayText() + ").", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixNodeMergeFieldsByIndex(eagle, node, fieldIndex, j)}, "Merge fields"); - field.issues.push({issue:issue,validity:Errors.Validity.Warning}) + field.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } else { const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has multiple attributes with the same display text (" + field.getDisplayText() + ").", function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixNodeMergeFields(eagle, node, field, field1)}, "Merge fields"); - field.issues.push({issue:issue,validity:Errors.Validity.Warning}) + field.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } } @@ -954,7 +952,7 @@ export class Field { const message = "Node " + node.getKey() + " (" + node.getName() + ") with category " + node.getCategory() + " contains field (" + field.getDisplayText() + ") with unsuitable type (" + field.getParameterType() + ")."; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showField(eagle, node.getId(),field);}, function(){Utils.fixFieldParameterType(eagle, node, field, suitableType)}, "Switch to suitable type, or remove if no suitable type"); - field.issues.push({issue:issue,validity:Errors.Validity.Warning}) + field.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } } diff --git a/src/GraphRenderer.ts b/src/GraphRenderer.ts index 21fe0c65c..c732f8462 100644 --- a/src/GraphRenderer.ts +++ b/src/GraphRenderer.ts @@ -1122,7 +1122,6 @@ export class GraphRenderer { const oldParent: Node = eagle.logicalGraph().findNodeByKeyQuiet(outerMostNode.getParentKey()); // keep track of whether we would update any node parents - const updated = {parent: false}; const allowGraphEditing = Setting.findValue(Setting.ALLOW_GRAPH_EDITING); //construct resizing if(outerMostNode.getParentKey() != null){ @@ -1140,13 +1139,13 @@ export class GraphRenderer { // if a parent was found, update if (parent !== null && outerMostNode.getParentKey() !== parent.getKey() && outerMostNode.getKey() !== parent.getKey() && !ancestorOfParent && !outerMostNode.isEmbedded()){ - GraphRenderer._updateNodeParent(outerMostNode, parent.getKey(), updated, allowGraphEditing); + GraphRenderer.updateNodeParent(outerMostNode, parent.getKey(), allowGraphEditing); GraphRenderer.NodeParentRadiusPreDrag = eagle.logicalGraph().findNodeByKeyQuiet(parent.getKey()).getRadius() } // if no parent found, update if (parent === null && outerMostNode.getParentKey() !== null && !outerMostNode.isEmbedded()){ - GraphRenderer._updateNodeParent(outerMostNode, null, updated, allowGraphEditing); + GraphRenderer.updateNodeParent(outerMostNode, null, allowGraphEditing); } if (oldParent !== null){ @@ -1594,14 +1593,13 @@ export class GraphRenderer { } // update the parent of the given node - // however, if allGraphEditing is false, then don't update - // always keep track of whether an update would have happened, sp we can warn user - static _updateNodeParent(node: Node, parentKey: number, updated: {parent: boolean}, allowGraphEditing: boolean): void { + // however, if allowGraphEditing is false, then don't update + static updateNodeParent(node: Node, parentKey: number, allowGraphEditing: boolean): void { if (node.getParentKey() !== parentKey){ if (allowGraphEditing){ node.setParentKey(parentKey); + Eagle.getInstance().checkGraph() } - updated.parent = true; } } @@ -2248,7 +2246,8 @@ export class GraphRenderer { } // check if link has a warning or is invalid - const linkValid : Errors.Validity = Edge.isValid(eagle,false, edge.getId(), edge.getSrcNodeKey(), edge.getSrcPortId(), edge.getDestNodeKey(), edge.getDestPortId(), edge.isLoopAware(), edge.isClosesLoop(), false, false, {errors:[], warnings:[]}); + // const linkValid : Errors.Validity = Edge.isValid(eagle,false, edge.getId(), edge.getSrcNodeKey(), edge.getSrcPortId(), edge.getDestNodeKey(), edge.getDestPortId(), edge.isLoopAware(), edge.isClosesLoop(), false, false, {errors:[], warnings:[]}); + const linkValid : Errors.Validity = Utils.worstEdgeError(edge.getErrorsWarnings()); if (linkValid === Errors.Validity.Error || linkValid === Errors.Validity.Impossible){ normalColor = GraphConfig.getColor('edgeInvalid'); diff --git a/src/LogicalGraph.ts b/src/LogicalGraph.ts index 82d079fb6..b3179a939 100644 --- a/src/LogicalGraph.ts +++ b/src/LogicalGraph.ts @@ -42,14 +42,14 @@ export class LogicalGraph { fileInfo : ko.Observable; private nodes : ko.ObservableArray; private edges : ko.ObservableArray; - private issues : {issue:Errors.Issue, validity:Errors.Validity}[] + private issues : ko.ObservableArray<{issue:Errors.Issue, validity:Errors.Validity}> //keeps track of higher level errors on the graph constructor(){ this.fileInfo = ko.observable(new FileInfo()); this.fileInfo().type = Eagle.FileType.Graph; this.nodes = ko.observableArray([]); this.edges = ko.observableArray([]); - this.issues = [] + this.issues = ko.observableArray([]) } static toOJSJson(graph : LogicalGraph, forTranslation : boolean) : object { @@ -316,7 +316,7 @@ export class LogicalGraph { } getIssues = (): {issue:Errors.Issue, validity:Errors.Validity}[] => { - return this.issues; + return this.issues(); } /** diff --git a/src/Node.ts b/src/Node.ts index e9c7b81a4..5017984e3 100644 --- a/src/Node.ts +++ b/src/Node.ts @@ -63,8 +63,7 @@ export class Node { private paletteDownloadUrl : ko.Observable; private dataHash : ko.Observable; - private issues : {issue:Errors.Issue, validity:Errors.Validity}[] - // private errorsWarnings : ko.Observable; + private issues : ko.ObservableArray<{issue:Errors.Issue, validity:Errors.Validity}>//keeps track of node level errors public static readonly DEFAULT_COLOR : string = "ffffff"; @@ -114,8 +113,7 @@ export class Node { this.paletteDownloadUrl = ko.observable(""); this.dataHash = ko.observable(""); - this.issues = []; - // this.errorsWarnings = ko.observable({warnings: [], errors: []}); + this.issues = ko.observableArray([]); //graph related things this.expanded = ko.observable(true); @@ -1135,7 +1133,7 @@ export class Node { } getIssues = (): {issue:Errors.Issue, validity:Errors.Validity}[] => { - return this.issues; + return this.issues(); } getAllErrors = () : {issue:Errors.Issue, validity:Errors.Validity}[] => { @@ -1978,13 +1976,13 @@ export class Node { static isValid(node: Node, selectedLocation: Eagle.FileType) : void { const eagle = Eagle.getInstance() - node.issues = []//clear old issues + node.issues([])//clear old issues // check that node has modern (not legacy) category if (node.getCategory() === Category.Component){ const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has legacy category (" + node.getCategory() + ")", function(){Utils.showNode(eagle, node.getId());}, function(){Utils.fixNodeCategory(eagle, node, Category.PythonApp, Category.Type.Application)}, ""); // errorsWarnings.warnings.push(issue); - node.issues.push({issue:issue,validity:Errors.Validity.Warning}) + node.issues().push({issue:issue,validity:Errors.Validity.Warning}) } // looping through and checking all the fields on the node @@ -2011,25 +2009,25 @@ export class Node { if (node.getInputPorts().length < cData.minInputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") may have too few input ports. A " + node.getCategory() + " component would typically have at least " + cData.minInputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - node.issues.push({issue:issue,validity:Errors.Validity.Warning}) + node.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } if ((node.getInputPorts().length - node.getInputEventPorts().length) > cData.maxInputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") has too many input ports. Should have at most " + cData.maxInputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - node.issues.push({issue:issue,validity:Errors.Validity.Warning}) + node.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } if (node.getOutputPorts().length < cData.minOutputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") may have too few output ports. A " + node.getCategory() + " component would typically have at least " + cData.minOutputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - node.issues.push({issue:issue,validity:Errors.Validity.Warning}) + node.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } if ((node.getOutputPorts().length - node.getOutputEventPorts().length) > cData.maxOutputs){ const message: string = "Node " + node.getKey() + " (" + node.getName() + ") may have too many output ports. Should have at most " + cData.maxOutputs; const issue: Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId())}, null, ""); - node.issues.push({issue:issue,validity:Errors.Validity.Warning}) + node.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } @@ -2046,7 +2044,7 @@ export class Node { // only check this if the component has been selected in the graph. If it was selected from the palette, it doesn't make sense to complain that it is not connected. if (!isConnected && !(cData.maxInputs === 0 && cData.maxOutputs === 0) && selectedLocation === Eagle.FileType.Graph){ const issue: Errors.Issue = Errors.ShowFix("Node " + node.getKey() + " (" + node.getName() + ") has no connected edges. It should be connected to the graph in some way", function(){Utils.showNode(eagle, node.getId())}, null, ""); - node.issues.push({issue:issue,validity:Errors.Validity.Warning}) + node.issues().push({issue:issue,validity:Errors.Validity.Warning}) // errorsWarnings.warnings.push(issue); } @@ -2054,19 +2052,19 @@ export class Node { if (node.hasInputApplication() && node.getInputApplication().getCategory() === Category.None){ const issue: Errors.Issue = Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has input application with category 'None'.") // errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has input application with category 'None'.")); - node.issues.push({issue:issue,validity:Errors.Validity.Error}); + node.issues().push({issue:issue,validity:Errors.Validity.Error}); } if (node.hasOutputApplication() && node.getOutputApplication().getCategory() === Category.None){ const issue : Errors.Issue = Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has output application with category 'None'.") // errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") has output application with category 'None'.")); - node.issues.push({issue:issue,validity:Errors.Validity.Error}); + node.issues().push({issue:issue,validity:Errors.Validity.Error}); } // check that Service nodes have inputApplications with no output ports! if (node.getCategory() === Category.Service && node.hasInputApplication() && node.getInputApplication().getOutputPorts().length > 0){ const issue : Errors.Issue = Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") is a Service node, but has an input application with at least one output.") // errorsWarnings.errors.push(Errors.Message("Node " + node.getKey() + " (" + node.getName() + ") is a Service node, but has an input application with at least one output.")); - node.issues.push({issue:issue,validity:Errors.Validity.Error}); + node.issues().push({issue:issue,validity:Errors.Validity.Error}); } // check that this category of node contains all the fields it requires @@ -2102,7 +2100,7 @@ export class Node { const message = "Node " + node.getKey() + " (" + node.getName() + ":" + node.category() + ":" + node.categoryType() + ") does not have the required '" + field.getDisplayText() + "' field"; const issue : Errors.Issue = Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId());}, function(){Utils.addMissingRequiredField(eagle, node, field);}, "Add missing " + field.getDisplayText() + " field.") // errorsWarnings.errors.push(Errors.ShowFix(message, function(){Utils.showNode(eagle, node.getId());}, function(){Utils.addMissingRequiredField(eagle, node, field);}, "Add missing " + field.getDisplayText() + " field.")); - node.issues.push({issue:issue,validity:Errors.Validity.Error}); + node.issues().push({issue:issue,validity:Errors.Validity.Error}); } else { if (existingField.getParameterType() !== field.getParameterType()){ const message = "Node " + node.getKey() + " (" + node.getName() + ") has a '" + field.getDisplayText() + "' field with the wrong parameter type (" + existingField.getParameterType() + "), should be a " + field.getParameterType(); diff --git a/src/Utils.ts b/src/Utils.ts index b8b3dc18e..39a691380 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1413,8 +1413,6 @@ export class Utils { } static checkGraph(eagle: Eagle): void { - // const errorsWarnings: Errors.ErrorsWarnings = {warnings: [], errors: []}; - const graph: LogicalGraph = eagle.logicalGraph(); // check all nodes are valid @@ -1438,7 +1436,8 @@ export class Utils { //from nodes for(const node of graph.getNodes()){ graphErrors.push(...node.getIssues()) - //fields + + //from fields for( const field of node.getFields()){ graphErrors.push(...field.getIssues()) } @@ -1464,12 +1463,13 @@ export class Utils { // from edges for (const edge of graph.getEdges()){ - graphErrors.push(...edge.getissues()) + graphErrors.push(...edge.getIssues()) } //from logical graph graphErrors.push(...graph.getIssues()) + //sort all issues into warnings or errors for(const error of graphErrors){ if(error.validity === Errors.Validity.Error || error.validity === Errors.Validity.Impossible || error.validity === Errors.Validity.Unknown){ errorsWarnings.errors.push(error.issue)