diff --git a/packages/editor/package.json b/packages/editor/package.json index 70303799..b4534bf1 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -25,7 +25,8 @@ "cypress": "^12.3.0", "cypress-image-snapshot": "^4.0.1", "jest-transform-stub": "^2.0.0", - "react": "^18.2.0" + "react": "^18.2.0", + "start-server-and-test": "^2.0.0" }, "dependencies": { "@wisemapping/mindplot": "^5.0.20", diff --git a/packages/editor/src/classes/model/node-property-builder/index.ts b/packages/editor/src/classes/model/node-property-builder/index.ts index 0826182b..fc8a4c4a 100644 --- a/packages/editor/src/classes/model/node-property-builder/index.ts +++ b/packages/editor/src/classes/model/node-property-builder/index.ts @@ -224,7 +224,7 @@ class NodePropertyBuilder { if (!this.topicShapeModel) this.topicShapeModel = { getValue: () => this.uniqueOrNull((node) => node.getShapeType()) as TopicShapeType, - setValue: (value: TopicShapeType) => this.designer.changeTopicShape(value), + setValue: (value: TopicShapeType) => this.designer.changeShapeType(value), }; return this.topicShapeModel; } diff --git a/packages/mindplot/src/components/CentralTopic.ts b/packages/mindplot/src/components/CentralTopic.ts index 87d1ad4c..98486015 100644 --- a/packages/mindplot/src/components/CentralTopic.ts +++ b/packages/mindplot/src/components/CentralTopic.ts @@ -43,8 +43,8 @@ class CentralTopic extends Topic { super.setCursor(type === 'move' ? 'default' : type); } - updateTopicShape(): void { - // Overwite behaviour ... + updateTopicShape(): boolean { + return true; } updatePositionOnChangeSize(): void { diff --git a/packages/mindplot/src/components/Designer.ts b/packages/mindplot/src/components/Designer.ts index 3b049b11..3cdc6fea 100644 --- a/packages/mindplot/src/components/Designer.ts +++ b/packages/mindplot/src/components/Designer.ts @@ -898,7 +898,7 @@ class Designer extends EventDispispatcher { } } - changeTopicShape(shape: TopicShapeType): void { + changeShapeType(shape: TopicShapeType): void { const validateFunc = (topic: Topic) => !(topic.getType() === 'CentralTopic' && (shape === 'line' || shape === 'none')); diff --git a/packages/mindplot/src/components/DesignerActionRunner.ts b/packages/mindplot/src/components/DesignerActionRunner.ts index 647f6b18..7b006818 100644 --- a/packages/mindplot/src/components/DesignerActionRunner.ts +++ b/packages/mindplot/src/components/DesignerActionRunner.ts @@ -41,6 +41,7 @@ class DesignerActionRunner { $assert(command, 'command can not be null'); command.execute(this._context); this._undoManager.enqueue(command); + this.fireChangeEvent(); LayoutEventBus.fireEvent('forceLayout'); } diff --git a/packages/mindplot/src/components/MainTopic.ts b/packages/mindplot/src/components/MainTopic.ts index f6d85d67..353536d2 100644 --- a/packages/mindplot/src/components/MainTopic.ts +++ b/packages/mindplot/src/components/MainTopic.ts @@ -65,10 +65,6 @@ class MainTopic extends Topic { return group; } - updateTopicShape() { - this.redrawShapeType(); - } - disconnect(canvas: Canvas) { super.disconnect(canvas); diff --git a/packages/mindplot/src/components/StandaloneActionDispatcher.ts b/packages/mindplot/src/components/StandaloneActionDispatcher.ts index 86eeb965..a6f5269f 100644 --- a/packages/mindplot/src/components/StandaloneActionDispatcher.ts +++ b/packages/mindplot/src/components/StandaloneActionDispatcher.ts @@ -199,12 +199,10 @@ class StandaloneActionDispatcher extends ActionDispatcher { } changeShapeTypeToTopic(topicsIds: number[], shapeType: TopicShapeType) { - $assert(topicsIds, 'topicsIds can not be null'); - $assert(shapeType, 'shapeType can not be null'); - const commandFunc = (topic: Topic, commandShapeType: TopicShapeType) => { const result = topic.getShapeType(); topic.setShapeType(commandShapeType); + return result; }; diff --git a/packages/mindplot/src/components/Topic.ts b/packages/mindplot/src/components/Topic.ts index fcdc4700..68ddc45a 100644 --- a/packages/mindplot/src/components/Topic.ts +++ b/packages/mindplot/src/components/Topic.ts @@ -117,34 +117,38 @@ abstract class Topic extends NodeGraph { return this._parent; } - protected redrawShapeType() { - this.removeInnerShape(); + updateTopicShape(): boolean { + const result = this.getInnerShape().getShapeType() !== this.getShapeType(); + if (result) { + this.removeInnerShape(); - // Create a new one ... - const innerShape = this.getInnerShape(); + // Create a new one ... + const innerShape = this.getInnerShape(); - // Update figure size ... - const size = this.getSize(); - this.setSize(size, true); + // Update figure size ... + const size = this.getSize(); + this.setSize(size, true); - const group = this.get2DElement(); - innerShape.appendTo(group); + const group = this.get2DElement(); + innerShape.appendTo(group); - // Move text to the front ... - const text = this.getOrBuildTextShape(); - text.moveToFront(); + // Move text to the front ... + const text = this.getOrBuildTextShape(); + text.moveToFront(); - // Move iconGroup to front ... - const iconGroup = this.getIconGroup(); - if (iconGroup) { - iconGroup.moveToFront(); - } - - // Move connector to front - const connector = this.getShrinkConnector(); - if (connector) { - connector.moveToFront(); + // Move iconGroup to front ... + const iconGroup = this.getIconGroup(); + if (iconGroup) { + iconGroup.moveToFront(); + } + + // Move connector to front + const connector = this.getShrinkConnector(); + if (connector) { + connector.moveToFront(); + } } + return result; } getShapeType(): TopicShapeType { @@ -709,7 +713,7 @@ abstract class Topic extends NodeGraph { this.get2DElement().setPosition(cx, cy); // Update connection lines ... - this._updateConnectionLines(); + this.updateConnection(); // Check object state. this.invariant(); @@ -735,21 +739,6 @@ abstract class Topic extends NodeGraph { return result; } - private _updateConnectionLines(): void { - // Update this to parent line ... - const outgoingLine = this.getOutgoingLine(); - if (outgoingLine) { - outgoingLine.redraw(); - } - - // Update all the incoming lines ... - const incomingLines = this.getIncomingLines(); - incomingLines.forEach((line) => line.redraw()); - - // Update relationship lines - this._relationships.forEach((r) => r.redraw()); - } - setBranchVisibility(value: boolean): void { let current: Topic = this; let parent: Topic | null = this; @@ -948,7 +937,6 @@ abstract class Topic extends NodeGraph { const model = this.getModel(); model.setOrder(value); - // In case of drag a node, color change based on the order ... this.redraw(); } @@ -964,14 +952,10 @@ abstract class Topic extends NodeGraph { // Create a connection line ... const outgoingLine = this.createConnectionLine(targetTopic); - // outgoingLine.setVisibility(false); this._outgoingLine = outgoingLine; canvas.append(outgoingLine); - // Update figure is necessary. - this.updateTopicShape(targetTopic); - // Display connection node... const connector = targetTopic.getShrinkConnector(); if (connector) { @@ -984,6 +968,10 @@ abstract class Topic extends NodeGraph { parentNode: targetTopic.getModel(), childNode: this.getModel(), }); + + // Hack for the case of first node created, it needs to review the positioning problem. + LayoutEventBus.fireEvent('forceLayout'); + this.redraw(); } } @@ -992,8 +980,6 @@ abstract class Topic extends NodeGraph { return new ConnectionLine(this, targetTopic, type); } - abstract updateTopicShape(targetTopic: Topic): void; - append(child: Topic): void { const children = this.getChildren(); children.push(child); @@ -1060,17 +1046,15 @@ abstract class Topic extends NodeGraph { return result; } - private redrawConnection(): boolean { + private updateConnection(): boolean { let result = false; if (this._isInWorkspace) { - // Adjust connection line if there is a change in the parent... if (this._outgoingLine) { // Has the style change ? const connStyleChanged = this._outgoingLine.getLineType() !== this.getParent()!.getConnectionStyle(); if (connStyleChanged) { - // Todo: Review static reference ... const workspace = designer.getWorkSpace(); this._outgoingLine.removeFromWorkspace(workspace); @@ -1079,11 +1063,18 @@ abstract class Topic extends NodeGraph { this._outgoingLine.setVisibility(this.isVisible()); workspace.append(this._outgoingLine); + + const incomingLines = this.getIncomingLines(); + incomingLines.forEach((line) => line.redraw()); + result = true; } + // Force the repaint in case that the main topic color has changed. + const borderColor = this.getBorderColor(); + this._connector!.setColor(borderColor); + this._outgoingLine.redraw(); - result = true; } } return result; @@ -1094,11 +1085,8 @@ abstract class Topic extends NodeGraph { const theme = ThemeFactory.create(this.getModel()); const textShape = this.getOrBuildTextShape(); - // Needs to update inner shape ... - const shapeType = this.getShapeType(); - if (shapeType !== this._innerShape?.getShapeType()) { - this.redrawShapeType(); - } + // Update shape ... + const shapeChanged = this.updateTopicShape(); // Update font ... const fontColor = this.getFontColor(); @@ -1145,32 +1133,27 @@ abstract class Topic extends NodeGraph { const topicWith = iconGroupWith + 2 * textIconSpacing + textWidth + padding * 2; // Update connections ... - const changed = this.redrawConnection(); - this.setSize({ width: topicWith, height: topicHeight }, changed); + const connectionChanged = this.updateConnection(); + this.setSize({ width: topicWith, height: topicHeight }, connectionChanged); // Adjust all topic elements positions ... const yPosition = (topicHeight - textHeight) / 2; iconGroup.setPosition(padding, yPosition - yPosition / 4); textShape.setPosition(padding + iconGroupWith + textIconSpacing, yPosition); + // Update relationship lines + this._relationships.forEach((r) => r.redraw()); + // Update topic color ... + const innerShape = this.getInnerShape(); const borderColor = this.getBorderColor(); - this.getInnerShape().setStroke(null, 'solid', borderColor); + innerShape.setStroke(null, 'solid', borderColor); const bgColor = this.getBackgroundColor(); - this.getInnerShape().setFill(bgColor); + innerShape.setFill(bgColor); - // Force the repaint in case that the main topic color has changed. - if (this.getParent()) { - this._connector!.setColor(borderColor); - - if (this.getParent()?.isCentralTopic()) { - this._outgoingLine?.redraw(); - } - } - - if (redrawChildren) { - this.getChildren().forEach((t) => t.redraw(redrawChildren)); + if (redrawChildren || shapeChanged || connectionChanged) { + this.getChildren().forEach((t) => t.redraw(true)); } } } diff --git a/packages/mindplot/src/components/layout/LayoutManager.ts b/packages/mindplot/src/components/layout/LayoutManager.ts index b2c06316..8d7deaa5 100644 --- a/packages/mindplot/src/components/layout/LayoutManager.ts +++ b/packages/mindplot/src/components/layout/LayoutManager.ts @@ -199,7 +199,6 @@ class LayoutManager extends EventDispispatcher { node.resetPositionState(); node.resetOrderState(); - node.resetFreeState(); this._events.push(event); } this._collectChanges(this._treeSet.getChildren(node)); diff --git a/packages/mindplot/src/components/layout/Node.ts b/packages/mindplot/src/components/layout/Node.ts index a5680521..44e3cf7b 100644 --- a/packages/mindplot/src/components/layout/Node.ts +++ b/packages/mindplot/src/components/layout/Node.ts @@ -55,43 +55,30 @@ class Node { return this._id; } - /** */ - setFree(value) { - this._setProperty('free', value); - } - - /** */ - isFree() { - return this._getProperty('free'); - } - - /** */ - hasFreeChanged() { - return this._isPropertyChanged('free'); - } - /** */ hasFreeDisplacementChanged() { - return this._isPropertyChanged('freeDisplacement'); + return this.isPropertyChanged('freeDisplacement'); } /** */ setShrunken(value: boolean) { - this._setProperty('shrink', value); + this.setProperty('shrink', value); } /** */ areChildrenShrunken() { - return this._getProperty('shrink'); + return this.getProperty('shrink'); } - /** */ setOrder(order: number) { $assert( typeof order === 'number' && Number.isFinite(order), `Order can not be null. Value:${order}`, ); - this._setProperty('order', order); + + if (this.getOrder() !== order) { + this.setProperty('order', order); + } } /** */ @@ -118,68 +105,75 @@ class Node { } } - /** */ - getOrder() { - return this._getProperty('order'); + getOrder(): number { + return this.getProperty('order') as number; } /** */ hasOrderChanged() { - return this._isPropertyChanged('order'); + return this.isPropertyChanged('order'); } /** */ hasPositionChanged() { - return this._isPropertyChanged('position'); + return this.isPropertyChanged('position'); } - /** */ hasSizeChanged(): boolean { - return this._isPropertyChanged('size'); + return this.isPropertyChanged('size'); } - /** */ getPosition(): PositionType { - return this._getProperty('position'); + return this.getProperty('position') as PositionType; } /** */ - setSize(size: SizeType) { - this._setProperty('size', { ...size }); + setSize(size: SizeType): void { + const currentSize = this.getSize(); + if ( + !currentSize || + (currentSize && + (Math.abs(currentSize.height - size.height) > 0.5 || + Math.abs(currentSize.width - size.width) > 0.5)) + ) { + this.setProperty('size', { ...size }); + } } /** */ getSize(): SizeType { - return this._getProperty('size'); + return this.getProperty('size') as SizeType; } - setFreeDisplacement(displacement: PositionType) { + setFreeDisplacement(displacement: PositionType): void { const oldDisplacement = this.getFreeDisplacement(); const newDisplacement = { x: oldDisplacement.x + displacement.x, y: oldDisplacement.y + displacement.y, }; - this._setProperty('freeDisplacement', { ...newDisplacement }); + this.setProperty('freeDisplacement', { ...newDisplacement }); } /** */ - resetFreeDisplacement() { - this._setProperty('freeDisplacement', { x: 0, y: 0 }); - } - - /** */ - getFreeDisplacement() { - const freeDisplacement = this._getProperty('freeDisplacement'); + getFreeDisplacement(): PositionType { + const freeDisplacement = this.getProperty('freeDisplacement') as PositionType; return freeDisplacement || { x: 0, y: 0 }; } - setPosition(position: PositionType) { + setPosition(position: PositionType): void { // This is a performance improvement to avoid movements that really could be avoided. - this._setProperty('position', position); + const currentPos = this.getPosition(); + if ( + !currentPos || + (currentPos && + (Math.abs(currentPos.x - position.x) > 0.5 || Math.abs(currentPos.y - position.y) > 0.5)) + ) { + this.setProperty('position', { ...position }); + } } - _setProperty(key: string, value) { + setProperty(key: string, value) { let prop = this._properties[key]; if (!prop) { prop = { @@ -198,12 +192,12 @@ class Node { this._properties[key] = prop; } - _getProperty(key: string) { + private getProperty(key: string): null | number | PositionType | SizeType { const prop = this._properties[key]; return $defined(prop) ? prop.value : null; } - _isPropertyChanged(key) { + isPropertyChanged(key: string) { const prop = this._properties[key]; return prop ? prop.hasChanged : false; } diff --git a/packages/mindplot/src/components/layout/OriginalLayout.ts b/packages/mindplot/src/components/layout/OriginalLayout.ts index 6a7ba47a..6252fe07 100644 --- a/packages/mindplot/src/components/layout/OriginalLayout.ts +++ b/packages/mindplot/src/components/layout/OriginalLayout.ts @@ -60,10 +60,6 @@ class OriginalLayout { throw new Error('Node already disconnected'); } - // Make it fixed - node.setFree(false); - node.resetFreeDisplacement(); - // Remove from children list. const sorter = parent.getSorter(); sorter.detach(this._treeSet, node); @@ -174,9 +170,6 @@ class OriginalLayout { private fixOverlapping(node: Node, heightById: Map): void { const children = this._treeSet.getChildren(node); - if (node.isFree()) { - this._shiftBranches(node, heightById); - } children.forEach((child) => { this.fixOverlapping(child, heightById); }); @@ -196,7 +189,7 @@ class OriginalLayout { OriginalLayout._branchesOverlap(shiftedBranch, sibling, heightById), ); /* eslint-enable */ - if (!sibling.isFree() || overlappingOccurs) { + if (overlappingOccurs) { const sAmount = node.getFreeDisplacement().y; this._treeSet.shiftBranchPosition(sibling, 0, sAmount); shiftedBranches.push(sibling); diff --git a/packages/mindplot/src/components/layout/RootedTreeSet.ts b/packages/mindplot/src/components/layout/RootedTreeSet.ts index a18562a3..79dfadf0 100644 --- a/packages/mindplot/src/components/layout/RootedTreeSet.ts +++ b/packages/mindplot/src/components/layout/RootedTreeSet.ts @@ -288,7 +288,7 @@ class RootedTreeSet { if (this._rootNodes.includes(node)) { fillColor = '#000'; } else { - fillColor = node.isFree() ? '#abc' : '#c00'; + fillColor = '#c00'; } rect.attr('fill', fillColor); diff --git a/yarn.lock b/yarn.lock index 3ec316e4..67e97aa5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6421,6 +6421,7 @@ __metadata: react: ^18.2.0 react-color: ^2.19.3 react-loader-spinner: ^5.3.4 + start-server-and-test: ^2.0.0 peerDependencies: "@emotion/react": ^11.10.5 "@emotion/styled": ^11.10.5 @@ -20604,6 +20605,26 @@ __metadata: languageName: node linkType: hard +"start-server-and-test@npm:^2.0.0": + version: 2.0.0 + resolution: "start-server-and-test@npm:2.0.0" + dependencies: + arg: ^5.0.2 + bluebird: 3.7.2 + check-more-types: 2.24.0 + debug: 4.3.4 + execa: 5.1.1 + lazy-ass: 1.6.0 + ps-tree: 1.2.0 + wait-on: 7.0.1 + bin: + server-test: src/bin/start.js + start-server-and-test: src/bin/start.js + start-test: src/bin/start.js + checksum: 8788e59ad78275332c78325a804504ac558f06a112d47cb5bc3d012d2bda46add72c863cae2357836fe245ee4e22e2fec0b6d47dbdf5e0f0f5cfd1a57544d100 + languageName: node + linkType: hard + "state-toggle@npm:^1.0.0": version: 1.0.3 resolution: "state-toggle@npm:1.0.3"