diff --git a/packages/editor/cypress/snapshots/relationship.cy.ts/move ctl pont 0.snap.png b/packages/editor/cypress/snapshots/relationship.cy.ts/move ctl pont 0.snap.png index bec552bf..cbae598a 100644 Binary files a/packages/editor/cypress/snapshots/relationship.cy.ts/move ctl pont 0.snap.png and b/packages/editor/cypress/snapshots/relationship.cy.ts/move ctl pont 0.snap.png differ diff --git a/packages/editor/cypress/snapshots/topicDragAndDrop.cy.ts/moveleftNode.snap.png b/packages/editor/cypress/snapshots/topicDragAndDrop.cy.ts/moveleftNode.snap.png index 4c63b69a..3653755f 100644 Binary files a/packages/editor/cypress/snapshots/topicDragAndDrop.cy.ts/moveleftNode.snap.png and b/packages/editor/cypress/snapshots/topicDragAndDrop.cy.ts/moveleftNode.snap.png differ diff --git a/packages/editor/cypress/snapshots/topicIcon.cy.ts/add-new-icon.snap.png b/packages/editor/cypress/snapshots/topicIcon.cy.ts/add-new-icon.snap.png index 117ce247..78388ecf 100644 Binary files a/packages/editor/cypress/snapshots/topicIcon.cy.ts/add-new-icon.snap.png and b/packages/editor/cypress/snapshots/topicIcon.cy.ts/add-new-icon.snap.png differ diff --git a/packages/editor/cypress/snapshots/topicManager.cy.ts/addChildNodeSortcut.snap.png b/packages/editor/cypress/snapshots/topicManager.cy.ts/addChildNodeSortcut.snap.png index 8d34e100..d869e227 100644 Binary files a/packages/editor/cypress/snapshots/topicManager.cy.ts/addChildNodeSortcut.snap.png and b/packages/editor/cypress/snapshots/topicManager.cy.ts/addChildNodeSortcut.snap.png differ diff --git a/packages/editor/cypress/snapshots/topicManager.cy.ts/editor-shortcut-edit.snap.png b/packages/editor/cypress/snapshots/topicManager.cy.ts/editor-shortcut-edit.snap.png index b8ded9b7..87a73f33 100644 Binary files a/packages/editor/cypress/snapshots/topicManager.cy.ts/editor-shortcut-edit.snap.png and b/packages/editor/cypress/snapshots/topicManager.cy.ts/editor-shortcut-edit.snap.png differ diff --git a/packages/editor/cypress/snapshots/topicShape.cy.ts/topicShapePanel.snap.png b/packages/editor/cypress/snapshots/topicShape.cy.ts/topicShapePanel.snap.png index f7df302d..ca124f68 100644 Binary files a/packages/editor/cypress/snapshots/topicShape.cy.ts/topicShapePanel.snap.png and b/packages/editor/cypress/snapshots/topicShape.cy.ts/topicShapePanel.snap.png differ diff --git a/packages/editor/src/components/editor-toolbar/configBuilder.tsx b/packages/editor/src/components/editor-toolbar/configBuilder.tsx index 7795d0ca..63e26d37 100644 --- a/packages/editor/src/components/editor-toolbar/configBuilder.tsx +++ b/packages/editor/src/components/editor-toolbar/configBuilder.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ import React from 'react'; -import BrushOutlinedIcon from '@mui/icons-material/BrushOutlined'; +import FormatPaintIconOutlineIcon from '@mui/icons-material/FormatPaintOutlined'; import FontDownloadOutlinedIcon from '@mui/icons-material/FontDownloadOutlined'; import TextIncreaseOutlinedIcon from '@mui/icons-material/TextIncreaseOutlined'; import TextDecreaseOutlinedIcon from '@mui/icons-material/TextDecreaseOutlined'; @@ -38,6 +38,7 @@ import TimelineOutined from '@mui/icons-material/TimelineOutlined'; import ShareOutlined from '@mui/icons-material/ShareOutlined'; import SwapCallsOutlined from '@mui/icons-material/SwapCallsOutlined'; import NotInterestedOutlined from '@mui/icons-material/NotInterestedOutlined'; +import ShortcutIconOutlined from '@mui/icons-material/ShortcutOutlined'; import Palette from '@mui/icons-material/Square'; import SquareOutlined from '@mui/icons-material/SquareOutlined'; @@ -62,7 +63,7 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo const modelBuilder = new NodePropertyValueModelBuilder(model.getDesigner()); // eslint-disable-next-line react-hooks/rules-of-hooks const colorAndShapeToolbarConfiguration: ActionConfig = { - icon: , + icon: , tooltip: intl.formatMessage({ id: 'editor-panel.tooltip-topic-style', defaultMessage: 'Topic Style', @@ -182,6 +183,15 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo onClick: () => modelBuilder.getConnectionStyleModel().setValue(LineType.THICK_CURVED), selected: () => modelBuilder.getConnectionStyleModel().getValue() === LineType.THICK_CURVED, }, + { + icon: , + tooltip: intl.formatMessage({ + id: 'editor-panel.tooltip-connection-style-arc', + defaultMessage: 'Arc', + }), + onClick: () => modelBuilder.getConnectionStyleModel().setValue(LineType.ARC), + selected: () => modelBuilder.getConnectionStyleModel().getValue() === LineType.ARC, + }, { icon: , tooltip: intl.formatMessage({ @@ -444,9 +454,9 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo return [ addNodeToolbarConfiguration, deleteNodeToolbarConfiguration, - connectionStyleConfiguration, - fontFormatToolbarConfiguration, colorAndShapeToolbarConfiguration, + fontFormatToolbarConfiguration, + connectionStyleConfiguration, editIconConfiguration, editNoteConfiguration, editLinkUrlConfiguration, diff --git a/packages/mindplot/src/components/ConnectionLine.ts b/packages/mindplot/src/components/ConnectionLine.ts index f192f3be..59df6201 100644 --- a/packages/mindplot/src/components/ConnectionLine.ts +++ b/packages/mindplot/src/components/ConnectionLine.ts @@ -22,6 +22,7 @@ import PositionType from './PositionType'; import Topic from './Topic'; import TopicConfig from './TopicConfig'; import Canvas from './Canvas'; +import ArcLine from './widget/ArcLine'; // eslint-disable-next-line no-shadow export enum LineType { @@ -29,6 +30,7 @@ export enum LineType { POLYLINE_MIDDLE, POLYLINE_CURVED, THICK_CURVED, + ARC, } class ConnectionLine { @@ -81,8 +83,13 @@ class ConnectionLine { line = new CurvedLine(); (line as CurvedLine).setWidth(this._targetTopic.isCentralTopic() ? 15 : 3); break; - default: - throw new Error(`Unexpected line type. ${lineType}`); + case LineType.ARC: + line = new ArcLine(this._sourceTopic, this._targetTopic); + break; + default: { + const exhaustiveCheck: never = lineType; + throw new Error(exhaustiveCheck); + } } return line; } @@ -110,8 +117,13 @@ class ConnectionLine { this._line.setStroke(1, 'solid', color, 1); this._line.setFill(color, 1); break; - default: - throw new Error(`Unexpected line type. ${this._type}`); + case LineType.ARC: + this._line.setStroke(1, 'solid', color, 1); + break; + default: { + const exhaustiveCheck: never = this._type; + throw new Error(exhaustiveCheck); + } } return color; } diff --git a/packages/mindplot/src/components/Designer.ts b/packages/mindplot/src/components/Designer.ts index 365bc388..0d0d380b 100644 --- a/packages/mindplot/src/components/Designer.ts +++ b/packages/mindplot/src/components/Designer.ts @@ -890,10 +890,9 @@ class Designer extends Events { } changeConnectionStyle(type: LineType): void { - const validateFunc = (topic: Topic) => !topic.isCentralTopic(); - - const validateError = $msg('CENTRAL_TOPIC_CONNECTION_STYLE_CAN_NOT_BE_CHANGED'); - const topicsIds = this.getModel().filterTopicsIds(validateFunc, validateError); + const topicsIds = this.getModel() + .filterSelectedTopics() + .map((t) => t.getId()); if (topicsIds.length > 0) { this._actionDispatcher.changeConnectionStyleToTopic(topicsIds, type); } diff --git a/packages/mindplot/src/components/Relationship.ts b/packages/mindplot/src/components/Relationship.ts index e4859ac6..14dfe3e4 100644 --- a/packages/mindplot/src/components/Relationship.ts +++ b/packages/mindplot/src/components/Relationship.ts @@ -277,8 +277,6 @@ class Relationship extends ConnectionLine { this._focusShape.setSrcControlPoint(ctrlPoints[0]); this._focusShape.setDestControlPoint(ctrlPoints[1]); - - this._focusShape.updateLine(); } addEvent(eventType: string, listener: () => void) { diff --git a/packages/mindplot/src/components/widget/ArcLine.ts b/packages/mindplot/src/components/widget/ArcLine.ts new file mode 100644 index 00000000..e866e1e9 --- /dev/null +++ b/packages/mindplot/src/components/widget/ArcLine.ts @@ -0,0 +1,46 @@ +/* + * Copyright [2021] [wisemapping] + * + * Licensed under WiseMapping Public License, Version 1.0 (the "License"). + * It is basically the Apache License, Version 2.0 (the "License") plus the + * "powered by wisemapping" text requirement on every single page; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the license at + * + * http://www.wisemapping.org/license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ArcLine as ArcLine2d } from '@wisemapping/web2d'; +import Topic from '../Topic'; + +class ArcLine extends ArcLine2d { + private _targetTopic: Topic; + + private _sourceTopic: Topic; + + constructor(sourceTopic: Topic, targetTopic: Topic) { + super(); + this._targetTopic = targetTopic; + this._sourceTopic = sourceTopic; + } + + // Adjust the x position so there is not overlap with the connector. + setFrom(x: number, y: number): void { + let xOffset = x; + if (this._targetTopic.isCentralTopic()) { + const sourceX = this._sourceTopic.getPosition().x; + xOffset = Math.sign(sourceX) * (this._targetTopic.getSize().width / 3); + } else { + xOffset = x + 3 * Math.sign(x); + } + + super.setFrom(xOffset, y); + } +} +export default ArcLine; diff --git a/packages/web2d/package.json b/packages/web2d/package.json index fff99b65..6d101614 100644 --- a/packages/web2d/package.json +++ b/packages/web2d/package.json @@ -20,7 +20,6 @@ "dev": "webpack --config webpack.dev.js", "build": "webpack --config webpack.prod.js", "lint": "eslint src", - "playground": "webpack serve --config webpack.playground.js", "cy:run": "cypress run", "cy:open": "cypress open", "test:integration": "start-server-and-test storybook http-get://localhost:6006 cy:run", diff --git a/packages/web2d/src/components/ArcLine.ts b/packages/web2d/src/components/ArcLine.ts new file mode 100644 index 00000000..580c6a5e --- /dev/null +++ b/packages/web2d/src/components/ArcLine.ts @@ -0,0 +1,103 @@ +/* + * Copyright [2021] [wisemapping] + * + * Licensed under WiseMapping Public License, Version 1.0 (the "License"). + * It is basically the Apache License, Version 2.0 (the "License") plus the + * "powered by wisemapping" text requirement on every single page; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the license at + * + * http://www.wisemapping.org/license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { $assert } from '@wisemapping/core-js'; +import WorkspaceElement from './WorkspaceElement'; +import Line from './Line'; +import StyleAttributes from './StyleAttributes'; +import Toolkit from './Toolkit'; +import ArcLinePeer from './peer/svg/ArcLinePeer'; +import PositionType from './PositionType'; + +class ArcLine extends WorkspaceElement implements Line { + constructor(attributes?: StyleAttributes) { + const peer = Toolkit.createArcLine(); + const defaultAttributes = { + strokeColor: 'blue', + strokeWidth: 1, + strokeStyle: 'solid', + strokeOpacity: 1, + fill: 'none 0', + }; + + const mergedAttr = { ...defaultAttributes, ...attributes }; + super(peer, mergedAttr); + } + + getType(): string { + return 'ArcLine'; + } + + setFrom(x: number, y: number): void { + $assert(!Number.isNaN(x), 'x must be defined'); + $assert(!Number.isNaN(y), 'y must be defined'); + + this.peer.setFrom(x, y); + } + + setTo(x: number, y: number): void { + $assert(!Number.isNaN(x), 'x must be defined'); + $assert(!Number.isNaN(y), 'y must be defined'); + + this.peer.setTo(x, y); + } + + getFrom() { + return this.peer.getFrom(); + } + + getTo() { + return this.peer.getTo(); + } + + getElementClass(): ArcLine { + return this; + } + + setIsSrcControlPointCustom(value: boolean): void { + throw new Error('Method not implemented.'); + } + + setIsDestControlPointCustom(value: boolean): void { + throw new Error('Method not implemented.'); + } + + setDashed(v: number, v2: number): void { + throw new Error('Method not implemented.'); + } + setSrcControlPoint(value: PositionType): void { + throw new Error('Method not implemented.'); + } + + setDestControlPoint(value: PositionType): void { + throw new Error('Method not implemented.'); + } + + isDestControlPointCustom(): boolean { + throw new Error('Method not implemented.'); + } + + isSrcControlPointCustom(): boolean { + throw new Error('Method not implemented.'); + } + + getControlPoints(): [PositionType, PositionType] { + throw new Error('Method not implemented.'); + } +} + +export default ArcLine; diff --git a/packages/web2d/src/components/Line.ts b/packages/web2d/src/components/Line.ts index ce099c68..73023210 100644 --- a/packages/web2d/src/components/Line.ts +++ b/packages/web2d/src/components/Line.ts @@ -70,8 +70,6 @@ interface Line { removeEvent(value, listener): void; - updateLine(): void; - getElementClass(): WorkspaceElement; } export default Line; diff --git a/packages/web2d/src/components/PolyLine.ts b/packages/web2d/src/components/PolyLine.ts index 309946a4..f1aa68a5 100644 --- a/packages/web2d/src/components/PolyLine.ts +++ b/packages/web2d/src/components/PolyLine.ts @@ -42,9 +42,6 @@ class PolyLine extends WorkspaceElement implements Line { return this; } - updateLine() { - throw new Error('Method not implemented.'); - } getTo(): PositionType { throw new Error('Method not implemented.'); } diff --git a/packages/web2d/src/components/StraightLine.ts b/packages/web2d/src/components/StraightLine.ts index 808e25fe..0e6017b0 100644 --- a/packages/web2d/src/components/StraightLine.ts +++ b/packages/web2d/src/components/StraightLine.ts @@ -36,9 +36,6 @@ class StraightLine extends WorkspaceElement implements Line { return this; } - updateLine() { - throw new Error('Method not implemented.'); - } setIsSrcControlPointCustom(value: boolean): void { throw new Error('Method not implemented.'); } diff --git a/packages/web2d/src/components/Toolkit.ts b/packages/web2d/src/components/Toolkit.ts index efb47a2b..e5f33427 100644 --- a/packages/web2d/src/components/Toolkit.ts +++ b/packages/web2d/src/components/Toolkit.ts @@ -26,6 +26,7 @@ import ArrowPeer from './peer/svg/ArrowPeer'; import TextPeer from './peer/svg/TextPeer'; import ImagePeer from './peer/svg/ImagePeer'; import RectPeer from './peer/svg/RectPeer'; +import ArcLinePeer from './peer/svg/ArcLinePeer'; class Toolkit { static createWorkspace(element?: HTMLElement) { @@ -52,6 +53,10 @@ class Toolkit { return new CurvedLinePeer(); } + static createArcLine(): ArcLinePeer { + return new ArcLinePeer(); + } + static createArrow() { return new ArrowPeer(); } diff --git a/packages/web2d/src/components/peer/svg/ArcLinePeer.ts b/packages/web2d/src/components/peer/svg/ArcLinePeer.ts new file mode 100644 index 00000000..ed14548c --- /dev/null +++ b/packages/web2d/src/components/peer/svg/ArcLinePeer.ts @@ -0,0 +1,85 @@ +/* + * Copyright [2021] [wisemapping] + * + * Licensed under WiseMapping Public License, Version 1.0 (the "License"). + * It is basically the Apache License, Version 2.0 (the "License") plus the + * "powered by wisemapping" text requirement on every single page; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the license at + * + * http://www.wisemapping.org/license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { $defined } from '@wisemapping/core-js'; +import PositionType from '../../PositionType'; +import ElementPeer from './ElementPeer'; + +class ArcLinePeer extends ElementPeer { + private _x1: number; + private _y1: number; + private _x2: number; + private _y2: number; + + constructor() { + const svgElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'path'); + super(svgElement); + this._x1 = 0; + this._x2 = 0; + this._y1 = 0; + this._y2 = 0; + this._updatePath(); + } + + setFrom(x1: number, y1: number): void { + const change = this._x1 !== x1 || this._y1 !== y1; + this._x1 = x1; + this._y1 = y1; + if (change) { + this._updatePath(); + } + } + + setTo(x2: number, y2: number) { + const change = this._x2 !== x2 || this._y2 !== y2; + this._x2 = x2; + this._y2 = y2; + if (change) this._updatePath(); + } + + getFrom(): PositionType { + return { x: this._x1, y: this._y1 }; + } + + getTo(): PositionType { + return { x: this._x2, y: this._y2 }; + } + + setStrokeWidth(width: number): void { + this._native.setAttribute('stroke-width', String(width)); + } + + private static pointToStr(x: number, y: number) { + return `${x.toFixed(1)},${y.toFixed(1)} `; + } + + private _updatePath() { + // Update style based on width .... + if ($defined(this._x1) && $defined(this._y1) && $defined(this._x2) && $defined(this._y2)) { + const fromPoint = ArcLinePeer.pointToStr(this._x1, this._y1); + const toPoint = ArcLinePeer.pointToStr(this._x2, this._y2); + + const curveP1 = ArcLinePeer.pointToStr(this._x1, this._y1 + (this._y2 - this._y1) / 8); + const curveP2 = ArcLinePeer.pointToStr(this._x2 - (this._x2 - this._x1), this._y2); + + const path = `M${fromPoint} C${curveP1},${curveP2} ${toPoint}`; + this._native.setAttribute('d', path); + } + } +} + +export default ArcLinePeer; diff --git a/packages/web2d/src/index.ts b/packages/web2d/src/index.ts index cbcc8f8d..73e57106 100644 --- a/packages/web2d/src/index.ts +++ b/packages/web2d/src/index.ts @@ -27,6 +27,7 @@ import Rect from './components/Rect'; import Text from './components/Text'; import Point from './components/Point'; import Image from './components/Image'; +import ArcLine from './components/ArcLine'; import WorkspaceElement from './components/WorkspaceElement'; import Line from './components/Line'; import ElementPeer from './components/peer/svg/ElementPeer'; @@ -42,6 +43,7 @@ export { StraightLine, Point, PolyLine, + ArcLine, Rect, Text, Workspace, diff --git a/packages/web2d/storybook/src/stories/ArcLine.js b/packages/web2d/storybook/src/stories/ArcLine.js new file mode 100644 index 00000000..bf9e4392 --- /dev/null +++ b/packages/web2d/storybook/src/stories/ArcLine.js @@ -0,0 +1,86 @@ +/* eslint-disable import/prefer-default-export */ +// eslint-disable-next-line import/prefer-default-export +import Ellipse from '../../../src/components/Ellipse'; +import Workspace from '../../../src/components/Workspace'; +import ArcLine from '../../../src/components/ArcLine'; + +export const createArcLine = ({ + strokeColor, + strokeWidth, + strokeStyle, +}) => { + const divElem = document.createElement('div'); + + const workspace = new Workspace(); + workspace.setSize('400px', '400px'); + workspace.setCoordSize(400, 400); + workspace.setCoordOrigin(-200, -200); + + // Line 1 ... + const line1 = new ArcLine(); + line1.setFrom(0, 0); + line1.setTo(100, 100); + line1.setStroke(strokeWidth, strokeStyle, strokeColor, 1); + workspace.append(line1); + + const line2 = new ArcLine(); + line2.setFrom(0, 0); + line2.setTo(-100, -100); + line2.setStroke(strokeWidth, strokeStyle, strokeColor, 1); + workspace.append(line2); + + const line3 = new ArcLine(); + line3.setFrom(0, 0); + line3.setTo(100, -100); + line3.setStroke(strokeWidth, strokeStyle, strokeColor, 1); + workspace.append(line3); + + const line4 = new ArcLine(); + line4.setFrom(0, 0); + line4.setTo(-100, 100); + line4.setStroke(strokeWidth, strokeStyle, strokeColor, 1); + workspace.append(line4); + + const line5 = new ArcLine(); + line5.setFrom(0, 0); + line5.setTo(-100, 0); + line5.setStroke(strokeWidth, strokeStyle, strokeColor, 1); + workspace.append(line5); + + const line6 = new ArcLine(); + line6.setFrom(0, 0); + line6.setTo(100, 0); + line6.setStroke(strokeWidth, strokeStyle, strokeColor, 1); + workspace.append(line6); + + // Add referene point ... + const e1 = new Ellipse(); + e1.setSize(5, 5); + e1.setPosition(0, 0); + e1.setFill('red'); + workspace.append(e1); + + const e2 = new Ellipse(); + e2.setPosition(-100, -100); + e2.setSize(10, 10); + workspace.append(e2); + + const e3 = new Ellipse(); + e3.setPosition(100, 100); + e3.setSize(10, 10); + workspace.append(e3); + + const e4 = new Ellipse(); + e4.setPosition(-100, 100); + e4.setSize(10, 10); + workspace.append(e4); + + const e5 = new Ellipse(); + e5.setPosition(100, -100); + e5.setSize(10, 10); + workspace.append(e5); + + workspace.addItAsChildTo(divElem); + + return divElem; +}; diff --git a/packages/web2d/storybook/src/stories/ArcLine.stories.js b/packages/web2d/storybook/src/stories/ArcLine.stories.js new file mode 100644 index 00000000..e780f6cc --- /dev/null +++ b/packages/web2d/storybook/src/stories/ArcLine.stories.js @@ -0,0 +1,32 @@ +import { createArcLine } from './ArcLine'; + +// More on default export: https://storybook.js.org/docs/html/writing-stories/introduction#default-export +export default { + title: 'Shapes/ArcLine', + // More on argTypes: https://storybook.js.org/docs/html/api/argtypes + argTypes: { + strokeColor: { control: 'color' }, + strokeStyle: { + control: { type: 'select' }, + options: ['dash', 'dot', 'solid', 'longdash', 'dashdot'], + }, + strokeWidth: { control: { type: 'number', min: 0, max: 30, step: 1 } }, + }, +}; + +// More on component templates: https://storybook.js.org/docs/html/writing-stories/introduction#using-args +const Template = ({ label, ...args }) => createArcLine({ label, ...args }); + +export const Width = Template.bind({}); +Width.args = { + strokeWidth: 3, + strokeStyle: 'solid', + strokeColor: 'blue', +}; + +export const Stroke = Template.bind({}); +Stroke.args = { + strokeWidth: 10, + strokeStyle: 'longdash', + strokeColor: 'red', +};