diff --git a/visual_console_client/playground/index.html b/visual_console_client/playground/index.html index e7067a0f4e..515c09a730 100644 --- a/visual_console_client/playground/index.html +++ b/visual_console_client/playground/index.html @@ -64,9 +64,9 @@ type: 20, // Color cloud = 20 label: null, labelText: "CLOUD", - isLinkEnabled: false, + isLinkEnabled: true, isOnTop: false, - parentId: null, + parentId: 10, aclGroupId: null, // Position props. x: 300, @@ -260,9 +260,9 @@ id: 11, type: 3, // Percentile = 3 label: null, - isLinkEnabled: false, + isLinkEnabled: true, isOnTop: false, - parentId: null, + parentId: 1, aclGroupId: null, // Position props. x: 830, diff --git a/visual_console_client/src/VisualConsole.ts b/visual_console_client/src/VisualConsole.ts index 806dbb1224..0a3b2c2ec3 100644 --- a/visual_console_client/src/VisualConsole.ts +++ b/visual_console_client/src/VisualConsole.ts @@ -125,8 +125,16 @@ export default class VisualConsole { private readonly containerRef: HTMLElement; // Properties. private _props: VisualConsoleProps; - // Visual Console Item instances. - private elements: Item[] = []; + // Visual Console Item instances by their Id. + private elementsById: { + [key: number]: Item | null; + } = {}; + // Visual Console Item Ids. + private elementIds: ItemProps["id"][] = []; + // Dictionary which store the created lines. + private relations: { + [key: string]: Line | null; + } = {}; public constructor( container: HTMLElement, @@ -139,27 +147,57 @@ export default class VisualConsole { // Force the first render. this.render(); - // TODO: Document. + // 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. items.forEach(item => { try { const itemInstance = itemInstanceFrom(item); - this.elements.push(itemInstance); + // Add the item to the list. + this.elementsById[itemInstance.props.id] = itemInstance; + this.elementIds.push(itemInstance.props.id); + // Item event handlers. itemInstance.onClick(e => console.log(`Clicked element #${e.data.id}`, e) ); + itemInstance.onRemove(e => { + // TODO: Remove the element from the list and its relations. + }); + // Add the item to the DOM. this.containerRef.append(itemInstance.elementRef); } catch (error) { console.log("Error creating a new element:", error.message); } }); - // Sort by isOnTop, id ASC - this.elements.sort(function(a, b) { - if (a.props.isOnTop && !b.props.isOnTop) return 1; - else if (!a.props.isOnTop && b.props.isOnTop) return -1; - else if (a.props.id < b.props.id) return 1; - else return -1; - }); + // Create lines. + this.buildRelations(); + } + + /** + * Public accessor of the `elements` property. + * @return Properties. + */ + public get elements(): Item[] { + // 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[]; } /** @@ -250,8 +288,76 @@ export default class VisualConsole { */ public remove(): void { this.elements.forEach(e => e.remove()); // Arrow function. - this.elements = []; + this.elementsById = {}; + this.elementIds = []; // Clean container. this.containerRef.innerHTML = ""; } + + /** + * Create line elements which connect the elements with their parents. + */ + private buildRelations(): void { + 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); + } + }); + } + + /** + * 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, + child: Item + ): Line { + const identifier = `${parent.props.id}|${child.props.id}`; + if (this.relations[identifier] != null) { + (this.relations[identifier] as Line).remove(); + } + + // Get the items center. + const startX = parent.props.x + parent.elementRef.clientWidth / 2; + const startY = parent.props.y + parent.elementRef.clientHeight / 2; + const endX = child.props.x + child.elementRef.clientWidth / 2; + const endY = child.props.y + child.elementRef.clientHeight / 2; + + 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; + } }