diff --git a/visual_console/src/items/ColorCloud.spec.ts b/visual_console/src/items/ColorCloud.spec.ts new file mode 100644 index 0000000000..5582e40f78 --- /dev/null +++ b/visual_console/src/items/ColorCloud.spec.ts @@ -0,0 +1,52 @@ +import ColorCloud, { colorCloudPropsDecoder } from "./ColorCloud"; + +const genericRawProps = { + id: 1, + type: 20, // COlor cloud item = 20 + label: null, + isLinkEnabled: false, + isOnTop: false, + parentId: null, + aclGroupId: null +}; + +const positionRawProps = { + x: 100, + y: 50 +}; + +const sizeRawProps = { + width: 100, + height: 100 +}; + +const colorCloudProps = { + color: "rgb(100, 50, 245)" +}; + +const linkedModuleProps = { + // Agent props. + agentId: null, + agentName: null, + // Module props. + moduleId: null, + moduleName: null +}; + +describe("Color cloud item", () => { + const groupInstance = new ColorCloud( + colorCloudPropsDecoder({ + ...genericRawProps, + ...positionRawProps, + ...sizeRawProps, + ...linkedModuleProps, + ...colorCloudProps + }) + ); + + it("should have the color-cloud class", () => { + expect( + groupInstance.elementRef.getElementsByClassName("color-cloud").length + ).toBeGreaterThan(0); + }); +}); diff --git a/visual_console/src/items/ColorCloud.ts b/visual_console/src/items/ColorCloud.ts new file mode 100644 index 0000000000..7f371b4c3b --- /dev/null +++ b/visual_console/src/items/ColorCloud.ts @@ -0,0 +1,126 @@ +import { + WithModuleProps, + LinkedVisualConsoleProps, + UnknownObject +} from "../types"; + +import { modulePropsDecoder, linkedVCPropsDecoder } from "../lib"; + +import VisualConsoleItem, { + VisualConsoleItemProps, + itemBasePropsDecoder, + VisualConsoleItemType +} from "../VisualConsoleItem"; + +export type ColorCloudProps = { + type: VisualConsoleItemType.COLOR_CLOUD; + color: string; + // TODO: Add the rest of the color cloud values? +} & VisualConsoleItemProps & + WithModuleProps & + LinkedVisualConsoleProps; + +/** + * 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 static graph props. + * @throws Will throw a TypeError if some property + * is missing from the raw object or have an invalid type. + */ +export function colorCloudPropsDecoder( + data: UnknownObject +): ColorCloudProps | never { + // TODO: Validate the color. + if (typeof data.color !== "string" || data.color.length === 0) { + throw new TypeError("invalid color."); + } + + return { + ...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects. + type: VisualConsoleItemType.COLOR_CLOUD, + color: data.color, + ...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects. + ...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects. + }; +} + +const svgNS = "http://www.w3.org/2000/svg"; + +export default class ColorCloud extends VisualConsoleItem { + createDomElement(): HTMLElement { + const container: HTMLDivElement = document.createElement("div"); + container.className = "color-cloud"; + + // Add the SVG. + container.append(this.createSvgElement()); + + return container; + } + + createSvgElement(): SVGSVGElement { + const gradientId = `grad_${this.props.id}`; + // SVG container. + const svg = document.createElementNS(svgNS, "svg"); + // Resize SVG. Use only the width, cause this element only needs a diameter. + svg.setAttribute("width", `${this.props.width}px`); + svg.setAttribute("height", `${this.props.width}px`); + + // Defs. + const defs = document.createElementNS(svgNS, "defs"); + // Radial gradient. + const radialGradient = document.createElementNS(svgNS, "radialGradient"); + radialGradient.setAttribute("id", gradientId); + radialGradient.setAttribute("cx", "50%"); + radialGradient.setAttribute("cy", "50%"); + radialGradient.setAttribute("r", "50%"); + radialGradient.setAttribute("fx", "50%"); + radialGradient.setAttribute("fy", "50%"); + // Stops. + const stop0 = document.createElementNS(svgNS, "stop"); + stop0.setAttribute("offset", "0%"); + stop0.setAttribute( + "style", + `stop-color:${this.props.color};stop-opacity:0.9` + ); + const stop100 = document.createElementNS(svgNS, "stop"); + stop100.setAttribute("offset", "100%"); + stop100.setAttribute( + "style", + `stop-color:${this.props.color};stop-opacity:0` + ); + // Circle. + const circle = document.createElementNS(svgNS, "circle"); + circle.setAttribute("fill", `url(#${gradientId})`); + circle.setAttribute("cx", "50%"); + circle.setAttribute("cy", "50%"); + circle.setAttribute("r", "50%"); + + // Append elements. + radialGradient.append(stop0, stop100); + defs.append(radialGradient); + svg.append(defs, circle); + + return svg; + } + + /** + * @override VisualConsoleItem.resize + * To resize the item. + * @param width Width. + * @param height Height. + */ + resize(width: number, height: number): void { + // Resize parent. Use only the width, cause this element only needs a diameter. + super.resize(width, width); + + // Get SVG element. + const svgElement = this.elementRef.getElementsByTagName("svg").item(0); + if (svgElement === null) return; + + // Resize SVG. Use only the width, cause this element only needs a diameter. + svgElement.setAttribute("width", `${this.props.width}px`); + svgElement.setAttribute("height", `${this.props.width}px`); + } +}