2
0
Эх сурвалжийг харах

Merge branch 'frontend-graph' into helm-manifest

sunguroku 5 жил өмнө
parent
commit
16c80cff41

+ 1 - 0
dashboard/src/main/Main.tsx

@@ -116,6 +116,7 @@ const GlobalStyle = createGlobalStyle`
   }
   body {
     background: #202227;
+    overscroll-behavior-x: none;
   }
 `;
 

+ 3 - 0
dashboard/src/main/home/dashboard/expanded-chart/OverviewSection.tsx

@@ -6,6 +6,7 @@ import { Context } from '../../../../shared/Context';
 import { ResourceType, StorageType, ChartType } from '../../../../shared/types';
 
 import ResourceItem from './ResourceItem';
+import GraphDisplay from './graph/GraphDisplay';
 
 type PropsType = {
   toggleExpanded: () => void,
@@ -99,6 +100,8 @@ export default class OverviewSection extends Component<PropsType, StateType> {
         </ResourceList>
       )
     }
+
+    return <GraphDisplay />
   }
 
   render() {

+ 59 - 0
dashboard/src/main/home/dashboard/expanded-chart/graph/Edge.tsx

@@ -0,0 +1,59 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+type PropsType = {
+  x1: number,
+  y1: number,
+  x2: number,
+  y2: number,
+  originX: number,
+  originY: number
+};
+
+type StateType = {
+};
+
+const thickness = 2;
+
+export default class Edge extends Component<PropsType, StateType> {
+  state = {
+  }
+
+  render() {
+    let { originX, originY } = this.props;
+    let x1 = Math.round(originX + this.props.x1);
+    let x2 = Math.round(originX + this.props.x2);
+    let y1 = Math.round(originY - this.props.y1);
+    let y2 = Math.round(originY - this.props.y2);
+    
+    var length = Math.sqrt(((x2-x1) * (x2-x1)) + ((y2-y1) * (y2-y1)));
+    // center
+    var cx = ((x1 + x2) / 2) - (length / 2);
+    var cy = ((y1 + y2) / 2) - (thickness / 2);
+    // angle
+    var angle = Math.atan2((y1-y2),(x1-x2))*(180/Math.PI);
+
+    return (
+      <StyledEdge
+        length={length}
+        cx={cx}
+        cy={cy}
+        angle={angle}
+      />
+    );
+  }
+}
+
+const StyledEdge: any = styled.div.attrs((props: any) => ({
+  style: {
+    top: props.cy + 'px',
+    left: props.cx + 'px',
+    },
+}))`
+  position: absolute;
+  width: ${(props: any) => props.length + 'px'};
+  height: ${thickness}px;
+  background: #ffffff66;
+  color: #ffffff22;
+  transform: rotate(${(props: any) => props.angle}deg);
+`;

+ 191 - 0
dashboard/src/main/home/dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -0,0 +1,191 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import Node from './Node';
+import Edge from './Edge';
+
+const nodes = [
+  { id: 0, x: 0, y: 0, w: 40, h: 40 },
+  { id: 1, x: 200, y: 50, w: 40, h: 40 },
+  { id: 2, x: -230, y: -250, w: 40, h: 40 },
+  { id: 3, x: -200, y: 150, w: 40, h: 40 },
+];
+
+const edges = [
+  [0, 1],
+  [0, 2],
+  [0, 3],
+  [1, 2],
+  [1, 3],
+  [2, 3]
+];
+
+type NodeType = {
+  id: number,
+  x: number,
+  y: number,
+  w: number,
+  h: number,
+  toCursorX?: number,
+  toCursorY?: number,
+}
+
+type PropsType = {
+};
+
+type StateType = {
+  nodes: NodeType[],
+  edges: [number, number][],
+  originX: number | null,
+  originY: number | null,
+  activeIds: number[],
+  cursorX: number | null,
+  cursorY: number | null,
+  deltaX: number | null,
+  deltaY: number | null,
+  dragBg: boolean,
+  preventDrag: boolean
+};
+
+export default class GraphDisplay extends Component<PropsType, StateType> {
+  state = {
+    nodes: nodes as NodeType[],
+    edges: edges as [number, number][],
+    originX: null as (number | null),
+    originY: null as (number | null),
+    activeIds: [] as number[],
+    cursorX: null as (number | null),
+    cursorY: null as (number | null),
+    deltaX: null as (number | null),
+    deltaY: null as (number | null),
+    dragBg: false,
+    preventDrag: false
+  }
+
+  myRef: any = React.createRef();
+
+  componentDidMount() {
+    let height = this.myRef.offsetHeight;
+    let width = this.myRef.offsetWidth;
+    this.setState({
+      originX: Math.round(width / 2),
+      originY: Math.round(height / 2)
+    });
+  }
+
+  // Push to activeIds if not already present
+  handleClickNode = (id: number) => {
+    let holding = this.state.activeIds;
+    if (!holding.includes(id)) {
+      holding.push(id);
+    }
+
+    // Track and store offset to grab node from anywhere (must store)
+    let node = this.state.nodes[id];
+    if (!node.toCursorX && !node.toCursorY) {
+      node.toCursorX = node.x - this.state.cursorX;
+      node.toCursorY = node.y - this.state.cursorY;
+    } else {
+      node.toCursorX = 0;
+      node.toCursorY = 0;
+    }
+
+    this.setState({ activeIds: holding, preventDrag: true });
+  }
+
+  handleReleaseNode = () => {
+    this.setState({ activeIds: [], preventDrag: false });
+
+    // Only update dot position state on release for all active
+    let { activeIds, nodes} = this.state;
+    for (var i=0; i < activeIds.length; i++) {
+      var a = activeIds[i];
+      nodes[a].toCursorX = 0;
+      nodes[a].toCursorY = 0;
+    }
+  }
+
+  onMouseMove = (e: any) => {
+    let { originX, originY, dragBg, preventDrag } = this.state;
+
+    // Update origin-centered cursor coordinates
+    let bounds = this.myRef.getBoundingClientRect();
+    let cursorX = e.clientX - bounds.left - originX;
+    let cursorY = -(e.clientY - bounds.top - originY);
+    this.setState({ cursorX, cursorY });
+
+    // Track delta for dragging background
+    if (dragBg && !preventDrag) {
+      this.setState({ deltaX: e.movementX, deltaY: e.movementY });
+    }
+  }
+
+  // Pass origin to node for offset
+  renderNodes = () => {
+    let { activeIds, originX, originY, cursorX, cursorY } = this.state;
+
+    return this.state.nodes.map((node: NodeType, i: number) => {
+
+      // Update dot position if currently selected
+      if (activeIds.includes(node.id)) {
+        node.x = cursorX + node.toCursorX;
+        node.y = cursorY + node.toCursorY;
+      }
+
+      // Apply movement from dragging background
+      if (this.state.dragBg && !this.state.preventDrag) {
+        node.x += this.state.deltaX;
+        node.y -= this.state.deltaY;
+      }
+      
+      return (
+        <Node
+          key={i}
+          node={node}
+          originX={originX}
+          originY={originY}
+          nodeMouseDown={() => this.handleClickNode(node.id)}
+          nodeMouseUp={this.handleReleaseNode}
+          isActive={activeIds.includes(node.id)}
+        />
+      );
+    });
+  }
+
+  renderEdges = () => {
+    return this.state.edges.map((edge: [number, number], i: number) => {
+      return (
+        <Edge
+          originX={this.state.originX}
+          originY={this.state.originY}
+          x1={this.state.nodes[edge[0]].x}
+          y1={this.state.nodes[edge[0]].y}
+          x2={this.state.nodes[edge[1]].x}
+          y2={this.state.nodes[edge[1]].y}
+        />
+      );
+    });
+  }
+
+  render() {
+    return (
+      <StyledGraphDisplay
+        ref={element => this.myRef = element}
+        onMouseMove={this.onMouseMove}
+        onMouseDown={() => this.setState({ dragBg: true })}
+        onMouseUp={() => this.setState({ dragBg: false })}
+      >
+        {this.renderEdges()}
+        {this.renderNodes()}
+      </StyledGraphDisplay>
+    );
+  }
+}
+
+const StyledGraphDisplay = styled.div`
+  position: relative;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  background: #202227;
+`;

+ 69 - 0
dashboard/src/main/home/dashboard/expanded-chart/graph/Node.tsx

@@ -0,0 +1,69 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+type NodeType = {
+  id: number,
+  x: number,
+  y: number,
+  w: number,
+  h: number
+}
+
+type PropsType = {
+  node: NodeType,
+  originX: number,
+  originY: number,
+  nodeMouseDown: () => void,
+  nodeMouseUp: () => void,
+  isActive: boolean
+};
+
+type StateType = {
+};
+
+export default class Node extends Component<PropsType, StateType> {
+  state = {
+  }
+
+  render() {
+    let { x, y, w, h } = this.props.node;
+    let { originX, originY, nodeMouseDown, nodeMouseUp, isActive } = this.props;
+    return (
+      <StyledNode
+        x={Math.round(originX + x - (w / 2))}
+        y={Math.round(originY - y - (h / 2))}
+        w={Math.round(w)}
+        h={Math.round(h)}
+        onMouseDown={nodeMouseDown}
+        onMouseUp={nodeMouseUp}
+        isActive={isActive}
+      >
+        <i className="material-icons">category</i>
+      </StyledNode>
+    );
+  }
+}
+
+const StyledNode: any = styled.div.attrs((props: NodeType) => ({
+  style: {
+    top: props.y + 'px',
+    left: props.x + 'px',
+    },
+}))`
+  position: absolute;
+  width: ${(props: NodeType) => props.w + 'px'};;
+  height: ${(props: NodeType) => props.h + 'px'};;
+  background: #444446;
+  box-shadow: ${(props: any) => props.isActive ? '0 0 10px #ffffff66' : '0px 0px 10px 2px #00000022'};
+  cursor: pointer;
+  border-radius: 5px;
+  color: #ffffff22;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  > i {
+    color: white;
+    font-size: 18px;
+  }
+`;