diff --git a/bundles/specmate-connectors-api/src/com/specmate/connectors/api/ConnectorBase.java b/bundles/specmate-connectors-api/src/com/specmate/connectors/api/ConnectorBase.java index 78d2bc538..21edf6050 100644 --- a/bundles/specmate-connectors-api/src/com/specmate/connectors/api/ConnectorBase.java +++ b/bundles/specmate-connectors-api/src/com/specmate/connectors/api/ConnectorBase.java @@ -10,10 +10,6 @@ public abstract class ConnectorBase implements IConnector { private IProject project; - public ConnectorBase(IProject project) { - this.project = project; - } - @Override public IProject getProject() { return project; diff --git a/bundles/specmate-connectors/test/com/specmate/connectors/test/ConnectorUtilTest.java b/bundles/specmate-connectors/test/com/specmate/connectors/test/ConnectorUtilTest.java index d27abe9b9..a8719f2ce 100644 --- a/bundles/specmate-connectors/test/com/specmate/connectors/test/ConnectorUtilTest.java +++ b/bundles/specmate-connectors/test/com/specmate/connectors/test/ConnectorUtilTest.java @@ -232,7 +232,7 @@ public Void answer(InvocationOnMock inv) throws Throwable { private abstract class TestRequirementSourceBase extends ConnectorBase { public TestRequirementSourceBase(IProject project) { - super(project); + setProject(project); } public static final String REQ_NAME = "req"; diff --git a/bundles/specmate-dummy-data/src/com/specmate/dummydata/DummyProject.java b/bundles/specmate-dummy-data/src/com/specmate/dummydata/DummyProject.java index 1df9561f9..42d1a23b7 100644 --- a/bundles/specmate-dummy-data/src/com/specmate/dummydata/DummyProject.java +++ b/bundles/specmate-dummy-data/src/com/specmate/dummydata/DummyProject.java @@ -36,7 +36,7 @@ public String getID() { /** Returns a connector that does nothing and authorizes every user. */ @Override public IConnector getConnector() { - return new ConnectorBase(this) { + ConnectorBase conn = new ConnectorBase() { @Override public Collection getRequirements() throws SpecmateException { @@ -64,6 +64,9 @@ public Requirement getRequirementById(String id) throws SpecmateException { } }; + conn.setProject(this); + ; + return conn; } @Override diff --git a/bundles/specmate-file-connector/src/com/specmate/connectors/fileconnector/internal/FileConnector.java b/bundles/specmate-file-connector/src/com/specmate/connectors/fileconnector/internal/FileConnector.java index 176bfe83e..330e7d077 100644 --- a/bundles/specmate-file-connector/src/com/specmate/connectors/fileconnector/internal/FileConnector.java +++ b/bundles/specmate-file-connector/src/com/specmate/connectors/fileconnector/internal/FileConnector.java @@ -60,10 +60,6 @@ public class FileConnector extends ConnectorBase { /** id of the project folder */ private String id; - public FileConnector(IProject project) { - super(project); - } - @Activate public void activate(Map properties) throws SpecmateException { validateConfig(properties); diff --git a/bundles/specmate-integration-test/src/com/specmate/test/integration/support/DummyProject.java b/bundles/specmate-integration-test/src/com/specmate/test/integration/support/DummyProject.java index 4f028eaed..0932b3489 100644 --- a/bundles/specmate-integration-test/src/com/specmate/test/integration/support/DummyProject.java +++ b/bundles/specmate-integration-test/src/com/specmate/test/integration/support/DummyProject.java @@ -30,7 +30,7 @@ public String getID() { @Override public IConnector getConnector() { - return new ConnectorBase(this) { + ConnectorBase conn = new ConnectorBase() { @Override public Collection getRequirements() throws SpecmateException { @@ -57,6 +57,8 @@ public Requirement getRequirementById(String id) throws SpecmateException { return null; } }; + conn.setProject(this); + return conn; } @Override diff --git a/bundles/specmate-trello-connector/src/com/specmate/connectors/trello/internal/services/TrelloConnector.java b/bundles/specmate-trello-connector/src/com/specmate/connectors/trello/internal/services/TrelloConnector.java index 5d67260fd..a4569b4ce 100644 --- a/bundles/specmate-trello-connector/src/com/specmate/connectors/trello/internal/services/TrelloConnector.java +++ b/bundles/specmate-trello-connector/src/com/specmate/connectors/trello/internal/services/TrelloConnector.java @@ -48,10 +48,6 @@ public class TrelloConnector extends ConnectorBase { private String token; private String id; - public TrelloConnector(IProject project) { - super(project); - } - @Activate public void activate(Map properties) throws SpecmateException { validateConfig(properties); diff --git a/web/src/app/config/config.ts b/web/src/app/config/config.ts index 31db94f5b..f2f3e19c6 100644 --- a/web/src/app/config/config.ts +++ b/web/src/app/config/config.ts @@ -57,7 +57,7 @@ export class Config { public static GRAPHICAL_EDITOR_ZOOM_STEP = 0.1; public static GRAPHICAL_EDITOR_ZOOM_MAX = 5; - public static CEG_NODE_WIDTH = 150; + public static CEG_NODE_WIDTH = 180; public static CEG_NODE_HEIGHT = 90; public static CEG_NODE_ARC_DIST: number = 17 + Math.sqrt((Config.CEG_NODE_WIDTH / 2.0) * (Config.CEG_NODE_WIDTH / 2.0) + diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-popup.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-popup.ts index 6a643265e..6bea798ea 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-popup.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-popup.ts @@ -2,9 +2,7 @@ import { mxgraph } from 'mxgraph'; import { CEGConnection } from 'src/app/model/CEGConnection'; import { StyleChanger } from '../util/style-changer'; import { EditorStyle } from './editor-style'; -import { SpecmateDataService } from 'src/app/modules/data/modules/data-service/services/specmate-data.service'; import { Type } from 'src/app/util/type'; -import { replaceClass } from '../util/css-utils'; import { TranslateService } from '@ngx-translate/core'; import { IContainer } from 'src/app/model/IContainer'; @@ -38,20 +36,19 @@ export class EditorPopup { } let element: IContainer = undefined; - let currentCell = cell; while (element === undefined) { - element = this.contents.find(element => element.url === currentCell.id); - currentCell = currentCell.parent; + element = this.contents.find(element => element.url === cell.id); } - currentCell = this.graph.getModel().getChildCells(this.graph.getDefaultParent()).find(graphCell => graphCell.id === element.url); + let selectedCells = this.graph.getSelectionCells(); + selectedCells = selectedCells.filter(element => element.parent == this.graph.getDefaultParent()); const deleteText = this.translate.instant('delete'); menu.addItem(deleteText, null, () => { - this.graph.removeCells([currentCell]); + this.graph.removeCells(selectedCells); }, undefined, 'fa fa-trash-o', undefined, undefined); - if (Type.is(element, CEGConnection)) { + if (Type.is(element, CEGConnection) && selectedCells.length === 1) { const connection = element as CEGConnection; const icon = connection.negate ? 'fa fa-check' : 'fa fa-circle-o'; diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-style.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-style.ts index eeef3a8f6..3796b5aa3 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-style.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/editor-components/editor-style.ts @@ -46,32 +46,13 @@ export class EditorStyle { public static readonly INNER_STYLE: Style = {}; public static readonly EFFECT_STYLE: Style = {}; - public static readonly TEXT_INPUT_STYLE = 'BASE_TEXT_INPUT'; - private static readonly TEXT_INPUT_STYLE_STR = 'shape=rectangle;rounded=0;strokeColor=none;align=center;fillColor=none;fontColor=#000000;autosize=1;whiteSpace=wrap'; - private static readonly TEXT_INPUT_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.TEXT_INPUT_STYLE_STR); - public static readonly VARIABLE_NAME_STYLE = 'BASE_VARIABLE_NAME_STYLE'; - private static readonly VARIABLE_NAME_STYLE_STR = 'shape=rectangle;rounded=0;strokeColor=none;fillColor=none;fontColor=#000000;autosize=1;whiteSpace=wrap;fontStyle=' + mx.mxConstants.FONT_BOLD; - private static readonly VARIABLE_NAME_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.VARIABLE_NAME_STYLE_STR); - public static readonly TEXT_INPUT_DISABLED_STYLE = 'BASE_TEXT_DISABLED_INPUT'; - private static readonly TEXT_INPUT_DISABLED_STYLE_STR = 'shape=rectangle;rounded=0;align=center;strokeColor=none;fillColor=none;editable=0;fontColor=#666666;autosize=1;whiteSpace=wrap'; - private static readonly TEXT_INPUT_DISABLED_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.TEXT_INPUT_DISABLED_STYLE_STR); - public static readonly VARIABLE_NAME_DISABLED_STYLE = 'BASE_VARIABLE_NAME_DISABLED_STYLE'; - private static readonly VARIABLE_NAME_DISABLED_STYLE_STR = 'shape=rectangle;rounded=0;align=center;strokeColor=none;fillColor=none;fontColor=#666666;editable=0;autosize=1;whiteSpace=wrap;fontStyle=' + mx.mxConstants.FONT_BOLD; - private static readonly VARIABLE_NAME_DISABLED_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.VARIABLE_NAME_DISABLED_STYLE_STR); - public static readonly ICON_STYLE = 'ICON_INPUT'; - private static readonly ICON_STYLE_STR = 'shape=rectangle;rounded=0;align=left;strokeColor=none;fillColor=none;fontColor=#000000;resizable=0;movable=0;editable=0;connectable=0;recursiveResize=0;rotatable=0;cloneable=0;deletable=0;'; - private static readonly ICON_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.ICON_STYLE_STR); - public static readonly BASE_CEG_NODE_STYLE = 'BASE_CEG_NODE'; - private static readonly BASE_CEG_NODE_STYLE_STR = 'shape=rectangle;rounded=1;arcSize=10;align=center;perimeter=rectanglePerimeter;dashed=0;textOpacity=0;autosize=1;whiteSpace=wrap'; + private static readonly BASE_CEG_NODE_STYLE_STR = 'shape=rectangle;rounded=1;arcSize=10;whiteSpace=wrap'; private static readonly BASE_CEG_NODE_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.BASE_CEG_NODE_STYLE_STR); - public static readonly TYPE_NAME_STYLE = 'TYPE_NAME_STYLE'; - private static readonly TYPE_NAME_STYLE_STR = 'shape=rectangle;autosize=0;strokeColor=none;fillColor=none'; - private static readonly TYPE_NAME_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.TYPE_NAME_STYLE_STR); - public static readonly BASE_CEG_LINKED_NODE_STYLE = 'BASE_CEG_LINKED_NODE'; - private static readonly BASE_CEG_LINKED_NODE_STYLE_STR = 'shape=rectangle;rounded=1;arcSize=10;align=center;perimeter=rectanglePerimeter;dashed=1;dashPattern=1 1;opacity=75;editable=0;textOpacity=0'; + private static readonly BASE_CEG_LINKED_NODE_STYLE_STR = 'shape=rectangle;rounded=1;arcSize=10;align=center;perimeter=rectanglePerimeter;dashed=1;dashPattern=1 1;opacity=75;editable=0;whiteSpace=wrap'; private static readonly BASE_CEG_LINKED_NODE_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.BASE_CEG_LINKED_NODE_STYLE_STR); + public static readonly BASE_PROCESS_START_STYLE = 'BASE_PROCESS_START_STYLE'; private static readonly BASE_PROCESS_START_STYLE_STR = 'shape=ellipse;whiteSpace=wrap;html=1;aspect=fixed;align=center;perimeter=ellipsePerimeter;editable=0;dashed=0;fontColor=#000000'; private static readonly BASE_PROCESS_START_STYLE_OBJ: Style = EditorStyle.createStyle(EditorStyle.BASE_PROCESS_START_STYLE_STR); @@ -118,12 +99,6 @@ export class EditorStyle { const stylesheet = graph.getStylesheet(); - stylesheet.putCellStyle(EditorStyle.TEXT_INPUT_STYLE, EditorStyle.TEXT_INPUT_STYLE_OBJ); - stylesheet.putCellStyle(EditorStyle.VARIABLE_NAME_STYLE, EditorStyle.VARIABLE_NAME_STYLE_OBJ); - stylesheet.putCellStyle(EditorStyle.TEXT_INPUT_DISABLED_STYLE, EditorStyle.TEXT_INPUT_DISABLED_STYLE_OBJ); - stylesheet.putCellStyle(EditorStyle.VARIABLE_NAME_DISABLED_STYLE, EditorStyle.VARIABLE_NAME_DISABLED_STYLE_OBJ); - stylesheet.putCellStyle(EditorStyle.ICON_STYLE, EditorStyle.ICON_STYLE_OBJ); - stylesheet.putCellStyle(EditorStyle.TYPE_NAME_STYLE, EditorStyle.TYPE_NAME_STYLE_OBJ); stylesheet.putCellStyle(EditorStyle.BASE_CEG_NODE_STYLE, EditorStyle.BASE_CEG_NODE_STYLE_OBJ); stylesheet.putCellStyle(EditorStyle.BASE_CEG_LINKED_NODE_STYLE, EditorStyle.BASE_CEG_LINKED_NODE_STYLE_OBJ); diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/graphical-editor.component.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/graphical-editor.component.ts index 810a7a156..6a0ff8680 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/graphical-editor.component.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/graphical-editor.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, ViewC import { TranslateService } from '@ngx-translate/core'; import { mxgraph } from 'mxgraph'; // Typings only - no code! import { Subscription } from 'rxjs'; -import { CEGLinkedNode } from 'src/app/model/CEGLinkedNode'; +import { isFunction } from 'rxjs/internal-compatibility'; import { CEGModel } from 'src/app/model/CEGModel'; import { Process } from 'src/app/model/Process'; import { ProcessConnection } from 'src/app/model/ProcessConnection'; @@ -22,6 +22,7 @@ import { EditorToolsService } from '../../tool-pallette/services/editor-tools.se import { ToolBase } from '../../tool-pallette/tools/tool-base'; import { ConverterBase } from '../converters/converter-base'; import { NodeNameConverterProvider } from '../providers/conversion/node-name-converter-provider'; +import { CEGmxModelLinkedNode } from '../providers/properties/ceg-mx-model-linked-node'; import { CEGmxModelNode } from '../providers/properties/ceg-mx-model-node'; import { ElementProvider } from '../providers/properties/element-provider'; import { NameProvider } from '../providers/properties/name-provider'; @@ -55,7 +56,7 @@ export class GraphicalEditor implements OnDestroy { private nameProvider: NameProvider; private elementProvider: ElementProvider; - private nodeNameConverter: ConverterBase; + private nodeNameConverter: ConverterBase; private shapeProvider: ShapeProvider; private changeTranslator: ChangeTranslator; private vertexProvider: VertexProvider; @@ -185,12 +186,6 @@ export class GraphicalEditor implements OnDestroy { this.modal.openOk(this.translate.instant('graphicalEditorErrorTitle'), message); }; - const cellEditorInit = mx.mxCellEditor.prototype.init; - mx.mxCellEditor.prototype.init = function () { - cellEditorInit.apply(this, arguments); - this.textarea.style.resize = ''; - }; - mx.mxEvent.disableContextMenu(this.graphContainerElement.nativeElement); if (this.graphMouseMove === undefined) { @@ -218,29 +213,10 @@ export class GraphicalEditor implements OnDestroy { const rubberBand = new mx.mxRubberband(this.graph); rubberBand.reset(); this.graph.setHtmlLabels(true); - this.graph.setTooltips(true); - this.graph.zoomFactor = 1.1; - - this.setFunctionGetPreferredSizeForCell(this.graph, this.shapeProvider); + this.graph.enterStopsCellEditing = true; - this.graph.addListener(mx.mxEvent.EDITING_STARTED, (sender: mxgraph.mxGraph, evt: mxgraph.mxEventObject) => { - const cell = evt.properties.cell as mxgraph.mxCell; - if (cell !== undefined && - (cell.id.endsWith(VertexProvider.ID_SUFFIX_VARIABLE) || cell.id.endsWith(VertexProvider.ID_SUFFIX_CONDITION))) { - let parentWidth = cell.parent.geometry.width; - cell.getGeometry().setRect(0, cell.geometry.y, parentWidth, cell.geometry.height); - this.graph.getView().invalidate(cell); - this.graph.getView().validate(cell); - } - }); - - this.graph.addListener(mx.mxEvent.DOUBLE_CLICK, (sender: mxgraph.mxGraph, evt: mxgraph.mxEventObject) => { - const cell = evt.properties.cell as mxgraph.mxCell; - if (cell !== undefined && cell.id.endsWith(VertexProvider.ID_SUFFIX_TYPE)) { - evt.consumed = true; - } - }); + this.graph.zoomFactor = 1.1; this.graph.getModel().addListener(mx.mxEvent.CHANGE, async (sender: mxgraph.mxEventSource, evt: mxgraph.mxEventObject) => { const edit = evt.getProperty('edit') as mxgraph.mxUndoableEdit; @@ -289,7 +265,6 @@ export class GraphicalEditor implements OnDestroy { edit.undo(); this.changeTranslator.preventDataUpdates = false; } finally { - this.graph.getView().revalidate(); this.undoService.setUndoEnabled(this.undoManager.canUndo()); this.undoService.setRedoEnabled(this.undoManager.canRedo()); } @@ -316,17 +291,6 @@ export class GraphicalEditor implements OnDestroy { if (selections.length >= 1) { // Highlight All Edges - if (selections.length === 1) { - if (selections[0].getParent() === null) { - this.graph.getModel().endUpdate(); - return; - } - if (selections[0].getParent() !== this.graph.getDefaultParent()) { - // We selected a child/ sublabel --> Select Parent instead - selections[0] = selections[0].getParent(); - } - } - for (const cell of selections) { if (cell.edge) { this.highlightedEdges.push(cell); @@ -437,26 +401,19 @@ export class GraphicalEditor implements OnDestroy { private makeVertexTool(tool: ToolBase) { const onDrop = (graph: mxgraph.mxGraph, evt: MouseEvent) => { this.graph.stopEditing(false); - const initialData: ShapeData = this.shapeProvider.getInitialData(tool.style); + this.graph.clearSelection(); + const initialData: ShapeData = mx.mxUtils.clone(this.shapeProvider.getInitialData(tool.style)); const coords = this.graph.getPointForEvent(evt); const vertexUrl = Url.build([this.model.url, Id.uuid]); this.graph.startEditing(evt); try { - if (initialData.style === EditorStyle.BASE_CEG_NODE_STYLE) { - this.vertexProvider.provideCEGNode(vertexUrl, coords.x, coords.y, - initialData.size.width, initialData.size.height, initialData.text as CEGmxModelNode, undefined); - } else if (initialData.style === EditorStyle.BASE_CEG_LINKED_NODE_STYLE) { - this.vertexProvider.provideLinkedCEGNode(vertexUrl, coords.x, coords.y, - initialData.size.width, initialData.size.height, initialData.text as CEGmxModelNode, undefined); - } else { - this.graph.insertVertex( - this.graph.getDefaultParent(), - vertexUrl, - initialData.text, - coords.x, coords.y, - initialData.size.width, initialData.size.height, - initialData.style); - } + this.graph.insertVertex( + this.graph.getDefaultParent(), + vertexUrl, + initialData.text, + coords.x, coords.y, + initialData.size.width, initialData.size.height, + initialData.style); } finally { this.graph.stopEditing(true); @@ -497,9 +454,8 @@ export class GraphicalEditor implements OnDestroy { this.elementProvider = new ElementProvider(this.model, this._contents); this.nodeNameConverter = new NodeNameConverterProvider(this.model).nodeNameConverter; this.vertexProvider - = new VertexProvider(this.model, this.graph, this.shapeProvider, this.nodeNameConverter, - this.dataService, this.changeGuard, this.translate); - this.vertexProvider.initRenderer(this.graph); + = new VertexProvider(this.model, this.graph, this.shapeProvider, this.nodeNameConverter, this.dataService, this.translate); + const parent = this.graph.getDefaultParent(); this.changeTranslator.preventDataUpdates = true; @@ -507,6 +463,10 @@ export class GraphicalEditor implements OnDestroy { this.initCEGModel(); } + if (Type.is(this.model, Process)) { + this.initProcessModel(); + } + this.graph.getModel().beginUpdate(); try { const vertexCache: { [url: string]: mxgraph.mxCell } = {}; @@ -539,69 +499,88 @@ export class GraphicalEditor implements OnDestroy { this.validationService.validateCurrent(); } } - private async initCEGModel(): Promise { const graph = this.graph; - this.graph.isCellEditable = function (cell) { - let c = cell as mxgraph.mxCell; - if (c.edge) { - return false; - } - if (c.children !== undefined && c.children !== null && c.children.length > 0) { - return false; - } - const cellStyle = graph.getStylesheet().getCellStyle(c.getStyle(), undefined); - if (cellStyle !== undefined && (cellStyle['editable'] === '0' || cellStyle['editable'] === 'false')) { - return false; + this.vertexProvider.initCEGVertexRenderer(); + + this.graph.getTooltipForCell = function (cell: mxgraph.mxCell) { + if (cell.value instanceof CEGmxModelNode || cell.value instanceof CEGmxModelLinkedNode) { + return cell.value.getHint(); } - return true; + return mx.mxGraph.prototype.getTooltipForCell.apply(this, arguments); }; - this.graph.graphHandler.setRemoveCellsFromParent(false); - - this.graph.isCellResizable = function (cell) { - let c = cell as mxgraph.mxCell; - const cellStyle = graph.getStylesheet().getCellStyle(c.getStyle(), undefined); - if (cellStyle !== undefined && (cellStyle['resizable'] === '0' || cellStyle['resizable'] === 'false')) { - return false; + let stopEditing = mx.mxCellEditor.prototype.stopEditing; + mx.mxCellEditor.prototype.stopEditing = function (cancel) { + stopEditing.apply(this, arguments); + let state = (!cancel) ? this.graph.view.getState(this.editingCell) : null; + if (state != null) { + state.cell.value.editField = undefined; } - let geo = this.model.getGeometry(cell); - return geo == null || !geo.relative; }; - const originalTooltip = this.graph.getTooltipForCell; - - this.graph.getTooltipForCell = function (cell) { - let c = cell as mxgraph.mxCell; - if (c.getId().endsWith(VertexProvider.ID_SUFFIX_LINK_ICON)) { - return null; + let getFieldnameForEvent = function (cell: any, evt: any) { + if (evt != null) { + let htmlElement = mx.mxEvent.getSource(evt); + if (htmlElement.hasAttribute('data-node')) { + return htmlElement.getAttribute('data-node'); + } else { + let point = mx.mxUtils.convertPoint(graph.container, + mx.mxEvent.getClientX(evt), mx.mxEvent.getClientY(evt)); + let state = graph.getView().getState(cell); + + // if no field is clicked directly, variable is selected for the upper half of the cell, condition otherwise + if (state != null) { + point.x -= state.x; + point.y -= state.y; + if (point.y < state.height / 2) { + return VertexProvider.ID_VARIABLE; + } else { + return VertexProvider.ID_CONDITION; + } + } + } } - return originalTooltip(cell); + return VertexProvider.ID_VARIABLE; }; - mx.mxConnectionHandler.prototype.isValidTarget = - (target) => target.value === undefined || target.value === null || !Type.is(target.value, CEGLinkedNode); + this.graph.getEditingValue = function (cell, evt) { + evt.fieldname = getFieldnameForEvent(cell, evt); + cell.value.editField = evt.fieldname; + return cell.value[evt.fieldname] || ''; + }; + + this.graph.getModel()['valueForCellChanged'] = + function (cell: mxgraph.mxCell, value: CEGmxModelLinkedNode | CEGmxModelNode | string) { + if (cell.isVertex()) { + if (value instanceof CEGmxModelNode) { + let previous = mx.mxUtils.clone(cell.value); + cell.value = value; + return previous; + } + let changedField = cell.value.editField; + let previous = cell.value[changedField]; + cell.value[changedField] = value; + return previous; + } + return ''; + }; + + mx.mxConnectionHandler.prototype.isValidTarget = function (cell: mxgraph.mxCell) { + return cell.value === undefined || cell.value === null || !(cell.value instanceof CEGmxModelLinkedNode); + }; + } + private async initProcessModel(): Promise { + this.setFunctionGetPreferredSizeForCell(this.graph, this.shapeProvider); } private setFunctionGetPreferredSizeForCell(graph: mxgraph.mxGraph, shapeProvider: ShapeProvider) { - let graphGetPreferredSizeForCell = graph.getPreferredSizeForCell; graph.getPreferredSizeForCell = function (cell: mxgraph.mxCell) { - if (cell.getId().endsWith(VertexProvider.ID_SUFFIX_TYPE)) { - return undefined; - } - let result = graphGetPreferredSizeForCell.apply(this, arguments); + let result = mx.mxGraph.prototype.getPreferredSizeForCell.apply(this, arguments); if (result !== null) { let width = result.width; - if (cell.children !== undefined && cell.children !== null) { - for (const child of cell.children) { - let resultChild = graph.getPreferredSizeForCell(child); - if (resultChild !== undefined) { - width = Math.max(width, resultChild.width); - } - } - } if (cell.style !== undefined) { let shapeData = shapeProvider.getInitialData(cell.style.split(';')[0]); if (shapeData !== undefined) { diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/util/change-translator.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/util/change-translator.ts index 8320fa08d..a8c077a1e 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/components/util/change-translator.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/components/util/change-translator.ts @@ -21,10 +21,10 @@ import { DeleteToolBase } from '../../../tool-pallette/tools/delete-tool-base'; import { ToolBase } from '../../../tool-pallette/tools/tool-base'; import { ConverterBase } from '../../converters/converter-base'; import { NodeNameConverterProvider } from '../../providers/conversion/node-name-converter-provider'; +import { CEGmxModelLinkedNode } from '../../providers/properties/ceg-mx-model-linked-node'; import { CEGmxModelNode } from '../../providers/properties/ceg-mx-model-node'; import { ShapeProvider } from '../../providers/properties/shape-provider'; import { ToolProvider } from '../../providers/properties/tool-provider'; -import { VertexProvider } from '../../providers/properties/vertex-provider'; import { EditorStyle } from '../editor-components/editor-style'; import { StyleChanger } from './style-changer'; @@ -40,7 +40,7 @@ export class ChangeTranslator { private contents: IContainer[]; private parentComponents: { [key: string]: IContainer }; - private nodeNameConverter: ConverterBase; + private nodeNameConverter: ConverterBase; public preventDataUpdates = false; private compoundIdStore: { [key: number]: string } = {}; @@ -254,8 +254,7 @@ export class ChangeTranslator { } if (Type.is(node, CEGNode)) { - const value = - new CEGmxModelNode(change.child.children[0].value, change.child.children[1].value, change.child.children[2].value || 'AND'); + const value = change.child.value; const elementValues = this.nodeNameConverter.convertFrom(value, node); for (const key in elementValues) { node[key] = elementValues[key]; @@ -265,6 +264,7 @@ export class ChangeTranslator { const linkingNode = node as CEGLinkedNode; const linkedNode = await this.dataService.readElement(linkingNode.linkTo.url, true) as CEGNode; node.name = linkedNode.variable + ' ' + linkedNode.condition; + change.child.setValue(new CEGmxModelLinkedNode(linkedNode.variable, linkedNode.condition)); } else { node.name = change.child.value; } @@ -284,40 +284,7 @@ export class ChangeTranslator { cells[newId] = cell; if (Type.is(node, CEGLinkedNode) || Type.is(node, CEGNode)) { - cell.setValue(node); - } - - - if (Type.is(node, CEGNode) || Type.is(node, CEGLinkedNode)) { - let variable = cell.children[0]; - let condition = cell.children[1]; - - let oldIdVariable = variable.id; - let oldIdCondition = condition.id; - - variable.setId(newId + VertexProvider.ID_SUFFIX_VARIABLE); - condition.setId(newId + VertexProvider.ID_SUFFIX_CONDITION); - - delete cells[oldIdVariable]; - delete cells[oldIdCondition]; - - cells[variable.id] = variable; - cells[condition.id] = condition; - - this.parentComponents[variable.id] = node; - this.parentComponents[condition.id] = node; - - delete this.parentComponents[oldIdVariable]; - delete this.parentComponents[oldIdCondition]; - } - if (Type.is(node, CEGNode)) { - let type = cell.children[2]; - let oldIdType = type.id; - type.setId(newId + VertexProvider.ID_SUFFIX_TYPE); - delete cells[oldIdType]; - cells[type.id] = type; - this.parentComponents[type.id] = node; - delete this.parentComponents[oldIdType]; + graph.getView().validate(cell); } return node; } @@ -338,64 +305,29 @@ export class ChangeTranslator { } } - private isTextInputChange(change: mxgraph.mxTerminalChange | mxgraph.mxValueChange): boolean { - const cell = change.cell as mxgraph.mxCell; - return (cell.id.endsWith(VertexProvider.ID_SUFFIX_VARIABLE) || cell.id.endsWith(VertexProvider.ID_SUFFIX_CONDITION)); - } private async translateNodeChange(change: mxgraph.mxTerminalChange | mxgraph.mxValueChange, element: IModelNode, graph: mxgraph.mxGraph, compoundId = Id.uuid): Promise { let cell = change.cell as mxgraph.mxCell; - if (this.ignoreChanges(cell)) { - return; - } - if (this.isTextInputChange(change)) { - if (cell.value === '' || cell.value === undefined || cell.value === null) { - StyleChanger.addStyle(cell, graph, EditorStyle.EMPTY_TEXT_NAME); - } else { - StyleChanger.removeStyle(cell, graph, EditorStyle.EMPTY_TEXT_NAME); - } - graph.getView().validate(cell); - graph.updateCellSize(cell.parent, true); - VertexProvider.adjustChildrenCellSizes(cell.parent, this.shapeProvider, graph); - } - let isLabelCell = false; if (this.nodeNameConverter) { - if (this.parentComponents[cell.getId()] !== undefined) { - // The changed node is a sublabel / child - isLabelCell = true; - cell = cell.getParent(); - } if (!Type.is(element, CEGLinkedNode)) { let value = cell.value; - if (Type.is(element, CEGNode)) { - value = new CEGmxModelNode(cell.children[0].value, cell.children[1].value, cell.children[2].value); - } - const elementValues = this.nodeNameConverter.convertFrom(value, element); for (const key in elementValues) { element[key] = elementValues[key]; } - await this.dataService.updateElement(element, true, compoundId); } } else { // Keep change.cell to avoid having a parent a child value element['variable'] = change.cell.value; - await this.dataService.updateElement(element, true, compoundId); - } - if (!isLabelCell) { - element['x'] = Math.max(0, cell.geometry.x); - element['y'] = Math.max(0, cell.geometry.y); - element['width'] = cell.geometry.width; - element['height'] = cell.geometry.height; - VertexProvider.adjustChildrenCellSizes(cell, this.shapeProvider, graph); - await this.dataService.updateElement(element, true, compoundId); } + element['x'] = Math.max(0, cell.geometry.x); + element['y'] = Math.max(0, cell.geometry.y); + element['width'] = cell.geometry.width; + element['height'] = cell.geometry.height; + await this.dataService.updateElement(element, true, compoundId); } - private ignoreChanges(cell: mxgraph.mxCell) { - return cell.getId().endsWith(VertexProvider.ID_SUFFIX_LINK_ICON); - } private async translateEdgeChange(change: mxgraph.mxTerminalChange | mxgraph.mxValueChange | mxgraph.mxGeometryChange, connection: IModelConnection, compoundId = Id.uuid): Promise { @@ -531,46 +463,32 @@ export class ChangeTranslator { let linkedNode = (await this.dataService.readElement(node.linkTo.url)) as CEGNode; graph.getModel().beginUpdate(); try { - graph.model.setValue(cell.children[0], linkedNode.variable); - graph.model.setValue(cell.children[1], linkedNode.condition); - StyleChanger.removeStyle(cell.children[0], graph, EditorStyle.EMPTY_TEXT_NAME); - StyleChanger.removeStyle(cell.children[1], graph, EditorStyle.EMPTY_TEXT_NAME); + let previous = mx.mxUtils.clone(cell.value) as CEGmxModelLinkedNode; + if (previous.variable !== linkedNode.variable || previous.condition !== linkedNode.condition) { + previous.variable = linkedNode.variable; + previous.condition = linkedNode.condition; + cell.setValue(previous); + } } finally { graph.getModel().endUpdate(); } } else { graph.getModel().beginUpdate(); try { - graph.model.setValue(cell.children[0], undefined); - graph.model.setValue(cell.children[1], undefined); + cell.setValue(new CEGmxModelLinkedNode(undefined, undefined)); changedElement.name = 'unlinked CEG linked node'; } finally { graph.getModel().endUpdate(); } } } else if (value instanceof CEGmxModelNode) { - for (const key in value) { - if (value.hasOwnProperty(key)) { - const val = value[key]; - let child = cell.children.find(s => s.getId().endsWith(key)); - if (child !== undefined) { - if (child.value !== val) { - graph.getModel().beginUpdate(); - try { - graph.model.setValue(child, val); - graph.updateCellSize(child, true); - graph.updateCellSize(cell, true); - VertexProvider.adjustChildrenPositions(cell); - } finally { - let width = child.geometry.width; - if (width <= 0) { - width = this.shapeProvider.getInitialSize(changedElement).width; - } - VertexProvider.adjustChildCellSize(child, width); - graph.getModel().endUpdate(); - } - } - } + if (value !== cell.value) { + let previous = mx.mxUtils.clone(cell.value); + if (previous.variable !== value.variable || previous.condition !== value.condition || previous.type !== value.type) { + previous.variable = value.variable; + previous.condition = value.condition; + previous.type = value.type; + graph.getModel().setValue(cell, previous); } } } else { @@ -586,6 +504,8 @@ export class ChangeTranslator { graph.getModel().endUpdate(); } } + graph.getView().invalidate(cell); + graph.getView().validate(cell); this.preventDataUpdates = false; } } diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/converters/variable-condition-name-converter.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/converters/variable-condition-name-converter.ts index 112c2d227..15cb0d2b2 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/converters/variable-condition-name-converter.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/converters/variable-condition-name-converter.ts @@ -1,21 +1,30 @@ import { ConverterBase } from './converter-base'; import { CEGmxModelNode } from '../providers/properties/ceg-mx-model-node'; +import { CEGmxModelLinkedNode } from '../providers/properties/ceg-mx-model-linked-node'; export type VariableAndCondition = { variable: string, condition: string, type: string }; -export class VariableConditionToNameConverter extends ConverterBase { - public convertTo(item: VariableAndCondition): CEGmxModelNode { +export class VariableConditionToNameConverter extends ConverterBase { + public convertTo(item: VariableAndCondition): CEGmxModelNode | CEGmxModelLinkedNode { + + if (item.condition === undefined) { + return new CEGmxModelLinkedNode(item.variable, item.condition); + } if (item.variable === undefined || item.condition === undefined) { - return name; + return new CEGmxModelNode('', '', 'AND'); } return new CEGmxModelNode(item.variable, item.condition, item.type); } - public convertFrom(value: CEGmxModelNode, item: VariableAndCondition): { variable: string, condition: string, type: string } { + public convertFrom(value: CEGmxModelNode | CEGmxModelLinkedNode, item: VariableAndCondition): VariableAndCondition { + let type = 'AND'; + if (value instanceof CEGmxModelNode) { + type = value.type; + } return { variable: value.variable, condition: value.condition, - type: value.type + type: type }; } } diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/conversion/node-name-converter-provider.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/conversion/node-name-converter-provider.ts index b944b7fd3..caf7a3417 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/conversion/node-name-converter-provider.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/conversion/node-name-converter-provider.ts @@ -3,9 +3,10 @@ import { ConverterBase } from '../../converters/converter-base'; import { VariableConditionToNameConverter } from '../../converters/variable-condition-name-converter'; import { ProcessNodeToNameConverter } from '../../converters/process-node-to-name-converter'; import { CEGmxModelNode } from '../properties/ceg-mx-model-node'; +import { CEGmxModelLinkedNode } from '../properties/ceg-mx-model-linked-node'; export class NodeNameConverterProvider extends ProviderBase { - public get nodeNameConverter(): ConverterBase { + public get nodeNameConverter(): ConverterBase { if (this.isCEGModel) { return new VariableConditionToNameConverter(); } else if (this.isProcessModel) { diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/ceg-mx-model-linked-node.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/ceg-mx-model-linked-node.ts new file mode 100644 index 000000000..0c3ab5f7e --- /dev/null +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/ceg-mx-model-linked-node.ts @@ -0,0 +1,26 @@ +import { xssEncode } from '../../components/util/xss-encoder'; + +export class CEGmxModelLinkedNode { + public variable: string; + public condition: string; + + constructor(variable: string, condition: string) { + if (variable === undefined) { + this.variable = undefined; + } else { + this.variable = xssEncode(variable); + } + if (condition === undefined) { + this.condition = undefined; + } else { + this.condition = xssEncode(condition); + } + } + + public getHint(): string { + if (this.variable === undefined) { + return ''; + } + return this.variable + ' ' + this.condition; + } +} diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/ceg-mx-model-node.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/ceg-mx-model-node.ts index 2f61b3f2f..294862589 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/ceg-mx-model-node.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/ceg-mx-model-node.ts @@ -1,17 +1,22 @@ import { xssEncode } from '../../components/util/xss-encoder'; export class CEGmxModelNode { - public static VARIABLE_KEY = 'variable'; - public static CONDITION_KEY = 'condition'; - public static TYPE_KEY = 'type'; public variable: string; public condition: string; public type: string; + public editField: string; - constructor(public _variable: string, public _condition: string, public _type: string) { - this.variable = xssEncode(_variable); - this.condition = xssEncode(_condition); - this.type = _type; + constructor(variable: string, condition: string, type: string) { + this.variable = xssEncode(variable); + this.condition = xssEncode(condition); + this.type = type; + } + + public getHint(): string { + if (this.variable === '' && this.condition === '') { + return ''; + } + return this.variable + ' ' + this.condition; } } diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/shape-provider.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/shape-provider.ts index 0efc2d555..598c669e1 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/shape-provider.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/shape-provider.ts @@ -12,11 +12,14 @@ import { EditorStyle } from '../../components/editor-components/editor-style'; import { NodeNameConverterProvider } from '../conversion/node-name-converter-provider'; import { CEGmxModelNode } from './ceg-mx-model-node'; import { ProviderBase } from './provider-base'; +import { CEGmxModelLinkedNode } from './ceg-mx-model-linked-node'; +import { CEGModel } from 'src/app/model/CEGModel'; +import { Process } from 'src/app/model/Process'; export type ShapeData = { style: string, size: { width: number, height: number, margin: number }, - text: string | CEGmxModelNode + text: CEGmxModelLinkedNode | CEGmxModelNode | string }; const mx: typeof mxgraph = require('mxgraph')({ @@ -31,80 +34,84 @@ export class ShapeProvider extends ProviderBase { constructor(type: { className: string }) { super(type); - this.shapeMap[CEGNode.className] = { - style: EditorStyle.BASE_CEG_NODE_STYLE, - size: { - width: Config.CEG_NODE_WIDTH, - height: Config.CEG_NODE_HEIGHT, - margin: 15 - }, - text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ - variable: Config.CEG_NODE_NEW_VARIABLE, - condition: Config.CEG_NODE_NEW_CONDITION, - type: Config.CEG_NODE_NEW_TYPE - }) - }; - - this.shapeMap[CEGLinkedNode.className] = { - style: EditorStyle.BASE_CEG_LINKED_NODE_STYLE, - size: { - width: Config.CEG_NODE_WIDTH, - height: Config.CEG_NODE_HEIGHT, - margin: 15 - }, - text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ - variable: Config.CEG_NODE_NEW_VARIABLE, - condition: Config.CEG_NODE_NEW_CONDITION - }) - }; - - this.shapeMap[ProcessStart.className] = { - style: EditorStyle.BASE_PROCESS_START_STYLE, - size: { - width: Config.PROCESS_START_END_NODE_RADIUS * 2, - height: Config.PROCESS_START_END_NODE_RADIUS * 2, - margin: 0 - }, - text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ - name: 'Start' - }) - }; - - this.shapeMap[ProcessEnd.className] = { - style: EditorStyle.BASE_PROCESS_END_STYLE, - size: { - width: Config.PROCESS_START_END_NODE_RADIUS * 2, - height: Config.PROCESS_START_END_NODE_RADIUS * 2, - margin: 0 - }, - text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ - name: 'End' - }) - }; - - this.shapeMap[ProcessStep.className] = { - style: EditorStyle.BASE_PROCESS_STEP_STYLE, - size: { - width: Config.CEG_NODE_WIDTH, - height: Config.CEG_NODE_HEIGHT, - margin: 15 - }, - text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ - name: Config.PROCESS_NEW_STEP_NAME - }) - }; - - this.shapeMap[ProcessDecision.className] = { - style: EditorStyle.BASE_PROCESS_DECISION_STYLE, - size: { - width: Config.PROCESS_DECISION_NODE_DIM, - height: Config.PROCESS_DECISION_NODE_DIM, - margin: 40 - }, - text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ - name: Config.PROCESS_NEW_DECISION_NAME - }) - }; + if (Type.is(type, CEGModel)) { + this.shapeMap[CEGNode.className] = { + style: EditorStyle.BASE_CEG_NODE_STYLE, + size: { + width: Config.CEG_NODE_WIDTH, + height: Config.CEG_NODE_HEIGHT, + margin: 15 + }, + text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ + variable: Config.CEG_NODE_NEW_VARIABLE, + condition: Config.CEG_NODE_NEW_CONDITION, + type: Config.CEG_NODE_NEW_TYPE + }) as CEGmxModelNode + }; + + this.shapeMap[CEGLinkedNode.className] = { + style: EditorStyle.BASE_CEG_LINKED_NODE_STYLE, + size: { + width: Config.CEG_NODE_WIDTH, + height: Config.CEG_NODE_HEIGHT, + margin: 15 + }, + text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ + variable: Config.CEG_NODE_NEW_VARIABLE, + condition: Config.CEG_NODE_NEW_CONDITION + }) as CEGmxModelLinkedNode + }; + } + + if (Type.is(type, Process)) { + this.shapeMap[ProcessStart.className] = { + style: EditorStyle.BASE_PROCESS_START_STYLE, + size: { + width: Config.PROCESS_START_END_NODE_RADIUS * 2, + height: Config.PROCESS_START_END_NODE_RADIUS * 2, + margin: 0 + }, + text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ + name: 'Start' + }) + }; + + this.shapeMap[ProcessEnd.className] = { + style: EditorStyle.BASE_PROCESS_END_STYLE, + size: { + width: Config.PROCESS_START_END_NODE_RADIUS * 2, + height: Config.PROCESS_START_END_NODE_RADIUS * 2, + margin: 0 + }, + text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ + name: 'End' + }) + }; + + this.shapeMap[ProcessStep.className] = { + style: EditorStyle.BASE_PROCESS_STEP_STYLE, + size: { + width: Config.CEG_NODE_WIDTH, + height: Config.CEG_NODE_HEIGHT, + margin: 15 + }, + text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ + name: Config.PROCESS_NEW_STEP_NAME + }) + }; + + this.shapeMap[ProcessDecision.className] = { + style: EditorStyle.BASE_PROCESS_DECISION_STYLE, + size: { + width: Config.PROCESS_DECISION_NODE_DIM, + height: Config.PROCESS_DECISION_NODE_DIM, + margin: 40 + }, + text: new NodeNameConverterProvider(type).nodeNameConverter.convertTo({ + name: Config.PROCESS_NEW_DECISION_NAME + }) + }; + } this.styles.push((element: { className: string }) => this.shapeMap[element.className]); @@ -118,46 +125,6 @@ export class ShapeProvider extends ProviderBase { }; } }); - - this.shapeMap['VariableName'] = { - style: EditorStyle.VARIABLE_NAME_STYLE, - size: { - width: 75, - height: mx.mxConstants.DEFAULT_FONTSIZE, - margin: 30 - }, - text: undefined - }; - - this.shapeMap['BaseTextInput'] = { - style: EditorStyle.TEXT_INPUT_STYLE, - size: { - width: 75, - height: mx.mxConstants.DEFAULT_FONTSIZE, - margin: 30 - }, - text: undefined - }; - - this.shapeMap['VariableNameDisabled'] = { - style: EditorStyle.VARIABLE_NAME_DISABLED_STYLE, - size: { - width: 75, - height: mx.mxConstants.DEFAULT_FONTSIZE, - margin: 30 - }, - text: undefined - }; - - this.shapeMap['BaseTextInputDisabled'] = { - style: EditorStyle.TEXT_INPUT_DISABLED_STYLE, - size: { - width: 75, - height: mx.mxConstants.DEFAULT_FONTSIZE, - margin: 30 - }, - text: undefined - }; } private getShapeData(element: { className: string }): ShapeData[] { @@ -172,7 +139,7 @@ export class ShapeProvider extends ProviderBase { return this.getShapeData(element).find(shapeData => shapeData.size !== undefined).size; } - public getInitialText(element: { className: string }): string | CEGmxModelNode { + public getInitialText(element: { className: string }): CEGmxModelLinkedNode | CEGmxModelNode | string { return this.getShapeData(element).find(shapeData => shapeData.text !== undefined).text; } diff --git a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/vertex-provider.ts b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/vertex-provider.ts index 965375bca..9172c90a6 100644 --- a/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/vertex-provider.ts +++ b/web/src/app/modules/views/main/editors/modules/graphical-editor/providers/properties/vertex-provider.ts @@ -1,5 +1,4 @@ import { TranslateService } from '@ngx-translate/core'; -import * as he from 'he'; import { mxgraph } from 'mxgraph'; // Typings only - no code! import { SpecmateDataService } from 'src/app/modules/data/modules/data-service/services/specmate-data.service'; import { CEGLinkedNode } from '../../../../../../../../model/CEGLinkedNode'; @@ -8,9 +7,10 @@ import { IContainer } from '../../../../../../../../model/IContainer'; import { IModelNode } from '../../../../../../../../model/IModelNode'; import { Type } from '../../../../../../../../util/type'; import { EditorStyle } from '../../components/editor-components/editor-style'; +import { GraphicalEditor } from '../../components/graphical-editor.component'; import { StyleChanger } from '../../components/util/style-changer'; import { ConverterBase } from '../../converters/converter-base'; -import { ChangeGuardService } from '../../services/change-guard.service'; +import { CEGmxModelLinkedNode } from './ceg-mx-model-linked-node'; import { CEGmxModelNode } from './ceg-mx-model-node'; import { ProviderBase } from './provider-base'; import { ShapeProvider } from './shape-provider'; @@ -21,179 +21,120 @@ const mx: typeof mxgraph = require('mxgraph')({ mxBasePath: 'mxgraph' }); -/** - * Based on https://github.com/jgraph/mxgraph/blob/master/javascript/examples/editing.html - */ -export class VertexProvider extends ProviderBase { +export enum NodeType { CAUSE, INNER, EFFECT } - public static ID_SUFFIX_VARIABLE = '/variable'; - public static ID_SUFFIX_CONDITION = '/condition'; - public static ID_SUFFIX_TYPE = '/type'; - public static ID_SUFFIX_LINK_ICON = '/link-icon'; +export class VertexProvider extends ProviderBase { - private static INITIAL_CHILD_NODE_X = 0.5; - private static EMPTY_CHILD_NODE_WIDTH = 50; + public static ID_VARIABLE = 'variable'; + public static ID_CONDITION = 'condition'; constructor(element: IContainer, private graph: mxgraph.mxGraph, private shapeProvider: ShapeProvider, - private nodeNameConverter: ConverterBase, + private nodeNameConverter: ConverterBase, private dataService: SpecmateDataService, - private changeGuard: ChangeGuardService, private translate: TranslateService) { super(element); } - public provideCEGNode(url: string, x: number, y: number, width: number, height: number, - data: CEGmxModelNode, node: CEGNode): mxgraph.mxCell { - const value = node; - const style = this.shapeProvider.getStyle(new CEGNode()); - const parent = this.graph.getDefaultParent(); - this.graph.getModel().beginUpdate(); - const vertex = this.graph.insertVertex(parent, url, value, x, y, width, height, style); - - const labelVariable = this.graph.insertVertex(vertex, url + VertexProvider.ID_SUFFIX_VARIABLE, data.variable, - VertexProvider.INITIAL_CHILD_NODE_X, 0.15, 0, (mx.mxConstants.DEFAULT_FONTSIZE), EditorStyle.VARIABLE_NAME_STYLE, true); - const labelCondition = this.graph.insertVertex(vertex, url + VertexProvider.ID_SUFFIX_CONDITION, data.condition, - VertexProvider.INITIAL_CHILD_NODE_X, 0.45, 0, (mx.mxConstants.DEFAULT_FONTSIZE), EditorStyle.TEXT_INPUT_STYLE, true); - const labelType = this.graph.insertVertex(vertex, url + VertexProvider.ID_SUFFIX_TYPE, data.type, - VertexProvider.INITIAL_CHILD_NODE_X, 0.70, 0, (mx.mxConstants.DEFAULT_FONTSIZE), EditorStyle.TYPE_NAME_STYLE, true); - - this.addEmptyTextStyle(data.variable, labelVariable); - this.addEmptyTextStyle(data.condition, labelCondition); - - labelVariable.isConnectable = () => false; - labelCondition.isConnectable = () => false; - labelType.isConnectable = () => false; - - VertexProvider.adjustChildrenCellSizes(vertex, this.shapeProvider, this.graph); - this.graph.getModel().endUpdate(); - return vertex; - } - - public provideLinkedCEGNode(url: string, x: number, y: number, width: number, height: number, - data: CEGmxModelNode, node: CEGLinkedNode): mxgraph.mxCell { - - const value = node; - const style = this.shapeProvider.getStyle(CEGLinkedNode); - const parent = this.graph.getDefaultParent(); - this.graph.getModel().beginUpdate(); - const vertex = this.graph.insertVertex(parent, url, value, x, y, width, height, style); - const vertexVariable = this.graph.insertVertex(vertex, url + VertexProvider.ID_SUFFIX_VARIABLE, data.variable, - // tslint:disable-next-line: max-line-length - VertexProvider.INITIAL_CHILD_NODE_X, 0.15, 0, (mx.mxConstants.DEFAULT_FONTSIZE), EditorStyle.VARIABLE_NAME_DISABLED_STYLE, true); - const vertexCondition = this.graph.insertVertex(vertex, url + VertexProvider.ID_SUFFIX_CONDITION, data.condition, - VertexProvider.INITIAL_CHILD_NODE_X, 0.4, 0, (mx.mxConstants.DEFAULT_FONTSIZE), EditorStyle.TEXT_INPUT_DISABLED_STYLE, true); - const vertexSymbol = this.graph.insertVertex(vertex, url + VertexProvider.ID_SUFFIX_LINK_ICON, '🔗', - 8, 8, 0, (mx.mxConstants.DEFAULT_FONTSIZE), EditorStyle.ICON_STYLE, false); - - this.addEmptyTextStyle(data.variable, vertexVariable); - this.addEmptyTextStyle(data.condition, vertexCondition); - - vertexVariable.isConnectable = () => false; - vertexCondition.isConnectable = () => false; - vertexSymbol.isConnectable = () => false; - - VertexProvider.adjustChildrenCellSizes(vertex, this.shapeProvider, this.graph); - this.graph.getModel().endUpdate(); - return vertex; - } - - private addEmptyTextStyle(text: string, cell: mxgraph.mxCell) { - if (text === '' || text === undefined || text === null) { - StyleChanger.addStyle(cell, this.graph, EditorStyle.EMPTY_TEXT_NAME); - } - } - - public static adjustChildCellSize(cell: mxgraph.mxCell, nodeWidth: number) { - const g = cell.getGeometry(); - let x = VertexProvider.INITIAL_CHILD_NODE_X; - let w = 0; - const minWidth = VertexProvider.EMPTY_CHILD_NODE_WIDTH; - if (g.width < minWidth && (cell.value === '' || cell.value === null || cell.value === undefined)) { - x = x - (minWidth / nodeWidth) / 2; - w = minWidth; - } - cell.getGeometry().setRect(x, g.y, w, g.height); - } - - public static adjustChildrenPositions(cell: mxgraph.mxCell) { - const g = cell.getGeometry(); - let parentWidth = g.width; - if (cell.children !== undefined && cell.children !== null) { - for (const child of cell.children) { - const childGeometry = child.getGeometry(); - let x = (parentWidth - childGeometry.width) / 2 / parentWidth; - child.getGeometry().setRect(x, childGeometry.y, childGeometry.width, childGeometry.height); - } - } - } - - public static adjustChildrenCellSizes(cell: mxgraph.mxCell, shapeProvider: ShapeProvider, graph: mxgraph.mxGraph) { - const g = cell.getGeometry(); - let parentWidth = g.width; - if (cell.children !== undefined && cell.children !== null) { - for (const child of cell.children) { - if (child.getId().endsWith(VertexProvider.ID_SUFFIX_TYPE)) { - continue; - } - const childGeometry = child.getGeometry(); - let preferredSize = graph.getPreferredSizeForCell(child); - let shapedata = shapeProvider.getInitialData(cell.style.split(';')[0]); - let widthMax = parentWidth - shapedata.size.margin; - let width = Math.min(preferredSize.width, widthMax); - child.getGeometry().setRect(childGeometry.x, childGeometry.y, width, childGeometry.height); - } - } - VertexProvider.adjustChildrenPositions(cell); - } - public async provideVertex(node: IModelNode, x?: number, y?: number): Promise { const width = node.width > 0 ? node.width : this.shapeProvider.getInitialSize(node).width; const height = node.height > 0 ? node.height : this.shapeProvider.getInitialSize(node).height; - if (Type.is(node, CEGNode)) { - let n = node as CEGNode; - const data = new CEGmxModelNode(n.variable, n.condition, n.type); - return this.provideCEGNode(node.url, x || node.x, y || node.y, width, height, data, node as CEGNode); - } + let value = (this.nodeNameConverter ? this.nodeNameConverter.convertTo(node) : node.name); if (Type.is(node, CEGLinkedNode)) { let n = node as CEGLinkedNode; let linkedNode = undefined; - let variable = ''; - let condition = ''; - let type = ''; + let variable = undefined; + let condition = undefined; if (n.linkTo !== undefined) { linkedNode = await this.dataService.readElement(n.linkTo.url) as CEGNode; if (linkedNode !== undefined) { variable = linkedNode.variable; condition = linkedNode.condition; - type = linkedNode.type; } } - const data = new CEGmxModelNode(variable, condition, type); - return this.provideLinkedCEGNode(node.url, x || node.x, y || node.y, width, height, data, node as CEGLinkedNode); + const data = new CEGmxModelLinkedNode(variable, condition); + value = data; } - const value: string = (this.nodeNameConverter ? this.nodeNameConverter.convertTo(node) : node.name) as string; const style = this.shapeProvider.getStyle(node); const parent = this.graph.getDefaultParent(); const vertex = this.graph.insertVertex(parent, node.url, value, x || node.x, y || node.y, width, height, style); return vertex; } - public initRenderer(graph: mxgraph.mxGraph) { - graph.convertValueToString = (cell: mxgraph.mxCell) => { - if (cell.getId().endsWith(VertexProvider.ID_SUFFIX_TYPE)) { - let parent = cell.getParent(); - let edges = parent.edges; - if (edges === undefined || edges === null) { - return; - } - let inDegree = edges.filter(value => value.target === parent).length; - if (inDegree < 2) { - return ''; + public initCEGVertexRenderer() { + this.graph.getLabel = (cell: mxgraph.mxCell): any => { + if (cell.isVertex()) { + let value = cell.value; + let table = document.createElement('table'); + table.style.height = '100%'; + table.style.width = '100%'; + + let body = document.createElement('tbody'); + let tr1 = document.createElement('tr'); + let tdIcons = document.createElement('td'); + tdIcons.rowSpan = 2; + tdIcons.style.width = '15px'; + + let td1 = document.createElement('td'); + td1.style.textAlign = 'center'; + td1.style.color = '#774400'; + td1.style.fontWeight = 'bold'; + + let tr2 = document.createElement('tr'); + let td2 = document.createElement('td'); + td2.style.textAlign = 'center'; + td2.style.color = '#774400'; + + tr1.appendChild(tdIcons); + tr1.appendChild(td1); + tr2.appendChild(td2); + body.appendChild(tr1); + body.appendChild(tr2); + if (value instanceof CEGmxModelLinkedNode) { + this.createCEGLinkedNodeRendering(cell, td1, td2); + mx.mxUtils.writeln(tdIcons, '🔗'); + } else if (value instanceof CEGmxModelNode) { + this.createCEGNodeRendering(cell, body, td1, td2); + tdIcons.rowSpan = 3; } + this.createCEGIconsRendering(cell, tdIcons); + this.createCEGColorRendering(cell); + + table.appendChild(body); + return table; + } + return ''; + }; + } + + private createCEGNodeRendering(cell: mxgraph.mxCell, + body: HTMLTableSectionElement, td1: HTMLTableDataCellElement, td2: HTMLTableDataCellElement) { + let value = cell.value as CEGmxModelNode; + td1.setAttribute('data-node', VertexProvider.ID_VARIABLE); + if (value.variable === '') { + mx.mxUtils.write(td1, '[' + this.translate.instant('typeHere') + ']'); + td1.style.fontStyle = 'italic'; + td1.style.opacity = '50%'; + } else { + mx.mxUtils.write(td1, value.variable); + } + + td2.setAttribute('data-node', VertexProvider.ID_CONDITION); + if (value.condition === '') { + mx.mxUtils.write(td2, '[' + this.translate.instant('typeHere') + ']'); + td2.style.fontStyle = 'italic'; + td2.style.opacity = '50%'; + } else { + mx.mxUtils.write(td2, value.condition); + } + + if (cell.edges !== null) { + let incomingEdges = cell.edges.filter(e => e.target === cell); + if (incomingEdges.length > 1) { + let tr3 = document.createElement('tr'); let dropdown = document.createElement('select'); let options = ['AND', 'OR']; let optionElements: HTMLOptionElement[] = []; @@ -201,59 +142,103 @@ export class VertexProvider extends ProviderBase { let optionElem = document.createElement('option'); optionElem.innerHTML = this.translate.instant(option); optionElem.setAttribute('value', option); - if (option === cell.getValue()) { + if (option === value.type) { optionElem.setAttribute('selected', 'true'); dropdown.value = option; } dropdown.appendChild(optionElem); optionElements.push(optionElem); } - - mx.mxEvent.addListener(dropdown, 'click', async (evt: MouseEvent) => { - const element = await this.dataService.readElement(parent.id, true); - const guardResult = await this.changeGuard.guardSelectedElements([element]); - if (!guardResult) { - evt.stopPropagation(); - evt.preventDefault(); - } - }); + tr3.appendChild(dropdown); + body.appendChild(tr3); mx.mxEvent.addListener(dropdown, 'change', async (evt: mxgraph.mxEventObject) => { - graph.model.setValue(cell, dropdown.value); + value.type = dropdown.value; + let newValue = mx.mxUtils.clone(value); + newValue.type = dropdown.value; + this.graph.getModel().setValue(cell, newValue); }); - cell.valueChanged = function (newValue: any) { - let sel = optionElements.find(e => e.value === newValue); - if (sel !== undefined) { - sel.setAttribute('selected', 'true'); - } - return mx.mxCell.prototype.valueChanged.bind(cell)(newValue); - }; - return dropdown; } - return mx.mxGraph.prototype.convertValueToString.bind(graph)(cell); - }; + } + } - graph.getLabel = function (cell: mxgraph.mxCell) { - if (VertexProvider.isCEGTextInputCell(cell)) { - if (cell.value !== undefined && cell.value !== null) { - return he.encode(cell.value); - } - } - return mx.mxGraph.prototype.getLabel.bind(graph)(cell); - }; + private createCEGLinkedNodeRendering(cell: mxgraph.mxCell, td1: HTMLTableDataCellElement, td2: HTMLTableDataCellElement) { + let value = cell.value as CEGmxModelLinkedNode; + if (value.variable === undefined || value.condition === undefined) { + mx.mxUtils.write(td1, '[' + this.translate.instant('hintAddLinkedNode') + ']'); + td1.style.fontStyle = 'italic'; + td1.style.opacity = '50%'; - graph.getTooltipForCell = (cell) => { - if (cell.getId().endsWith(VertexProvider.ID_SUFFIX_TYPE) || cell.value === null || cell.value === undefined) { - return ''; + } else { + mx.mxUtils.write(td1, value.variable); + mx.mxUtils.write(td2, value.condition); + } + } + private createCEGIconsRendering(cell: mxgraph.mxCell, tdIcons: HTMLTableDataCellElement) { + if (cell.edges != null && cell.edges.length > 0) { + let incomingEdges = cell.edges.filter(e => e.target === cell); + let outgoingEdges = cell.edges.filter(e => e.source === cell); + if (incomingEdges.length > 0 && outgoingEdges.length > 0) { + tdIcons.appendChild(this.createNodeIcon(NodeType.INNER)); + } else if (incomingEdges.length > 0) { + tdIcons.appendChild(this.createNodeIcon(NodeType.EFFECT)); + } else if (outgoingEdges.length > 0) { + tdIcons.appendChild(this.createNodeIcon(NodeType.CAUSE)); } - return he.encode(cell.value + '').replace('[object Object]', ''); - }; + } else { + let icon = document.createElement('i'); + icon.className = 'fa fa-exclamation-circle'; + icon.setAttribute('aria-hidden', 'true'); + tdIcons.appendChild(icon); + } + } + private createCEGColorRendering(cell: mxgraph.mxCell) { + let nodeType = GraphicalEditor.getCEGNodeType(cell); + if (!cell.style.match(new RegExp(';*' + nodeType + ';*'))) { + StyleChanger.removeStyle(cell, this.graph, EditorStyle.CAUSE_STYLE_NAME); + StyleChanger.removeStyle(cell, this.graph, EditorStyle.EFFECT_STYLE_NAME); + StyleChanger.removeStyle(cell, this.graph, EditorStyle.INNER_STYLE_NAME); + StyleChanger.addStyle(cell, this.graph, nodeType); + } } - public static isCEGTextInputCell(cell: mxgraph.mxCell): boolean { - return [VertexProvider.ID_SUFFIX_VARIABLE, VertexProvider.ID_SUFFIX_CONDITION] - .find(suffix => cell.id.endsWith(suffix)) !== undefined; + private createNodeIcon(type: NodeType): HTMLElement { + let container = document.createElement('span'); + container.className = 'fa-stack'; + container.style.fontWeight = 'bold'; + container.setAttribute('aria-hidden', 'true'); + + let box = document.createElement('i'); + box.className = 'fa fa-square-o fa-stack-1x'; + box.style.fontSize = '1.6em'; + box.style.fontWeight = 'bold'; + + let arrow = document.createElement('i'); + arrow.className = 'fa fa-long-arrow-right fa-stack-1x'; + arrow.style.top = '-1px'; + arrow.style.fontWeight = 'bold'; + arrow.style.fontSize = '1.1em'; + container.appendChild(box); + container.appendChild(arrow); + + if (type === NodeType.EFFECT) { + arrow.style.left = '-7px'; + } + if (type === NodeType.CAUSE) { + arrow.style.left = '12px'; + } + if (type === NodeType.INNER) { + arrow.style.left = '-7px'; + let arrowRight = document.createElement('i'); + arrowRight.className = 'fa fa-long-arrow-right fa-stack-1x'; + arrowRight.style.top = '-1px'; + arrowRight.style.left = '12px'; + arrowRight.style.fontWeight = 'bold'; + arrowRight.style.fontSize = '1.1em'; + container.appendChild(arrowRight); + } + return container; } } diff --git a/web/src/assets/i18n/de.json b/web/src/assets/i18n/de.json index b0bfb0d08..462e69808 100644 --- a/web/src/assets/i18n/de.json +++ b/web/src/assets/i18n/de.json @@ -150,6 +150,7 @@ "fieldInvalid": "Feld ungültig", "graphicalEditorErrorTitle": "Fehler im Editor", "hideGrid": "Hilfslinien ausblenden", + "hintAddLinkedNode": "Bitte Knoten verlinken", "inactivityLoggedOut": "Sie wurden automatisch abgemeldet.", "invalidCharacter": "Ungültige Zeichen: , ; |", "layoutErrorCircle": "Das Modell konnte nicht geordnet werden: Der Graph enthält einen Kreis.", @@ -283,6 +284,7 @@ "triedToReadContensForVirtualElement": "Versuch, Inhalt eines virtuellen Elements zu laden", "triedToReadContensVirtuallyButCouldNotFindThemFallingBackToServer": "Inhalte sollten virtuell geladen werden, wurden aber nicht gefunden. Falle auf Server zurück.", "triedToReadElementVirtuallyButCouldNotFindItFallingBackToServer": "Element sollte virtuell geladen werden, wurde aber nicht gefunden. Falle auf Server zurück.", + "typeHere": "Hier tippen", "undo": "Rückgängig", "undoAll": "Alle rückgängig", "unknownError": "Ein unbekannter Fehler ist auf dem Server aufgetreten", diff --git a/web/src/assets/i18n/gb.json b/web/src/assets/i18n/gb.json index a1d1b05f2..d66ed601e 100644 --- a/web/src/assets/i18n/gb.json +++ b/web/src/assets/i18n/gb.json @@ -150,6 +150,7 @@ "fieldInvalid": "Field invalid", "graphicalEditorErrorTitle": "Editor Error", "hideGrid": "Hide grid", + "hintAddLinkedNode": "Please link another node", "inactivityLoggedOut": "You were logged out automatically.", "invalidCharacter": "Invalid Characters: , ; |", "layoutErrorCircle": "Could not layout the model: There is a circle in the graph.", @@ -283,6 +284,7 @@ "triedToReadContensForVirtualElement": "Tried to read contents for virtual element.", "triedToReadContensVirtuallyButCouldNotFindThemFallingBackToServer": "Tried to read contents virtually, but could not find them. Falling back to server.", "triedToReadElementVirtuallyButCouldNotFindItFallingBackToServer": "Tried to read element virtually, but could not find it. Falling back to server.", + "typeHere": "type here", "undo": "Undo", "undoAll": "Undo all", "unknownError": "An unknown error occurred on the server",