diff --git a/demo/src/diagram-viewers/add-diagrams.ts b/demo/src/diagram-viewers/add-diagrams.ts
index b0f5c8d5..09afba43 100644
--- a/demo/src/diagram-viewers/add-diagrams.ts
+++ b/demo/src/diagram-viewers/add-diagrams.ts
@@ -34,6 +34,8 @@ import {
OnMoveTextNodeCallbackType,
OnSelectNodeCallbackType,
OnToggleNadHoverCallbackType,
+ OnSaveCallbackType,
+ MouseMode,
} from '../../../src';
export const addNadToDemo = () => {
@@ -54,13 +56,13 @@ export const addNadToDemo = () => {
true,
false,
null,
- handleToggleNadHover
+ handleToggleNadHover,
+ handleSave,
+ true,
+ MouseMode.MOVE
);
- document
- .getElementById('svg-container-nad')
- ?.getElementsByTagName('svg')[0]
- .setAttribute('style', 'border:2px; border-style:solid;');
+ document.getElementById('svg-container-nad')?.setAttribute('style', 'border:2px; border-style:solid;');
});
fetch(NadSvgExample)
@@ -74,19 +76,21 @@ export const addNadToDemo = () => {
600,
1000,
1200,
- handleNodeMove,
- handleTextNodeMove,
+ null,
+ null,
handleNodeSelect,
false,
false,
null,
- handleToggleNadHover
+ handleToggleNadHover,
+ handleSave,
+ true,
+ null
);
document
.getElementById('svg-container-nad-no-moving')
- ?.getElementsByTagName('svg')[0]
- .setAttribute('style', 'border:2px; border-style:solid;');
+ ?.setAttribute('style', 'border:2px; border-style:solid;');
});
fetch(NadSvgMultibusVLNodesExample)
@@ -106,13 +110,15 @@ export const addNadToDemo = () => {
true,
false,
null,
- handleToggleNadHover
+ handleToggleNadHover,
+ handleSave,
+ true,
+ MouseMode.MOVE
);
document
.getElementById('svg-container-nad-multibus-vlnodes')
- ?.getElementsByTagName('svg')[0]
- .setAttribute('style', 'border:2px; border-style:solid;');
+ ?.setAttribute('style', 'border:2px; border-style:solid;');
});
fetch(NadSvgMultibusVLNodes14Example)
@@ -132,13 +138,15 @@ export const addNadToDemo = () => {
true,
false,
null,
- handleToggleNadHover
+ handleToggleNadHover,
+ handleSave,
+ true,
+ MouseMode.MOVE
);
document
.getElementById('svg-container-nad-multibus-vlnodes14')
- ?.getElementsByTagName('svg')[0]
- .setAttribute('style', 'border:2px; border-style:solid;');
+ ?.setAttribute('style', 'border:2px; border-style:solid;');
});
fetch(NadSvgPstHvdcExample)
@@ -158,13 +166,15 @@ export const addNadToDemo = () => {
true,
false,
null,
- handleToggleNadHover
+ handleToggleNadHover,
+ handleSave,
+ true,
+ MouseMode.MOVE
);
document
.getElementById('svg-container-nad-pst-hvdc')
- ?.getElementsByTagName('svg')[0]
- .setAttribute('style', 'border:2px; border-style:solid;');
+ ?.setAttribute('style', 'border:2px; border-style:solid;');
});
fetch(NadSvgThreeWTDanglingLineUnknownBusExample)
@@ -184,13 +194,15 @@ export const addNadToDemo = () => {
true,
false,
null,
- handleToggleNadHover
+ handleToggleNadHover,
+ handleSave,
+ true,
+ MouseMode.SELECT
);
document
.getElementById('svg-container-nad-threewt-dl-ub')
- ?.getElementsByTagName('svg')[0]
- .setAttribute('style', 'border:2px; border-style:solid;');
+ ?.setAttribute('style', 'border:2px; border-style:solid;');
});
fetch(NadSvgPartialNetworkExample)
@@ -210,13 +222,15 @@ export const addNadToDemo = () => {
true,
true,
null,
- handleToggleNadHover
+ handleToggleNadHover,
+ handleSave,
+ false,
+ MouseMode.MOVE
);
document
.getElementById('svg-container-nad-partial-network')
- ?.getElementsByTagName('svg')[0]
- .setAttribute('style', 'border:2px; border-style:solid;');
+ ?.setAttribute('style', 'border:2px; border-style:solid;');
});
};
@@ -414,3 +428,8 @@ const handleToggleNadHover: OnToggleNadHoverCallbackType = (hovered, mousePositi
console.log(msg);
}
};
+
+const handleSave: OnSaveCallbackType = (svg, metadata) => {
+ console.log(svg);
+ console.log(metadata);
+};
diff --git a/src/components/network-area-diagram-viewer/diagram-utils.ts b/src/components/network-area-diagram-viewer/diagram-utils.ts
index 50a2f10a..ee724140 100644
--- a/src/components/network-area-diagram-viewer/diagram-utils.ts
+++ b/src/components/network-area-diagram-viewer/diagram-utils.ts
@@ -3,6 +3,7 @@
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
*/
import { Point } from '@svgdotjs/svg.js';
@@ -126,7 +127,7 @@ export function getLabelData(point1: Point, point2: Point, arrowLabelShift: numb
}
// get fork position of a multibranch edge
-export function getEdgeFork(point: Point, edgeForkLength: number, angleFork: number) {
+export function getEdgeFork(point: Point, edgeForkLength: number, angleFork: number): Point {
return new Point(point.x + edgeForkLength * Math.cos(angleFork), point.y + edgeForkLength * Math.sin(angleFork));
}
@@ -521,3 +522,53 @@ export function getTextNodeMoves(
{ xOrig: textNode.connectionShiftX, yOrig: textNode.connectionShiftY, xNew: connXNew, yNew: connYNew },
];
}
+
+function getButton(svg: string, title: string, pressed: boolean): HTMLButtonElement {
+ const button = document.createElement('button');
+ button.innerHTML = svg;
+ button.title = title;
+ button.style.height = '25px';
+ button.style.width = '25px';
+ button.style.marginRight = '1px';
+ button.style.marginLeft = '3px';
+ button.style.marginTop = '3px';
+ button.style.padding = '0px';
+ if (pressed) {
+ button.style.border = 'medium solid orange';
+ } else {
+ button.style.border = 'none';
+ }
+ return button;
+}
+
+export function getSaveButton(): HTMLButtonElement {
+ return getButton(
+ '',
+ 'Save',
+ false
+ );
+}
+
+export function getMoveButton(pressed: boolean): HTMLButtonElement {
+ return getButton(
+ '',
+ 'Move',
+ pressed
+ );
+}
+
+export function getSelectButton(pressed: boolean): HTMLButtonElement {
+ return getButton(
+ '',
+ 'Select',
+ pressed
+ );
+}
+
+export function pressButton(button: HTMLButtonElement) {
+ button.style.border = 'medium solid orange';
+}
+
+export function releaseButton(button: HTMLButtonElement) {
+ button.style.border = 'none';
+}
diff --git a/src/components/network-area-diagram-viewer/layout-parameters.ts b/src/components/network-area-diagram-viewer/layout-parameters.ts
index fd198ce6..0f70ff6a 100644
--- a/src/components/network-area-diagram-viewer/layout-parameters.ts
+++ b/src/components/network-area-diagram-viewer/layout-parameters.ts
@@ -3,6 +3,7 @@
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
*/
import { LayoutParametersMetadata } from './diagram-metadata';
diff --git a/src/components/network-area-diagram-viewer/network-area-diagram-viewer.test.ts b/src/components/network-area-diagram-viewer/network-area-diagram-viewer.test.ts
index 1a932e9a..a24435cb 100644
--- a/src/components/network-area-diagram-viewer/network-area-diagram-viewer.test.ts
+++ b/src/components/network-area-diagram-viewer/network-area-diagram-viewer.test.ts
@@ -27,6 +27,9 @@ describe('Test network-area-diagram-viewer', () => {
false,
false,
null,
+ null,
+ null,
+ false,
null
);
diff --git a/src/components/network-area-diagram-viewer/network-area-diagram-viewer.ts b/src/components/network-area-diagram-viewer/network-area-diagram-viewer.ts
index 00830257..35f5ffab 100644
--- a/src/components/network-area-diagram-viewer/network-area-diagram-viewer.ts
+++ b/src/components/network-area-diagram-viewer/network-area-diagram-viewer.ts
@@ -3,6 +3,7 @@
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
*/
import { Point, SVG, ViewBoxLike, Svg } from '@svgdotjs/svg.js';
@@ -17,6 +18,11 @@ import { debounce } from '@mui/material';
type DIMENSIONS = { width: number; height: number; viewbox: VIEWBOX };
type VIEWBOX = { x: number; y: number; width: number; height: number };
+export enum MouseMode {
+ MOVE,
+ SELECT,
+}
+
export type OnMoveNodeCallbackType = (
equipmentId: string,
nodeId: string,
@@ -41,6 +47,7 @@ export type OnMoveTextNodeCallbackType = (
) => void;
export type OnSelectNodeCallbackType = (equipmentId: string, nodeId: string) => void;
+
export type OnToggleNadHoverCallbackType = (
hovered: boolean,
mousePosition: Point | null,
@@ -48,8 +55,11 @@ export type OnToggleNadHoverCallbackType = (
equipmentType: string
) => void;
+export type OnSaveCallbackType = (svg: string | null, metadata: string | null) => void;
+
export class NetworkAreaDiagramViewer {
container: HTMLElement;
+ svgDiv: HTMLElement;
svgContent: string;
diagramMetadata: DiagramMetadata | null;
width: number;
@@ -73,6 +83,8 @@ export class NetworkAreaDiagramViewer {
onSelectNodeCallback: OnSelectNodeCallbackType | null;
dynamicCssRules: CSS_RULE[];
onToggleHoverCallback: OnToggleNadHoverCallbackType | null;
+ onSaveCallback: OnSaveCallbackType | null;
+ mouseMode: MouseMode;
constructor(
container: HTMLElement,
@@ -88,9 +100,13 @@ export class NetworkAreaDiagramViewer {
enableNodeInteraction: boolean,
enableLevelOfDetail: boolean,
customDynamicCssRules: CSS_RULE[] | null,
- onToggleHoverCallback: OnToggleNadHoverCallbackType | null
+ onToggleHoverCallback: OnToggleNadHoverCallbackType | null,
+ onSaveCallback: OnSaveCallbackType | null,
+ addButtons: boolean,
+ defaultMouseMode: MouseMode | null
) {
this.container = container;
+ this.svgDiv = document.createElement('div');
this.svgContent = svgContent;
this.diagramMetadata = diagramMetadata;
this.width = 0;
@@ -98,6 +114,12 @@ export class NetworkAreaDiagramViewer {
this.originalWidth = 0;
this.originalHeight = 0;
this.dynamicCssRules = customDynamicCssRules ?? DEFAULT_DYNAMIC_CSS_RULES;
+ this.mouseMode = defaultMouseMode ?? MouseMode.MOVE;
+ this.onMoveNodeCallback = onMoveNodeCallback;
+ this.onMoveTextNodeCallback = onMoveTextNodeCallback;
+ this.onSelectNodeCallback = onSelectNodeCallback;
+ this.onToggleHoverCallback = onToggleHoverCallback;
+ this.onSaveCallback = onSaveCallback;
this.init(
minWidth,
minHeight,
@@ -105,14 +127,11 @@ export class NetworkAreaDiagramViewer {
maxHeight,
enableNodeInteraction,
enableLevelOfDetail,
- diagramMetadata !== null
+ diagramMetadata !== null,
+ addButtons
);
this.svgParameters = new SvgParameters(diagramMetadata?.svgParameters);
this.layoutParameters = new LayoutParameters(diagramMetadata?.layoutParameters);
- this.onMoveNodeCallback = onMoveNodeCallback;
- this.onMoveTextNodeCallback = onMoveTextNodeCallback;
- this.onSelectNodeCallback = onSelectNodeCallback;
- this.onToggleHoverCallback = onToggleHoverCallback;
}
public setWidth(width: number): void {
@@ -185,7 +204,7 @@ export class NetworkAreaDiagramViewer {
public moveNodeToCoordinates(equipmentId: string, x: number, y: number) {
const nodeId = this.getNodeIdFromEquipmentId(equipmentId);
if (nodeId != null) {
- const elemToMove: SVGElement | null = this.container.querySelector('[id="' + nodeId + '"]');
+ const elemToMove: SVGElement | null = this.svgDiv.querySelector('[id="' + nodeId + '"]');
if (elemToMove) {
const newPosition = new Point(x, y);
this.onDragStart(elemToMove);
@@ -201,7 +220,8 @@ export class NetworkAreaDiagramViewer {
maxHeight: number,
enableNodeInteraction: boolean,
enableLevelOfDetail: boolean,
- hasMetadata: boolean
+ hasMetadata: boolean,
+ addButtons: boolean
): void {
if (!this.container || !this.svgContent) {
return;
@@ -215,6 +235,14 @@ export class NetworkAreaDiagramViewer {
// clear the previous svg in div element before replacing
this.container.innerHTML = '';
+ // add buttons bar
+ if (addButtons) {
+ this.container.appendChild(this.getButtonsBar(enableNodeInteraction && hasMetadata));
+ }
+
+ // add svg div
+ this.container.appendChild(this.svgDiv);
+
// set dimensions
this.setOriginalWidth(dimensions.width);
this.setOriginalHeight(dimensions.height);
@@ -223,7 +251,7 @@ export class NetworkAreaDiagramViewer {
// set the SVG
this.svgDraw = SVG()
- .addTo(this.container)
+ .addTo(this.svgDiv)
.size(this.width, this.height)
.viewbox(dimensions.viewbox.x, dimensions.viewbox.y, dimensions.viewbox.width, dimensions.viewbox.height);
const drawnSvg: HTMLElement = this.svgDraw.svg(this.svgContent).node.firstElementChild;
@@ -304,7 +332,7 @@ export class NetworkAreaDiagramViewer {
if (enableNodeInteraction && hasMetadata) {
// fill empty elements: unknown buses and three windings transformers
- const emptyElements: NodeListOf = this.container.querySelectorAll(
+ const emptyElements: NodeListOf = this.svgDiv.querySelectorAll(
'.nad-unknown-busnode, .nad-3wt-nodes .nad-winding'
);
emptyElements.forEach((emptyElement) => {
@@ -313,6 +341,48 @@ export class NetworkAreaDiagramViewer {
}
}
+ private getButtonsBar(showNodeInteractionButtons: boolean): HTMLDivElement {
+ const buttonsDiv = document.createElement('div');
+ buttonsDiv.style.display = 'flex';
+ buttonsDiv.style.alignItems = 'center';
+ buttonsDiv.style.position = 'absolute';
+ buttonsDiv.style.zIndex = '2';
+ if (showNodeInteractionButtons) {
+ const moveButton = DiagramUtils.getMoveButton(this.mouseMode === MouseMode.MOVE);
+ buttonsDiv.appendChild(moveButton);
+ const selectButton = DiagramUtils.getSelectButton(this.mouseMode === MouseMode.SELECT);
+ buttonsDiv.appendChild(selectButton);
+ moveButton.addEventListener('click', () => {
+ this.mouseMode = MouseMode.MOVE;
+ DiagramUtils.pressButton(moveButton);
+ DiagramUtils.releaseButton(selectButton);
+ });
+ selectButton.addEventListener('click', () => {
+ this.mouseMode = MouseMode.SELECT;
+ DiagramUtils.pressButton(selectButton);
+ DiagramUtils.releaseButton(moveButton);
+ });
+ }
+ if (this.onSaveCallback != null) {
+ const saveSvgButton = DiagramUtils.getSaveButton();
+ buttonsDiv.appendChild(saveSvgButton);
+ saveSvgButton.addEventListener('click', () => {
+ if (this.onSaveCallback != null) {
+ this.onSaveCallback(this.getSvg(), this.getJsonMetadata());
+ }
+ });
+ }
+ return buttonsDiv;
+ }
+
+ public getSvg(): string | null {
+ return this.svgDraw !== undefined ? this.svgDraw.svg() : null;
+ }
+
+ public getJsonMetadata(): string | null {
+ return JSON.stringify(this.diagramMetadata);
+ }
+
public getDimensionsFromSvg(): DIMENSIONS | null {
// Dimensions are set in the main svg tag attributes. We want to parse those data without loading the whole svg in the DOM.
const result = this.svgContent.match('