From 4e6c0f61bfa84d8b3e6bbc81f84e01bf48e6b529 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Thu, 27 Feb 2020 15:49:18 +0000 Subject: [PATCH] Remove unused topology used to be imported by the now defunct topologicl inventory ui --- src/topology/data.js | 72 --- src/topology/index.js | 1 - src/topology/stories/topology.stories.jsx | 8 - src/topology/stories/topologyWrapper.jsx | 378 ------------- src/topology/styles.scss | 23 - .../topologyCanvas.test.jsx.snap | 176 ------ src/topology/test/topologyCanvas.test.jsx | 90 ---- src/topology/test/utils.test.js | 39 -- src/topology/topologyCanvas.jsx | 509 ------------------ src/topology/utils.js | 53 -- 10 files changed, 1349 deletions(-) delete mode 100644 src/topology/data.js delete mode 100644 src/topology/index.js delete mode 100644 src/topology/stories/topology.stories.jsx delete mode 100644 src/topology/stories/topologyWrapper.jsx delete mode 100644 src/topology/styles.scss delete mode 100644 src/topology/test/__snapshots__/topologyCanvas.test.jsx.snap delete mode 100644 src/topology/test/topologyCanvas.test.jsx delete mode 100644 src/topology/test/utils.test.js delete mode 100644 src/topology/topologyCanvas.jsx delete mode 100644 src/topology/utils.js diff --git a/src/topology/data.js b/src/topology/data.js deleted file mode 100644 index 4d99fce..0000000 --- a/src/topology/data.js +++ /dev/null @@ -1,72 +0,0 @@ -export const nodes = [ - { - id: 3, - title: 'Yang', - size: 24, - fonticon: 'fa fa-cloud', - depth: 3, - status: 'valid', - }, - { - id: 4, - title: 'Gray', - size: 24, - fonticon: 'fa fa-cloud', - depth: 3, - status: 'valid', - }, - { - id: 5, - title: 'Maddox', - size: 24, - fileicon: 'https://www.svgrepo.com/show/5386/speedometer.svg', - depth: 3, - status: 'warning', - }, - { - id: 0, - title: 'Levy', - size: 24, - fonticon: 'fa fa-cog', - depth: 1, - status: 'valid', - }, - { - id: 1, - title: 'Celina', - size: 24, - fonticon: 'fa fa-cloud', - depth: 2, - status: 'warning', - }, - { - id: 2, - title: 'Nancy', - size: 24, - fonticon: 'fa fa-cloud', - depth: 2, - status: 'critical', - }, -]; -export const edges = [ - { - source: 1, - target: 4, - }, - { - source: 2, - target: 5, - }, - { - source: 0, - target: 1, - }, - { - source: 0, - target: 2, - }, - { - source: 1, - target: 3, - }, -]; diff --git a/src/topology/index.js b/src/topology/index.js deleted file mode 100644 index c7d60ea..0000000 --- a/src/topology/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as TopologyCanvas } from './topologyCanvas'; diff --git a/src/topology/stories/topology.stories.jsx b/src/topology/stories/topology.stories.jsx deleted file mode 100644 index 379fb58..0000000 --- a/src/topology/stories/topology.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { withInfo } from '@storybook/addon-info'; -import TopologyWrapper from './topologyWrapper'; - -storiesOf('Topology', module).add('Controlled topology component', withInfo()(() => ( - -))); diff --git a/src/topology/stories/topologyWrapper.jsx b/src/topology/stories/topologyWrapper.jsx deleted file mode 100644 index 19c638a..0000000 --- a/src/topology/stories/topologyWrapper.jsx +++ /dev/null @@ -1,378 +0,0 @@ -import * as React from 'react'; -import { Row, Col, Grid, Button, Icon, Label, FormGroup, FormControl, Form } from 'patternfly-react'; // eslint-disable-line -import TopologyCanvas from '../topologyCanvas'; -import { nodes as n, edges as e } from '../data'; - -class TopologyWrapper extends React.Component { - constructor(props) { - super(props); - this.state = { - nodes: n, - edges: e, - filteredValue: '', - addedParents: false, - sidePanelOpen: false, - prevFilters: [], - addedChildren: {}, - showAll: false, - }; - } - - handleInvokeFilter = value => - this.setState(prevState => - ({ - filteredValue: value, - nodes: this.filterNodes(prevState.nodes, value), - })); - - filterNodes = (nodes, value) => nodes.map((node) => { - if (value.trim().length === 0) { - return { ...node, highlight: false }; - } - return node.title.toUpperCase().includes(value.toUpperCase().trim()) ? { ...node, highlight: true } : { ...node, highlight: false }; - }); - - invokeAddParent = (node) => { - const { id } = node; - if (this.state.showAll) { - this.setState(prevState => ({ - filteredValue: '', - addedParents: true, - sidePanelOpen: true, - selectedNode: node, - prevFilters: [...prevState.prevFilters, prevState.filteredValue], - })); - return; - } - if (id === 0 && !this.state.addedParents) { - this.setState(prevState => ({ - addedParents: true, - filteredValue: '', - sidePanelOpen: true, - selectedNode: node, - prevFilters: [...prevState.prevFilters, prevState.filteredValue], - nodes: [{ - id: 6, - title: 'Parent 1', - size: 24, - fonticon: 'fa fa-cog', - depth: 0, - }, { - id: 7, - title: 'Parent 2', - size: 24, - fonticon: 'fa fa-cog', - depth: 0, - }, ...prevState.nodes], - edges: [...prevState.edges, { - source: 6, - target: 0, - }, { - source: 7, - target: 0, - }], - })); - } - if (id === 7 && !this.state.addedChildren[id]) { - this.setState(prevState => ({ - addedChildren: true, - filteredValue: '', - sidePanelOpen: true, - selectedNode: node, - prevFilters: [...prevState.prevFilters, prevState.filteredValue], - nodes: [ - ...prevState.nodes, - { - id: 8, - title: 'Levy', - size: 24, - fonticon: 'fa fa-cog', - depth: 1, - status: 'valid', - }, - ], - edges: [ - ...prevState.edges, - { - source: 7, - target: 8, - }, - ], - })); - } - if (id === 8 && !this.state.addedChildren[id]) { - this.setState(prevState => ({ - addedChildren: true, - filteredValue: '', - sidePanelOpen: true, - selectedNode: node, - prevFilters: [...prevState.prevFilters, prevState.filteredValue], - nodes: [ - ...prevState.nodes, - - { - id: 9, - title: 'Celina', - size: 24, - fonticon: 'fa fa-cloud', - depth: 2, - status: 'valid', - }, - { - id: 10, - title: 'Nancy', - size: 24, - fonticon: 'fa fa-cloud', - depth: 2, - status: 'valid', - }, - { - id: 11, - title: 'Yang', - size: 24, - fonticon: 'fa fa-cloud', - depth: 3, - status: 'valid', - }, - { - id: 12, - title: 'Gray', - size: 24, - fonticon: 'fa fa-cloud', - depth: 3, - status: 'critical', - }, - { - id: 13, - title: 'Maddox', - size: 24, - fileicon: 'https://www.svgrepo.com/show/5386/speedometer.svg', - depth: 3, - status: 'valid', - }, - ], - edges: [ - ...prevState.edges, - { - source: 8, - target: 9, - }, - { - source: 8, - target: 10, - }, - { - source: 8, - target: 11, - }, - { - source: 8, - target: 12, - }, - { - source: 8, - target: 13, - }, - ], - })); - } - this.setState(prevState => ({ - filteredValue: '', - sidePanelOpen: true, - selectedNode: node, - prevFilters: [...prevState.prevFilters, prevState.filteredValue], - })); - } - - handleFocusFilterInput = () => { - if (this.state.filteredValue.trim().length === 0) { - this.setState(prevState => ({ - filteredValue: prevState.prevFilters.pop() || '', - prevFilters: [...prevState.prevFilters], - })); - } - } - - handleShowAll = () => this.setState(prevState => ({ - showAll: true, - filteredValue: '', - nodes: [ - ...prevState.nodes, - { - id: 8, - title: 'Levy', - size: 24, - fonticon: 'fa fa-cog', - depth: 1, - status: 'valid', - }, - { - id: 9, - title: 'Celina', - size: 24, - fonticon: 'fa fa-cloud', - depth: 2, - status: 'valid', - }, - { - id: 10, - title: 'Nancy', - size: 24, - fonticon: 'fa fa-cloud', - depth: 2, - status: 'valid', - }, - { - id: 11, - title: 'Yang', - size: 24, - fonticon: 'fa fa-cloud', - depth: 3, - status: 'valid', - }, - { - id: 12, - title: 'Gray', - size: 24, - fonticon: 'fa fa-cloud', - depth: 3, - status: 'critical', - }, - { - id: 13, - title: 'Maddox', - size: 24, - fileicon: 'https://www.svgrepo.com/show/5386/speedometer.svg', - depth: 3, - status: 'valid', - }, { - id: 6, - title: 'Parent 1', - size: 24, - fonticon: 'fa fa-cog', - depth: 0, - }, { - id: 7, - title: 'Parent 2', - size: 24, - fonticon: 'fa fa-cog', - depth: 0, - }, - ], - edges: [ - ...prevState.edges, - { - source: 8, - target: 9, - }, - { - source: 8, - target: 10, - }, - { - source: 7, - target: 8, - }, - { - source: 8, - target: 11, - }, - { - source: 8, - target: 12, - }, - { - source: 8, - target: 13, - }, { - source: 6, - target: 0, - }, { - source: 7, - target: 0, - }, - ], - })) - - renderSidePanel = node => ( -
-

- {node.title} - this.setState({ sidePanelOpen: false })} style={{ float: 'right', cursor: 'pointer' }} type="fa" name="close" /> -

-
-        {JSON.stringify(node, 0, 2)}
-      
-
- ) - - render() { - return ( -
- - - - - -
- - - this.handleInvokeFilter(value)} - placeholder="Filter" - onFocus={this.handleFocusFilterInput} - /> - - this.setState({ filteredValue: '' })} /> - - - - - - Show Health State: - this.setState(prevState => ({ showHealthState: !prevState.showHealthState }))} - /> - - - -
- - - { - this.state.filteredValue.trim().length > 0 && - - - - } -
-
- - - 0} - handleNodeClick={this.invokeAddParent} - healthState={this.state.showHealthState} - resetSelected={this.state.sidePanelOpen} - /> - - - {this.state.sidePanelOpen && this.renderSidePanel(this.state.selectedNode)} - -
-
-
- ); - } -} - -export default TopologyWrapper; diff --git a/src/topology/styles.scss b/src/topology/styles.scss deleted file mode 100644 index b4f34ab..0000000 --- a/src/topology/styles.scss +++ /dev/null @@ -1,23 +0,0 @@ -.topology-graph { - width: 100%; - height: 100%; - font-family: 'FontAwesome'; - cursor: auto; -} - -.topology-container { - position: relative; - height: 100%; -} - -.topology-zoom-container { - position: absolute; - bottom: 0; - width: 100%; - div { - float: right; - button { - display: block; - } - } -} \ No newline at end of file diff --git a/src/topology/test/__snapshots__/topologyCanvas.test.jsx.snap b/src/topology/test/__snapshots__/topologyCanvas.test.jsx.snap deleted file mode 100644 index 1e5197d..0000000 --- a/src/topology/test/__snapshots__/topologyCanvas.test.jsx.snap +++ /dev/null @@ -1,176 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Topology canvas component should render correctly 1`] = ` - -
- -
-
- - - - -
-
-
-
-`; diff --git a/src/topology/test/topologyCanvas.test.jsx b/src/topology/test/topologyCanvas.test.jsx deleted file mode 100644 index 4a30529..0000000 --- a/src/topology/test/topologyCanvas.test.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import { TopologyCanvas } from '../'; -import { nodes as n, edges as e } from '../data'; - -describe('Topology canvas component', () => { - let initialProps; - beforeEach(() => { - initialProps = { - nodes: n, - edges: e, - handleNodeClick: jest.fn(), - }; - }); - it('should render correctly', () => { - const wrapper = mount(); - wrapper.instance().transform.k = 2; - wrapper.instance().forceTick(); - wrapper.update(); - expect(toJson(wrapper)).toMatchSnapshot(); - }); - it('should zoom on button clicks.', () => { - const wrapper = mount(); - const spyZoom = jest.spyOn(wrapper.instance(), 'handleButtonZoom'); - - const zoomIn = wrapper.find('button#canvas-zoom-in'); - zoomIn.simulate('click'); - expect(spyZoom).toHaveBeenCalledWith(0.25); - jest.clearAllMocks(); - - const zoomOut = wrapper.find('button#canvas-zoom-out'); - zoomOut.simulate('click'); - expect(spyZoom).toHaveBeenCalledWith(-0.25); - }); - it('should assign highlighted nodes when filtering', () => { - const wrapper = mount(); - wrapper.setProps({ - nodes: [...initialProps.nodes, { - id: 44, - title: 'foo', - size: 24, - fonticon: 'fa fa-cloud', - depth: 3, - status: 'valid', - highlight: true, - }], - isFiltering: true, - }); - wrapper.instance().forceTick(); - expect(wrapper.instance().highlightedNodes).toHaveLength(1); - wrapper.setProps({ isFiltering: false }); - wrapper.instance().forceTick(); - wrapper.update(); - expect(wrapper.instance().highlightedNodes).toHaveLength(0); - }); - - it('should highlight hovered node', () => { - const wrapper = mount(); - const spyDrawNodeGradient = jest.spyOn(wrapper.instance(), 'drawNodeGradient'); - wrapper.instance().hoveredNode = { ...initialProps.nodes[0] }; - wrapper.instance().forceTick(); - expect(spyDrawNodeGradient).toHaveBeenCalledWith(expect.objectContaining({ id: initialProps.nodes[0].id }), expect.any(Array), '#DDDDDD'); - }); - - it('should highligh clicked node', () => { - const wrapper = mount(); - const spyDrawNodeGradient = jest.spyOn(wrapper.instance(), 'drawNodeGradient'); - wrapper.instance().selectedNode = { ...initialProps.nodes[0] }; - wrapper.instance().forceTick(); - expect(spyDrawNodeGradient).toHaveBeenCalledWith(expect.objectContaining({ id: initialProps.nodes[0].id }), expect.any(Array)); - }); - - it('should call drawStateLegend method', () => { - const wrapper = mount(); - const spyDrawStateLegend = jest.spyOn(wrapper.instance(), 'drawStateLegend'); - wrapper.setProps({ healthState: true }); - wrapper.instance().forceTick(); - expect(spyDrawStateLegend).toHaveBeenCalled(); - }); - - it('should find node from simulation', () => { - const wrapper = mount(); - wrapper.instance().forceTick(); - const expectedNode = wrapper.instance().simulation.nodes()[0]; - expect(wrapper.instance().findNode(expectedNode.x + 5, expectedNode.y - 8)).toEqual(expectedNode); - }); -}); diff --git a/src/topology/test/utils.test.js b/src/topology/test/utils.test.js deleted file mode 100644 index f2a770e..0000000 --- a/src/topology/test/utils.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import { duplicateArray } from '../utils'; - -describe('Duplicate array utility function', () => { - let demoArray; - beforeEach(() => { - demoArray = [{ - id: 1, - value: 'foo 1', - }, { - id: 2, - value: 'foo 2', - }, { - id: 3, - value: 'foo 3', - }, { - id: 4, - value: 'foo 4', - }, { - id: 5, - value: 'foo 5', - }, { - id: 6, - value: 'foo 6', - }]; - }); - - it('should duplicate array without any modification', () => { - const expectedResult = JSON.stringify(demoArray); - expect(JSON.stringify(duplicateArray(demoArray))).toEqual(expectedResult); - }); - - it('should duplicate array and modify all its items', () => { - const expectedResult = JSON.stringify(demoArray.map(item => ({ - ...item, - newValue: item.value.trim(), - }))); - expect(JSON.stringify(duplicateArray(demoArray, item => ({ ...item, newValue: item.value.trim() })))).toEqual(expectedResult); - }); -}); diff --git a/src/topology/topologyCanvas.jsx b/src/topology/topologyCanvas.jsx deleted file mode 100644 index ddb61e2..0000000 --- a/src/topology/topologyCanvas.jsx +++ /dev/null @@ -1,509 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import * as d3 from 'd3'; -import _ from 'lodash'; // eslint-disable-line -import { Button, Icon } from 'patternfly-react'; -import { __ } from '../global-functions'; -import { duplicateArray, loadImage, findIconUnicode, createIconChar } from './utils'; -import './styles.scss'; - -const VALID = 'valid'; -const WARNING = 'warning'; -const CRITICAL = 'critical'; -const WHITE = '#FFFFFF'; -const DEFAUT_NODE_SIZE = 30; -const EDGE_COLOR = '#DCDCDC'; -const TOOLTIP_COLOR = '#72767B'; -const FONT = 'normal normal normal 12px "Open Sans"'; - -class TopologyCanvas extends Component { - componentDidMount() { - this.IE11 = !!window.MSInputMethodContext && !!document.documentMode; - const { canvas } = this; - this.updateDimensions(canvas); - this.ctx = canvas.getContext('2d'); - this.highlightedNodes = []; - this.transform = d3.zoomIdentity; - if (this.IE11) { - this.cssRules = this.findCssIconRules(); - } - this.setUpCanvas(); - this.simulation = d3.forceSimulation() - .force('link', d3.forceLink().id(node => node.id).distance(130).strength(1) - .iterations(5)) - .force('collision', d3.forceCollide(() => 60)) - .force('charge', d3.forceManyBody().strength(-100).distanceMin(500)) - .force('y', d3.forceY(this.canvasHeight / 2).strength(0.005)) - .force('x', d3.forceX(this.canvasWidth / 2).strength(0.035)); - - // find node on hover - d3.select(this.canvas).on('mousemove', () => { - this.hoveredNode = this.findNode(d3.event.x - this.coords.left, d3.event.y - this.coords.top); - this.simulation.on('tick')(); - }); - - this.minDepth = Math.min.apply(undefined, this.props.nodes.map(({ depth }) => depth)); - this.staticNodesCount = this.props.nodes.filter(({ depth }) => depth === this.minDepth).length; - // setup inital nodes positions - const n = duplicateArray(this.props.nodes, this.computeLevelDistance); - const e = duplicateArray(this.props.edges); - this.cacheIconChars(n); - - // register resize event - window.addEventListener('resize', () => { - this.updateDimensions(canvas); - this.setUpCanvas(); - this.simulation.on('tick')(); - }); - this.simulation.nodes(n).on('tick', this.forceTick); - this.simulation.force('link').links(e); - } - - componentDidUpdate(prevProps) { - const { canvas } = this; - this.staticNodesRendered = 0; - this.updateDimensions(canvas); - this.setUpCanvas(); - if (prevProps.resetSelected && !this.props.resetSelected) { - this.selectedNode = undefined; - } - - this.minDepth = Math.min.apply(undefined, this.props.nodes.map(({ depth }) => depth)); - this.staticNodesCount = this.props.nodes.filter(({ depth }) => depth === this.minDepth).length; - const newNodes = this.props.nodes.filter(({ id }) => !prevProps.nodes.find(node => id === node.id)) - .map(node => ({ ...node, ...this.computeLevelDistance(node) })); - this.simulation.nodes([...this.simulation.nodes().map(node => ({ - ...node, - highlight: this.props.nodes.find(({ id }) => id === node.id).highlight, - })), ...newNodes]); - this.simulation.force('link').links(duplicateArray(this.props.edges)); - if (!prevProps.isFiltering) this.overlayDelay = 0; - this.highlightedNodes = this.props.isFiltering ? [ - ...this.simulation.nodes().filter(({ highlight }) => !!highlight), - ] : []; - this.simulation.alphaTarget(0.2); - this.simulation.restart(); - // smooth animation after data update - let x = 0; - const intervalID = setInterval(() => { - this.simulation.on('tick')(); - x += 1; - if (x === 100) { - window.clearInterval(intervalID); - } - }, 5); - } - - componentWillUnmount() { - window.removeEventListener('resize', this.updateDimensions); - } - - onDragStart = () => { - if (!d3.event.active) { - this.simulation.alphaTarget(0.1).restart(); - } - d3.event.subject.fx = d3.event.subject.x; - d3.event.subject.fy = d3.event.subject.y; - d3.event.subject.dragStart = true; - } - onDrag = () => { - d3.event.subject.fx += d3.event.dx / this.transform.k; - d3.event.subject.fy += d3.event.dy / this.transform.k; - d3.event.subject.dragStart = false; - } - onDragEnd = () => { - if (!d3.event.active) { - this.simulation.alphaTarget(0); - } - if (d3.event.subject.dragStart) { - d3.event.subject.fx = null; - d3.event.subject.fy = null; - } - delete d3.event.subject.dragStart; - } - - onZoom = () => { - if (d3.event !== null) { - this.simulation.alphaTarget(0).restart(); - this.transform = d3.event.transform; - } - this.simulation.on('tick')(); - } - - getStateIcon = status => ({ - [VALID]: this.icons['fa fa-check'], - [CRITICAL]: this.icons['fa fa-times'], - [WARNING]: this.icons['fa fa-exclamation'], - })[status] - - getStateIconColor = status => ({ - [VALID]: '#3F9C35', - [CRITICAL]: '#CC0000', - [WARNING]: '#EC7A08', - })[status]; - - setUpNodeClick = () => { - const selectedNode = this.findNode(d3.event.x - this.coords.left, d3.event.y - this.coords.top); - if (selectedNode) { - if (this.selectedNode) { - this.selectedNode.selected = false; - } - selectedNode.selected = true; - this.selectedNode = selectedNode; - this.props.handleNodeClick(selectedNode); - } - } - - setUpCanvas = () => { - const { canvas } = this; - // pan and zoom handler - this.zoom = d3.zoom() - .translateExtent([[0, 0], [this.canvasWidth, this.canvasHeight]]) - .scaleExtent([1, 8]) - .on('zoom', this.onZoom); - // click event handler - d3.select(this.canvas).on('click', this.setUpNodeClick); - // drag handler - d3.select(canvas).call(d3.drag().container(canvas) - .subject(() => this.findNode(d3.event.x, d3.event.y)) - .on('start', this.onDragStart) - .on('drag', this.onDrag) - .on('end', this.onDragEnd)); - // assign zoom handler to canvas - d3.select(canvas).call(this.zoom); - } - - loadIcon = (fontIcon) => { - if (this.IE11) { - const code = findIconUnicode(fontIcon, this.cssRules).toUpperCase().replace('\\', '0x'); - return String.fromCharCode(code.replace(/'|"/g, '')); - } - return createIconChar(fontIcon); - } - - cacheIconChars = (nodes) => { - this.icons = {}; - this.icons['fa fa-question'] = this.loadIcon('fa fa-question'); - this.icons['fa fa-exclamation'] = this.loadIcon('fa fa-exclamation'); - this.icons['fa fa-times'] = this.loadIcon('fa fa-times'); - this.icons['fa fa-check'] = this.loadIcon('fa fa-check'); - nodes.forEach(({ fonticon, fileicon }) => { - if (fonticon && !this.icons[fonticon]) { - this.icons[fonticon] = this.loadIcon(fonticon); - } else if (fileicon && !this.icons[fileicon]) { - this.icons[fileicon] = loadImage(fileicon); - } - }); - } - - findNode = (x, y) => this.simulation.find(...this.transform.invert([x, y]), DEFAUT_NODE_SIZE); - - normalizeNode = (node) => { - node.size = node.size || DEFAUT_NODE_SIZE; // eslint-disable-line no-param-reassign - node.x = Math.max(node.size + 5, Math.min(this.canvasWidth - node.size - 5, node.x)); // eslint-disable-line no-param-reassign - node.y = Math.max(node.size + 5, Math.min(this.canvasHeight - node.size - DEFAUT_NODE_SIZE / 2, node.y)); // eslint-disable-line no-param-reassign - } - - drawEdge = ({ source, target }) => { - this.ctx.beginPath(); - this.ctx.lineWidth = 2; - this.ctx.moveTo(...this.transform.apply([source.x, source.y])); - this.ctx.lineTo(...this.transform.apply([target.x, target.y])); - this.drawOnCanvas({ strokeStyle: EDGE_COLOR }); - } - - drawTooltip = ({ x, y }, { title, highlight }) => { - this.ctx.beginPath(); - this.ctx.fillStyle = highlight ? WHITE : TOOLTIP_COLOR; - this.ctx.textAlign = 'center'; - this.ctx.textBaseline = 'middle'; - this.ctx.font = FONT; - this.ctx.fillText(title, x, y + 35); - } - - drawHealthState = ({ - x, - y, - size, - status, - }) => { - this.ctx.textAlign = 'center'; - this.ctx.textBaseline = 'middle'; - this.ctx.font = 'normal normal normal 8px FontAwesome'; - this.ctx.beginPath(); - this.ctx.arc(...[x + size / 1.5, y + size / 1.5], 7, 0, 2 * Math.PI); - this.ctx.lineWidth = 3; - this.drawOnCanvas({ strokeStyle: this.getStateIconColor(status), fillStyle: WHITE }); - - this.ctx.fillStyle = this.getStateIconColor(status); - this.ctx.fillText(this.getStateIcon(status), x + size / 1.5, y + size / 1.5); - this.ctx.restore(); - } - - drawStateLegend = () => { - const y = this.canvasHeight - 10; - let x = 10; - this.ctx.fillStyle = this.props.isFiltering ? WHITE : TOOLTIP_COLOR; - this.ctx.font = FONT; - let text = `${__('Health State')}: `; - x += this.ctx.measureText(text).width; - this.ctx.fillText(text, x, y); - x += this.ctx.measureText(text).width / 2 + 23; - - this.drawHealthState({ - x, y, size: 0, status: VALID, - }); - this.ctx.font = FONT; - this.ctx.fillStyle = this.props.isFiltering ? WHITE : TOOLTIP_COLOR; - text = __('Valid'); - x += this.ctx.measureText(text).width; - this.ctx.fillText(text, x, y); - - x += this.ctx.measureText(text).width + 15; - this.drawHealthState({ - x, y, size: 0, status: WARNING, - }); - x -= 10; - - this.ctx.fillStyle = this.props.isFiltering ? WHITE : TOOLTIP_COLOR; - this.ctx.font = FONT; - text = __('Warning'); - x += this.ctx.measureText(text).width; - this.ctx.fillText(text, x, y); - - x += this.ctx.measureText(text).width; - this.drawHealthState({ - x, y, size: 0, status: CRITICAL, - }); - x -= 15; - - this.ctx.fillStyle = this.props.isFiltering ? WHITE : TOOLTIP_COLOR; - this.ctx.font = FONT; - x += this.ctx.measureText(text).width; - text = __('Critical'); - this.ctx.fillText(text, x, y); - } - - isOdd = value => value % 2 === 1; - - computeLevelDistance = (node) => { - if (!node.x) { - node.x = node.depth === this.minDepth ? 50 : node.depth * 200; // eslint-disable-line no-param-reassign - } - if (!node.y) { - node.y = this.canvasHeight * 0.2; // eslint-disable-line no-param-reassign - } - let coords = this.transform.apply([node.x + node.depth * 10 * this.simulation.alpha(), node.y]); - if (node.depth === this.minDepth) { - const step = 120; - const topCoord = this.isOdd(this.staticNodesCount) ? - ((this.staticNodesCount / 2) - this.staticNodesRendered) * step - : (((this.staticNodesCount / 2) + 0.5) - this.staticNodesRendered) * step; - this.staticNodesRendered += 1; - coords = [ - 60 + node.depth * 1 * this.simulation.alpha(), - this.canvasHeight / 2 + topCoord - step, - ]; - node.fx = coords[0]; // eslint-disable-line - node.fy = coords[1]; // eslint-disable-line - return { - fx: node.fx, - fy: node.fy, - }; - } - return { - x: coords[0], - y: coords[1], - }; - } - - drawNodeGradient = ({ size }, coords, outerColor = '#BBBBBB') => { - const gradient = this.ctx.createRadialGradient( - coords[0], - coords[1], - 5, - coords[0], - coords[1], - size, - ); - gradient.addColorStop(0, WHITE); - gradient.addColorStop(1, outerColor); - this.ctx.strokeStyle = outerColor; - this.ctx.fillStyle = gradient; - } - - drawNode = (node) => { - this.computeLevelDistance(node); - const coords = this.transform.apply([node.x, node.y]); - this.ctx.restore(); - this.ctx.beginPath(); - // Create shadow - this.ctx.shadowColor = 'rgba(0,0,0,0.3)'; - this.ctx.shadowBlur = 5; - this.ctx.shadowOffsetX = 1; - this.ctx.shadowOffsetY = 2; - this.ctx.arc(...coords, node.size, 0, 2 * Math.PI); - this.drawOnCanvas(); - this.ctx.shadowBlur = 0; - this.ctx.shadowOffsetX = 0; - this.ctx.shadowOffsetY = 0; - - // Create the circle - this.ctx.beginPath(); - this.ctx.arc(...coords, node.size, 0, 2 * Math.PI); - // render gradient for selected node - if (this.selectedNode && node.id === this.selectedNode.id) { - this.drawNodeGradient(node, coords); - } else if (this.hoveredNode && node.id === this.hoveredNode.id) { - this.drawNodeGradient(node, coords, '#DDDDDD'); - } else { - this.ctx.fillStyle = WHITE; - this.ctx.strokeStyle = WHITE; - } - this.drawOnCanvas(); - - // draw node graphic - if (node.fileicon) { - this.drawImage(node, { x: coords[0], y: coords[1] }); - } else { - this.drawIcon(node, { x: coords[0], y: coords[1] }); - } - // draw node health state - if (this.props.healthState && node.status) this.drawHealthState({ ...node, x: coords[0], y: coords[1] }); - this.drawTooltip({ x: coords[0], y: coords[1] }, node); - } - - drawImage = ({ fileicon, size }, { x, y }) => { - const imgRadius = size * 0.7; - this.ctx.drawImage(this.icons[fileicon], x - imgRadius, y - imgRadius, 2 * imgRadius, 2 * imgRadius); - } - - drawIcon = ({ fonticon = 'fa fa-question', size, iconColor }, { x, y }) => { - const iconChar = this.icons[fonticon]; - this.ctx.beginPath(); - this.ctx.fillStyle = iconColor || TOOLTIP_COLOR; - this.ctx.textAlign = 'center'; - this.ctx.textBaseline = 'middle'; - this.ctx.font = `normal normal normal ${size}px FontAwesome`; - this.ctx.fillText(iconChar, x, y); - } - - drawOnCanvas = ({ strokeStyle = this.ctx.strokeStyle, fillStyle = this.ctx.fillStyle } = {}) => { - this.ctx.save(); - this.ctx.fillStyle = fillStyle; - this.ctx.strokeStyle = strokeStyle; - this.ctx.stroke(); - this.ctx.fill(); - this.ctx.restore(); - } - - drawMinimap = () => { - this.ctx.beginPath(); - const mapX = 0.9 * this.canvasWidth - 10; - const mapY = 10; - const mapW = 0.1 * this.canvasWidth; - const mapH = 0.1 * this.canvasHeight; - this.ctx.rect(mapX, mapY, mapW, mapH); - this.drawOnCanvas({ strokeStyle: 'rgba(0, 0, 0, 0.3)', fillStyle: 'rgba(252, 252, 252, 0.3)' }); - this.ctx.beginPath(); - this.ctx.fillStyle = 'rgba(224, 224, 224, 0.3)'; - this.ctx.rect( - mapX - this.transform.x / this.transform.k * 0.1, - mapY - this.transform.y / this.transform.k * 0.1, - mapW / this.transform.k, - mapH / this.transform.k, - ); - this.drawOnCanvas({ fillStyle: 'rgba(224, 224, 224, 0.3)' }); - } - - forceTick = () => { - // clear the canvas - this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); - // reset static nodes counter - this.staticNodesRendered = 0; - const nodes = this.simulation.nodes(); - const links = this.simulation.force('link').links(); - nodes.forEach(this.normalizeNode); - // render health state - if (this.props.healthState) this.drawStateLegend(); - // render edges - links.forEach(this.drawEdge); - // render non highlighted nodes - nodes.forEach((node) => { - if (this.props.isFiltering && node.highlight) { - return; - } - this.drawNode(node); - }); - // render canvas overlay if filtering nodes - if (this.props.isFiltering) { - if (this.overlayDelay < 0.5) this.overlayDelay += 0.025; - this.ctx.fillStyle = `rgba(0, 0, 0, ${this.overlayDelay})`; - this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); - this.highlightedNodes.forEach(this.drawNode); - } else if (!this.props.isFiltering && this.overlayDelay > 0) { - this.overlayDelay -= 0.025; - this.ctx.fillStyle = `rgba(0, 0, 0, ${this.overlayDelay})`; - this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); - this.highlightedNodes.forEach(this.drawNode); - } - // render minimap if zoomed - if (this.transform.k !== 1) this.drawMinimap(); - } - - handleButtonZoom = value => d3.select(this.canvas).transition().duration(300).call(this.zoom.scaleTo, this.transform.k + value); - - // find the css rule containing with icon content. Only for IE11 - /* istanbul ignore next */ - /** - findCssIconRules = () => { - const styleSheet = _.find(document.styleSheets, sheet => - _.find(sheet.rules, rule => rule.selectorText && rule.selectorText.indexOf('fa-gear::before') !== -1)); - return styleSheet ? styleSheet.rules : undefined; - } - */ - /** - * @description Updates canvas size based on parent element size - */ - updateDimensions = (canvas) => { - this.coords = canvas.getBoundingClientRect(); - this.canvasWidth = canvas.clientWidth; - this.canvasHeight = canvas.clientHeight; - canvas.width = this.canvasWidth; // eslint-disable-line no-param-reassign - canvas.height = this.canvasHeight; // eslint-disable-line no-param-reassign - } - - render() { - return ( -
- { this.canvas = canvas; }} - /> -
-
- - -
-
-
- ); - } -} - -TopologyCanvas.propTypes = { - nodes: PropTypes.arrayOf(PropTypes.object).isRequired, - edges: PropTypes.arrayOf(PropTypes.object).isRequired, - isFiltering: PropTypes.bool, - handleNodeClick: PropTypes.func.isRequired, - healthState: PropTypes.bool, - resetSelected: PropTypes.bool, -}; - -TopologyCanvas.defaultProps = { - isFiltering: false, - healthState: false, - resetSelected: false, -}; - -export default TopologyCanvas; diff --git a/src/topology/utils.js b/src/topology/utils.js deleted file mode 100644 index c594aab..0000000 --- a/src/topology/utils.js +++ /dev/null @@ -1,53 +0,0 @@ -import _ from 'lodash'; - -/** - * @description returns copy of an array of objects. Can modify items with modifier function - * @param {Array} array array of objects that should be duplicated - * @param {Function} itemModifier function that mutates each item in array. Must return object. - * @return {Array} copy of input array. - */ -export const duplicateArray = (array, itemModifier = () => ({})) => - array.map(item => ({ - ...item, - ...itemModifier(item), - })); - -/** - * @param {string} imagePath source path to the image - * @returns {Image} - */ -export const loadImage = (imagePath) => { - const image = new Image(60, 60); - image.src = imagePath; - return image; -}; - -/** - * @description Creates an icon unicode character from css rule. This is required for - * older browsers like IE11 to correctly render fonticons into the html canvas. - * @param {string} fontIcon css selector of font-icon - * @param {Object} cssRules set of css rules containing the css definition of icon - * @returns {(string|undefined)} unicode for given font-icon - */ -export const findIconUnicode = (fontIcon, cssRules) => { - let rule; - const className = fontIcon.substring(fontIcon.indexOf(' ') + 1); - if (cssRules) { - rule = _.find(cssRules, r => r.selectorText && r.selectorText.indexOf(`${className}::before`) !== -1); - } - return rule ? rule.style.content : undefined; -}; - -/** - * @description creates font-icon actual content character that is later rendered into html canvas. - * @param {string} fontIcon css class of the icon - * @returns {string} icon content character - */ -export const createIconChar = (fontIcon) => { - const tmp = document.createElement('i'); - document.body.appendChild(tmp); - tmp.className = `hidden ${fontIcon}`; - const char = window.getComputedStyle(tmp, ':before').content; - document.body.removeChild(tmp); - return char.replace(/'|"/g, ''); -};