2019-02-18 17:23:57 +01:00
|
|
|
import { UnknownObject, Size } from "./types";
|
2019-03-08 13:32:56 +01:00
|
|
|
import {
|
|
|
|
parseBoolean,
|
|
|
|
sizePropsDecoder,
|
|
|
|
parseIntOr,
|
|
|
|
notEmptyStringOr
|
|
|
|
} from "./lib";
|
2019-04-12 14:24:34 +02:00
|
|
|
import Item, {
|
|
|
|
ItemType,
|
|
|
|
ItemProps,
|
|
|
|
ItemClickEvent,
|
|
|
|
ItemRemoveEvent
|
|
|
|
} from "./Item";
|
2019-02-18 17:23:57 +01:00
|
|
|
import StaticGraph, { staticGraphPropsDecoder } from "./items/StaticGraph";
|
|
|
|
import Icon, { iconPropsDecoder } from "./items/Icon";
|
|
|
|
import ColorCloud, { colorCloudPropsDecoder } from "./items/ColorCloud";
|
|
|
|
import Group, { groupPropsDecoder } from "./items/Group";
|
2019-02-26 17:05:30 +01:00
|
|
|
import Clock, { clockPropsDecoder } from "./items/Clock";
|
2019-03-08 13:32:56 +01:00
|
|
|
import Box, { boxPropsDecoder } from "./items/Box";
|
2019-03-19 10:09:45 +01:00
|
|
|
import Line, { linePropsDecoder } from "./items/Line";
|
2019-03-27 13:37:00 +01:00
|
|
|
import Label, { labelPropsDecoder } from "./items/Label";
|
|
|
|
import SimpleValue, { simpleValuePropsDecoder } from "./items/SimpleValue";
|
2019-04-01 11:50:30 +02:00
|
|
|
import EventsHistory, {
|
|
|
|
eventsHistoryPropsDecoder
|
|
|
|
} from "./items/EventsHistory";
|
2019-04-02 13:29:16 +02:00
|
|
|
import Percentile, { percentilePropsDecoder } from "./items/Percentile";
|
2019-04-09 18:21:49 +02:00
|
|
|
import TypedEvent, { Disposable, Listener } from "./TypedEvent";
|
2019-04-10 18:33:05 +02:00
|
|
|
import DonutGraph, { donutGraphPropsDecoder } from "./items/DonutGraph";
|
|
|
|
import BarsGraph, { barsGraphPropsDecoder } from "./items/BarsGraph";
|
|
|
|
import ModuleGraph, { moduleGraphPropsDecoder } from "./items/ModuleGraph";
|
2019-04-15 18:14:18 +02:00
|
|
|
import Service, { servicePropsDecoder } from "./items/Service";
|
2019-02-11 13:43:04 +01:00
|
|
|
|
2019-04-11 10:04:35 +02:00
|
|
|
// TODO: Document.
|
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
|
|
function itemInstanceFrom(data: UnknownObject) {
|
|
|
|
const type = parseIntOr(data.type, null);
|
|
|
|
if (type == null) throw new TypeError("missing item type.");
|
|
|
|
|
|
|
|
switch (type as ItemType) {
|
|
|
|
case ItemType.STATIC_GRAPH:
|
|
|
|
return new StaticGraph(staticGraphPropsDecoder(data));
|
|
|
|
case ItemType.MODULE_GRAPH:
|
|
|
|
return new ModuleGraph(moduleGraphPropsDecoder(data));
|
|
|
|
case ItemType.SIMPLE_VALUE:
|
|
|
|
case ItemType.SIMPLE_VALUE_MAX:
|
|
|
|
case ItemType.SIMPLE_VALUE_MIN:
|
|
|
|
case ItemType.SIMPLE_VALUE_AVG:
|
|
|
|
return new SimpleValue(simpleValuePropsDecoder(data));
|
|
|
|
case ItemType.PERCENTILE_BAR:
|
|
|
|
case ItemType.PERCENTILE_BUBBLE:
|
|
|
|
case ItemType.CIRCULAR_PROGRESS_BAR:
|
|
|
|
case ItemType.CIRCULAR_INTERIOR_PROGRESS_BAR:
|
|
|
|
return new Percentile(percentilePropsDecoder(data));
|
|
|
|
case ItemType.LABEL:
|
|
|
|
return new Label(labelPropsDecoder(data));
|
|
|
|
case ItemType.ICON:
|
|
|
|
return new Icon(iconPropsDecoder(data));
|
|
|
|
case ItemType.SERVICE:
|
2019-04-15 18:14:18 +02:00
|
|
|
return new Service(servicePropsDecoder(data));
|
2019-04-11 10:04:35 +02:00
|
|
|
case ItemType.GROUP_ITEM:
|
|
|
|
return new Group(groupPropsDecoder(data));
|
|
|
|
case ItemType.BOX_ITEM:
|
|
|
|
return new Box(boxPropsDecoder(data));
|
|
|
|
case ItemType.LINE_ITEM:
|
|
|
|
return new Line(linePropsDecoder(data));
|
|
|
|
case ItemType.AUTO_SLA_GRAPH:
|
|
|
|
return new EventsHistory(eventsHistoryPropsDecoder(data));
|
|
|
|
case ItemType.DONUT_GRAPH:
|
|
|
|
return new DonutGraph(donutGraphPropsDecoder(data));
|
|
|
|
case ItemType.BARS_GRAPH:
|
|
|
|
return new BarsGraph(barsGraphPropsDecoder(data));
|
|
|
|
case ItemType.CLOCK:
|
|
|
|
return new Clock(clockPropsDecoder(data));
|
|
|
|
case ItemType.COLOR_CLOUD:
|
|
|
|
return new ColorCloud(colorCloudPropsDecoder(data));
|
|
|
|
default:
|
|
|
|
throw new TypeError("item not found");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Document.
|
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
|
|
function decodeProps(data: UnknownObject) {
|
|
|
|
const type = parseIntOr(data.type, null);
|
|
|
|
if (type == null) throw new TypeError("missing item type.");
|
|
|
|
|
|
|
|
switch (type as ItemType) {
|
|
|
|
case ItemType.STATIC_GRAPH:
|
|
|
|
return staticGraphPropsDecoder(data);
|
|
|
|
case ItemType.MODULE_GRAPH:
|
|
|
|
throw new TypeError("decoder not found");
|
|
|
|
case ItemType.SIMPLE_VALUE:
|
|
|
|
case ItemType.SIMPLE_VALUE_MAX:
|
|
|
|
case ItemType.SIMPLE_VALUE_MIN:
|
|
|
|
case ItemType.SIMPLE_VALUE_AVG:
|
|
|
|
return simpleValuePropsDecoder(data);
|
|
|
|
case ItemType.PERCENTILE_BAR:
|
|
|
|
case ItemType.PERCENTILE_BUBBLE:
|
|
|
|
case ItemType.CIRCULAR_PROGRESS_BAR:
|
|
|
|
case ItemType.CIRCULAR_INTERIOR_PROGRESS_BAR:
|
|
|
|
return percentilePropsDecoder(data);
|
|
|
|
case ItemType.LABEL:
|
|
|
|
return labelPropsDecoder(data);
|
|
|
|
case ItemType.ICON:
|
|
|
|
return iconPropsDecoder(data);
|
|
|
|
case ItemType.SERVICE:
|
|
|
|
throw new TypeError("decoder not found");
|
|
|
|
case ItemType.GROUP_ITEM:
|
|
|
|
return groupPropsDecoder(data);
|
|
|
|
case ItemType.BOX_ITEM:
|
|
|
|
return boxPropsDecoder(data);
|
|
|
|
case ItemType.LINE_ITEM:
|
|
|
|
return linePropsDecoder(data);
|
|
|
|
case ItemType.AUTO_SLA_GRAPH:
|
|
|
|
return eventsHistoryPropsDecoder(data);
|
|
|
|
case ItemType.DONUT_GRAPH:
|
|
|
|
case ItemType.BARS_GRAPH:
|
|
|
|
throw new TypeError("decoder not found");
|
|
|
|
case ItemType.CLOCK:
|
|
|
|
return clockPropsDecoder(data);
|
|
|
|
case ItemType.COLOR_CLOUD:
|
|
|
|
return colorCloudPropsDecoder(data);
|
|
|
|
default:
|
|
|
|
throw new TypeError("decoder not found");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-18 17:23:57 +01:00
|
|
|
// Base properties.
|
|
|
|
export interface VisualConsoleProps extends Size {
|
|
|
|
readonly id: number;
|
|
|
|
name: string;
|
|
|
|
groupId: number;
|
|
|
|
backgroundURL: string | null; // URL?
|
|
|
|
backgroundColor: string | null;
|
|
|
|
isFavorite: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build a valid typed object from a raw object.
|
|
|
|
* This will allow us to ensure the type safety.
|
|
|
|
*
|
|
|
|
* @param data Raw object.
|
|
|
|
* @return An object representing the Visual Console props.
|
|
|
|
* @throws Will throw a TypeError if some property
|
|
|
|
* is missing from the raw object or have an invalid type.
|
|
|
|
*/
|
|
|
|
export function visualConsolePropsDecoder(
|
|
|
|
data: UnknownObject
|
|
|
|
): VisualConsoleProps | never {
|
|
|
|
// Object destructuring: http://es6-features.org/#ObjectMatchingShorthandNotation
|
|
|
|
const {
|
|
|
|
id,
|
|
|
|
name,
|
|
|
|
groupId,
|
|
|
|
backgroundURL,
|
|
|
|
backgroundColor,
|
|
|
|
isFavorite
|
|
|
|
} = data;
|
|
|
|
|
|
|
|
if (id == null || isNaN(parseInt(id))) {
|
|
|
|
throw new TypeError("invalid Id.");
|
|
|
|
}
|
|
|
|
if (typeof name !== "string" || name.length === 0) {
|
|
|
|
throw new TypeError("invalid name.");
|
|
|
|
}
|
|
|
|
if (groupId == null || isNaN(parseInt(groupId))) {
|
|
|
|
throw new TypeError("invalid group Id.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
id: parseInt(id),
|
|
|
|
name,
|
|
|
|
groupId: parseInt(groupId),
|
2019-03-08 13:32:56 +01:00
|
|
|
backgroundURL: notEmptyStringOr(backgroundURL, null),
|
|
|
|
backgroundColor: notEmptyStringOr(backgroundColor, null),
|
2019-02-18 17:23:57 +01:00
|
|
|
isFavorite: parseBoolean(isFavorite),
|
|
|
|
...sizePropsDecoder(data)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class VisualConsole {
|
|
|
|
// Reference to the DOM element which will contain the items.
|
|
|
|
private readonly containerRef: HTMLElement;
|
|
|
|
// Properties.
|
|
|
|
private _props: VisualConsoleProps;
|
2019-04-08 16:38:02 +02:00
|
|
|
// Visual Console Item instances by their Id.
|
|
|
|
private elementsById: {
|
2019-04-11 10:04:35 +02:00
|
|
|
[key: number]: Item<ItemProps>;
|
2019-04-08 16:38:02 +02:00
|
|
|
} = {};
|
|
|
|
// Visual Console Item Ids.
|
|
|
|
private elementIds: ItemProps["id"][] = [];
|
|
|
|
// Dictionary which store the created lines.
|
|
|
|
private relations: {
|
2019-04-11 10:04:35 +02:00
|
|
|
[key: string]: Line;
|
2019-04-08 16:38:02 +02:00
|
|
|
} = {};
|
2019-04-09 18:21:49 +02:00
|
|
|
// Event manager for click events.
|
|
|
|
private readonly clickEventManager = new TypedEvent<
|
|
|
|
ItemClickEvent<ItemProps>
|
|
|
|
>();
|
|
|
|
// List of references to clean the event listeners.
|
|
|
|
private readonly disposables: Disposable[] = [];
|
2019-02-18 17:23:57 +01:00
|
|
|
|
2019-04-12 14:24:34 +02:00
|
|
|
/**
|
|
|
|
* React to a click on an element.
|
|
|
|
* @param e Event object.
|
|
|
|
*/
|
|
|
|
private handleElementClick: (e: ItemClickEvent<ItemProps>) => void = e => {
|
|
|
|
this.clickEventManager.emit(e);
|
|
|
|
// console.log(`Clicked element #${e.data.id}`, e);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear some element references.
|
|
|
|
* @param e Event object.
|
|
|
|
*/
|
|
|
|
private handleElementRemove: (e: ItemRemoveEvent<ItemProps>) => void = e => {
|
|
|
|
// Remove the element from the list and its relations.
|
|
|
|
this.elementIds = this.elementIds.filter(id => id !== e.data.id);
|
|
|
|
delete this.elementsById[e.data.id];
|
|
|
|
this.clearRelations(e.data.id);
|
|
|
|
};
|
|
|
|
|
2019-02-26 17:05:30 +01:00
|
|
|
public constructor(
|
2019-02-18 17:23:57 +01:00
|
|
|
container: HTMLElement,
|
2019-03-05 16:18:52 +01:00
|
|
|
props: UnknownObject,
|
2019-02-18 17:23:57 +01:00
|
|
|
items: UnknownObject[]
|
|
|
|
) {
|
|
|
|
this.containerRef = container;
|
2019-03-05 16:18:52 +01:00
|
|
|
this._props = visualConsolePropsDecoder(props);
|
2019-02-18 17:23:57 +01:00
|
|
|
|
|
|
|
// Force the first render.
|
|
|
|
this.render();
|
|
|
|
|
2019-04-08 16:38:02 +02:00
|
|
|
// Sort by isOnTop, id ASC
|
|
|
|
items = items.sort(function(a, b) {
|
|
|
|
if (
|
|
|
|
a.isOnTop == null ||
|
|
|
|
b.isOnTop == null ||
|
|
|
|
a.id == null ||
|
|
|
|
b.id == null
|
|
|
|
) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a.isOnTop && !b.isOnTop) return 1;
|
|
|
|
else if (!a.isOnTop && b.isOnTop) return -1;
|
|
|
|
else if (a.id < b.id) return 1;
|
|
|
|
else return -1;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Initialize the items.
|
2019-02-18 17:23:57 +01:00
|
|
|
items.forEach(item => {
|
|
|
|
try {
|
|
|
|
const itemInstance = itemInstanceFrom(item);
|
2019-04-08 16:38:02 +02:00
|
|
|
// Add the item to the list.
|
|
|
|
this.elementsById[itemInstance.props.id] = itemInstance;
|
|
|
|
this.elementIds.push(itemInstance.props.id);
|
|
|
|
// Item event handlers.
|
2019-04-12 14:24:34 +02:00
|
|
|
itemInstance.onClick(this.handleElementClick);
|
|
|
|
itemInstance.onRemove(this.handleElementRemove);
|
2019-04-08 16:38:02 +02:00
|
|
|
// Add the item to the DOM.
|
2019-02-18 17:23:57 +01:00
|
|
|
this.containerRef.append(itemInstance.elementRef);
|
|
|
|
} catch (error) {
|
|
|
|
console.log("Error creating a new element:", error.message);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-04-08 16:38:02 +02:00
|
|
|
// Create lines.
|
|
|
|
this.buildRelations();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Public accessor of the `elements` property.
|
|
|
|
* @return Properties.
|
|
|
|
*/
|
|
|
|
public get elements(): Item<ItemProps>[] {
|
|
|
|
// Ensure the type cause Typescript doesn't know the filter removes null items.
|
|
|
|
return this.elementIds
|
|
|
|
.map(id => this.elementsById[id])
|
|
|
|
.filter(_ => _ != null) as Item<ItemProps>[];
|
2019-02-18 17:23:57 +01:00
|
|
|
}
|
|
|
|
|
2019-04-11 10:04:35 +02:00
|
|
|
/**
|
|
|
|
* Public setter of the `elements` property.
|
|
|
|
* @param items.
|
|
|
|
*/
|
|
|
|
public updateElements(items: UnknownObject[]): void {
|
|
|
|
const itemIds = items.map(item => item.id || null).filter(id => id != null);
|
|
|
|
itemIds as number[]; // Tell the type system to rely on us.
|
|
|
|
// Get the elements we should delete.
|
|
|
|
const deletedIds: number[] = this.elementIds.filter(
|
|
|
|
id => itemIds.indexOf(id) < 0
|
|
|
|
);
|
|
|
|
// Delete the elements.
|
|
|
|
deletedIds.forEach(id => {
|
|
|
|
if (this.elementsById[id] != null) {
|
|
|
|
this.elementsById[id].remove();
|
|
|
|
delete this.elementsById[id];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Replace the element ids.
|
|
|
|
this.elementIds = itemIds;
|
|
|
|
|
|
|
|
// Initialize the items.
|
|
|
|
items.forEach(item => {
|
|
|
|
if (item.id) {
|
|
|
|
if (this.elementsById[item.id] == null) {
|
|
|
|
// New item.
|
|
|
|
try {
|
|
|
|
const itemInstance = itemInstanceFrom(item);
|
|
|
|
// Add the item to the list.
|
|
|
|
this.elementsById[itemInstance.props.id] = itemInstance;
|
|
|
|
// Item event handlers.
|
2019-04-12 14:24:34 +02:00
|
|
|
itemInstance.onClick(this.handleElementClick);
|
|
|
|
itemInstance.onRemove(this.handleElementRemove);
|
2019-04-11 10:04:35 +02:00
|
|
|
// Add the item to the DOM.
|
|
|
|
this.containerRef.append(itemInstance.elementRef);
|
|
|
|
} catch (error) {
|
|
|
|
console.log("Error creating a new element:", error.message);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Update item.
|
|
|
|
try {
|
|
|
|
this.elementsById[item.id].props = decodeProps(item);
|
|
|
|
} catch (error) {
|
|
|
|
console.log("Error updating an element:", error.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2019-04-12 14:24:34 +02:00
|
|
|
|
|
|
|
// Re-build relations.
|
|
|
|
this.buildRelations();
|
2019-04-11 10:04:35 +02:00
|
|
|
}
|
|
|
|
|
2019-02-18 17:23:57 +01:00
|
|
|
/**
|
|
|
|
* Public accessor of the `props` property.
|
|
|
|
* @return Properties.
|
|
|
|
*/
|
2019-02-26 17:05:30 +01:00
|
|
|
public get props(): VisualConsoleProps {
|
2019-04-11 10:04:35 +02:00
|
|
|
return { ...this._props }; // Return a copy.
|
2019-02-18 17:23:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Public setter of the `props` property.
|
|
|
|
* If the new props are different enough than the
|
|
|
|
* stored props, a render would be fired.
|
|
|
|
* @param newProps
|
|
|
|
*/
|
2019-02-26 17:05:30 +01:00
|
|
|
public set props(newProps: VisualConsoleProps) {
|
2019-02-18 17:23:57 +01:00
|
|
|
const prevProps = this.props;
|
|
|
|
// Update the internal props.
|
|
|
|
this._props = newProps;
|
|
|
|
|
|
|
|
// From this point, things which rely on this.props can access to the changes.
|
|
|
|
|
|
|
|
// Re-render.
|
|
|
|
this.render(prevProps);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recreate or update the HTMLElement which represents the Visual Console into the DOM.
|
|
|
|
* @param prevProps If exists it will be used to only DOM updates instead of a full replace.
|
|
|
|
*/
|
2019-02-26 17:05:30 +01:00
|
|
|
public render(prevProps: VisualConsoleProps | null = null): void {
|
2019-02-18 17:23:57 +01:00
|
|
|
if (prevProps) {
|
|
|
|
if (prevProps.backgroundURL !== this.props.backgroundURL) {
|
2019-04-12 09:14:44 +02:00
|
|
|
this.containerRef.style.backgroundImage = `url(${
|
|
|
|
this.props.backgroundURL
|
|
|
|
})`;
|
2019-02-18 17:23:57 +01:00
|
|
|
}
|
|
|
|
if (prevProps.backgroundColor !== this.props.backgroundColor) {
|
|
|
|
this.containerRef.style.backgroundColor = this.props.backgroundColor;
|
|
|
|
}
|
|
|
|
if (this.sizeChanged(prevProps, this.props)) {
|
|
|
|
this.resizeElement(this.props.width, this.props.height);
|
|
|
|
}
|
|
|
|
} else {
|
2019-04-12 09:14:44 +02:00
|
|
|
this.containerRef.style.backgroundImage = `url(${
|
|
|
|
this.props.backgroundURL
|
|
|
|
})`;
|
2019-02-18 17:23:57 +01:00
|
|
|
this.containerRef.style.backgroundColor = this.props.backgroundColor;
|
|
|
|
this.resizeElement(this.props.width, this.props.height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compare the previous and the new size and return
|
|
|
|
* a boolean value in case the size changed.
|
|
|
|
* @param prevSize
|
|
|
|
* @param newSize
|
|
|
|
* @return Whether the size changed or not.
|
|
|
|
*/
|
2019-02-26 17:05:30 +01:00
|
|
|
public sizeChanged(prevSize: Size, newSize: Size): boolean {
|
2019-02-18 17:23:57 +01:00
|
|
|
return (
|
|
|
|
prevSize.width !== newSize.width || prevSize.height !== newSize.height
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resize the DOM container.
|
|
|
|
* @param width
|
|
|
|
* @param height
|
|
|
|
*/
|
2019-02-26 17:05:30 +01:00
|
|
|
public resizeElement(width: number, height: number): void {
|
2019-02-18 17:23:57 +01:00
|
|
|
this.containerRef.style.width = `${width}px`;
|
|
|
|
this.containerRef.style.height = `${height}px`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the size into the properties and resize the DOM container.
|
|
|
|
* @param width
|
|
|
|
* @param height
|
|
|
|
*/
|
2019-02-26 17:05:30 +01:00
|
|
|
public resize(width: number, height: number): void {
|
2019-02-18 17:23:57 +01:00
|
|
|
this.props = {
|
|
|
|
...this.props, // Object spread: http://es6-features.org/#SpreadOperator
|
|
|
|
width,
|
|
|
|
height
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* To remove the event listeners and the elements from the DOM.
|
|
|
|
*/
|
2019-02-26 17:05:30 +01:00
|
|
|
public remove(): void {
|
2019-04-09 18:21:49 +02:00
|
|
|
this.disposables.forEach(d => d.dispose()); // Arrow function.
|
2019-02-18 17:23:57 +01:00
|
|
|
this.elements.forEach(e => e.remove()); // Arrow function.
|
2019-04-08 16:38:02 +02:00
|
|
|
this.elementsById = {};
|
|
|
|
this.elementIds = [];
|
2019-04-12 14:24:34 +02:00
|
|
|
// Clear relations.
|
|
|
|
this.clearRelations();
|
2019-04-01 11:50:30 +02:00
|
|
|
// Clean container.
|
|
|
|
this.containerRef.innerHTML = "";
|
2019-02-18 17:23:57 +01:00
|
|
|
}
|
2019-04-08 16:38:02 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create line elements which connect the elements with their parents.
|
|
|
|
*/
|
|
|
|
private buildRelations(): void {
|
2019-04-12 14:24:34 +02:00
|
|
|
// Clear relations.
|
|
|
|
this.clearRelations();
|
|
|
|
// Add relations.
|
2019-04-08 16:38:02 +02:00
|
|
|
this.elements.forEach(item => {
|
|
|
|
if (item.props.parentId !== null) {
|
|
|
|
const parent = this.elementsById[item.props.parentId];
|
|
|
|
const child = this.elementsById[item.props.id];
|
|
|
|
if (parent && child) this.addRelationLine(parent, child);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-04-12 14:24:34 +02:00
|
|
|
/**
|
|
|
|
* @param itemId Optional identifier of a parent or child item.
|
|
|
|
* Remove the line elements which connect the elements with their parents.
|
|
|
|
*/
|
|
|
|
private clearRelations(itemId?: number): void {
|
|
|
|
if (itemId != null) {
|
|
|
|
for (let key in this.relations) {
|
|
|
|
const ids = key.split("|");
|
|
|
|
const parentId = Number.parseInt(ids[0]);
|
|
|
|
const childId = Number.parseInt(ids[1]);
|
|
|
|
|
|
|
|
if (itemId === parentId || itemId === childId) {
|
|
|
|
this.relations[key].remove();
|
|
|
|
delete this.relations[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (let key in this.relations) {
|
|
|
|
this.relations[key].remove();
|
|
|
|
delete this.relations[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-08 16:38:02 +02:00
|
|
|
/**
|
|
|
|
* Retrieve the line element which represent the relation between items.
|
|
|
|
* @param parentId Identifier of the parent item.
|
|
|
|
* @param childId Itentifier of the child item.
|
|
|
|
* @return The line element or nothing.
|
|
|
|
*/
|
|
|
|
private getRelationLine(parentId: number, childId: number): Line | null {
|
|
|
|
const identifier = `${parentId}|${childId}`;
|
|
|
|
return this.relations[identifier] || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a new line item to represent a relation between the items.
|
|
|
|
* @param parent Parent item.
|
|
|
|
* @param child Child item.
|
|
|
|
* @return Whether the line was added or not.
|
|
|
|
*/
|
|
|
|
private addRelationLine(
|
|
|
|
parent: Item<ItemProps>,
|
|
|
|
child: Item<ItemProps>
|
|
|
|
): Line {
|
|
|
|
const identifier = `${parent.props.id}|${child.props.id}`;
|
|
|
|
if (this.relations[identifier] != null) {
|
2019-04-11 10:04:35 +02:00
|
|
|
this.relations[identifier].remove();
|
2019-04-08 16:38:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the items center.
|
|
|
|
const startX = parent.props.x + parent.elementRef.clientWidth / 2;
|
2019-04-16 09:41:17 +02:00
|
|
|
const startY =
|
|
|
|
parent.props.y +
|
|
|
|
(parent.elementRef.clientHeight + parent.labelElementRef.clientHeight) /
|
|
|
|
2;
|
2019-04-08 16:38:02 +02:00
|
|
|
const endX = child.props.x + child.elementRef.clientWidth / 2;
|
2019-04-16 09:41:17 +02:00
|
|
|
const endY =
|
|
|
|
child.props.y +
|
|
|
|
(child.elementRef.clientHeight + child.labelElementRef.clientHeight) / 2;
|
2019-04-08 16:38:02 +02:00
|
|
|
|
|
|
|
const line = new Line(
|
|
|
|
linePropsDecoder({
|
|
|
|
id: 0,
|
|
|
|
type: ItemType.LINE_ITEM,
|
|
|
|
startX,
|
|
|
|
startY,
|
|
|
|
endX,
|
|
|
|
endY,
|
|
|
|
width: 0,
|
|
|
|
height: 0
|
|
|
|
})
|
|
|
|
);
|
|
|
|
// Save a reference to the line item.
|
|
|
|
this.relations[identifier] = line;
|
|
|
|
|
|
|
|
// Add the line to the DOM.
|
|
|
|
line.elementRef.style.zIndex = "0";
|
|
|
|
this.containerRef.append(line.elementRef);
|
|
|
|
|
|
|
|
return line;
|
|
|
|
}
|
2019-04-09 18:21:49 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an event handler to the click of the linked visual console elements.
|
|
|
|
* @param listener Function which is going to be executed when a linked console is clicked.
|
|
|
|
*/
|
|
|
|
public onClick(listener: Listener<ItemClickEvent<ItemProps>>): Disposable {
|
|
|
|
/*
|
|
|
|
* The '.on' function returns a function which will clean the event
|
|
|
|
* listener when executed. We store all the 'dispose' functions to
|
|
|
|
* call them when the item should be cleared.
|
|
|
|
*/
|
|
|
|
const disposable = this.clickEventManager.on(listener);
|
|
|
|
this.disposables.push(disposable);
|
|
|
|
|
|
|
|
return disposable;
|
|
|
|
}
|
2019-02-18 17:23:57 +01:00
|
|
|
}
|