Просмотр исходного кода

Merge branch 'staging' of https://github.com/porter-dev/porter into staging

sunguroku 5 лет назад
Родитель
Сommit
35978325d3

+ 4 - 2
dashboard/src/components/YamlEditor.tsx

@@ -7,9 +7,10 @@ import 'ace-builds/src-noconflict/theme-terminal';
 
 type PropsType = {
   value: string,
-  onChange: (e: any) => void,
+  onChange?: (e: any) => void, // Might be read-only
   height?: string,
-  border?: boolean
+  border?: boolean,
+  readOnly?: boolean
 }
 
 type StateType = {
@@ -52,6 +53,7 @@ class YamlEditor extends Component<PropsType, StateType> {
             theme='terminal'
             onChange={this.props.onChange}
             name='codeEditor'
+            readOnly={this.props.readOnly}
             editorProps={{ $blockScrolling: true }}
             height={this.props.height}
             width='100%'

+ 1 - 1
dashboard/src/main/home/dashboard/chart/ChartList.tsx

@@ -35,7 +35,7 @@ export default class ChartList extends Component<PropsType, StateType> {
       if (this.state.loading) {
         this.setState({ loading: false, error: true });
       }
-    }, 2000);
+    }, 3000);
 
     api.getCharts('<token>', {
       namespace: this.props.namespace,

+ 2 - 0
dashboard/src/main/home/dashboard/expanded-chart/ResourceItem.tsx

@@ -48,6 +48,8 @@ export default class ResourceItem extends Component<PropsType, StateType> {
             value={this.state.RawYAML}
             onChange={(e: any) => this.setState({ RawYAML: e })}
             height='300px'
+            border={true}
+            readOnly={true}
           />
         </ExpandWrapper>
       );

+ 77 - 37
dashboard/src/main/home/dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -22,7 +22,7 @@ type StateType = {
   nodes: NodeType[],
   edges: EdgeType[],
   activeIds: number[], // IDs of all currently selected nodes
-  originX: number | null, 
+  originX: number | null,
   originY: number | null,
   cursorX: number | null,
   cursorY: number | null,
@@ -32,14 +32,19 @@ type StateType = {
   panY: number | null, // Two-finger pan y-displacement
   anchorX: number | null, // Initial cursorX during region select
   anchorY: number | null, // Initial cursorY during region select
+  nodeClickX: number | null, // Initial cursorX during node click (drag vs click)
+  nodeClickY: number | null, // Initial cursorY during node click (drag vs click)
   dragBg: boolean, // Boolean to track if all nodes should move with mouse (bg drag)
-  preventBgDrag: boolean, // Prevents bg drag when moving selected with mouse down
-  relocateAllowed: boolean, // Suppresses movement of selected when drawing select region
+  preventBgDrag: boolean, // Prevent bg drag when moving selected with mouse down
+  relocateAllowed: boolean, // Suppress movement of selected when drawing select region
   scale: number,
   showKindLabels: boolean,
+  isExpanded: boolean,
   currentNode: NodeType | null,
   currentEdge: EdgeType | null,
-  isExpanded: boolean
+  openedNode: NodeType | null,
+  suppressCloseNode: boolean, // Still click should close opened unless on a node
+  suppressDisplayClicks: boolean, // Ignore clicks on InfoPanel or ButtonSection
 };
 
 // TODO: region-based unselect, shift-click, multi-region
@@ -58,14 +63,19 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     panY: null as (number | null),
     anchorX: null as (number | null),
     anchorY: null as (number | null),
+    nodeClickX: null as (number | null),
+    nodeClickY: null as (number | null),
     dragBg: false,
     preventBgDrag: false,
+    relocateAllowed: false,
     scale: 0.5,
     showKindLabels: true,
+    isExpanded: false,
     currentNode: null as (NodeType | null),
     currentEdge: null as (EdgeType | null),
-    relocateAllowed: false,
-    isExpanded: false
+    openedNode: null as (NodeType | null),
+    suppressCloseNode: false,
+    suppressDisplayClicks: false
   }
 
   spaceRef: any = React.createRef();
@@ -101,7 +111,7 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
       this.setState({ nodes, edges });
     } else {
       let storedState = JSON.parse(localStorage.getItem(`charts.${this.props.currentChartName}`))
-      this.setState(storedState)
+      this.setState(storedState);
     }
 
     document.addEventListener("keydown", this.handleKeyDown);
@@ -156,12 +166,15 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
 
   componentWillUnmount() {
     let graph = this.state;
-    console.log("unmounting...", graph)
+
     // flush non-persistent data
     graph.activeIds = [];
     graph.currentNode = null;
     graph.currentEdge = null;
     graph.isExpanded = false;
+    graph.openedNode = null;
+    graph.suppressDisplayClicks = false;
+    graph.suppressCloseNode = false;
 
     localStorage.setItem(`charts.${this.props.currentChartName}`, JSON.stringify(graph))
     this.spaceRef.removeEventListener("touchmove", (e: any) => e.preventDefault());
@@ -202,8 +215,13 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     }
   }
 
-  // Push to activeIds if not already present
   handleClickNode = (clickedId: number) => {
+    let { cursorX, cursorY } = this.state;
+
+    // Store position for distinguishing click vs drag on release
+    this.setState({ nodeClickX: cursorX, nodeClickY: cursorY, suppressCloseNode: true });
+
+    // Push to activeIds if not already present
     let holding = this.state.activeIds;
     if (!holding.includes(clickedId)) {
       holding.push(clickedId);
@@ -212,28 +230,51 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     // Track and store offset to grab node from anywhere (must store)
     this.state.nodes.forEach((node: NodeType) => {
       if (this.state.activeIds.includes(node.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;
-        }
+        node.toCursorX = node.x - cursorX;
+        node.toCursorY = node.y - cursorY;
       }
     });
 
     this.setState({ activeIds: holding, preventBgDrag: true, relocateAllowed: true });
   }
 
-  handleReleaseNode = () => {
+  handleReleaseNode = (node: NodeType) => {
+    let { cursorX, cursorY, nodeClickX, nodeClickY } = this.state;
     this.setState({ activeIds: [], preventBgDrag: 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;
+    // Distinguish node click vs drag (can't use onClick since drag counts)
+    if (cursorX === nodeClickX && cursorY === nodeClickY) {
+      this.setState({ openedNode: node });
+    }
+  }
+
+  handleMouseDown = () => {
+    let { cursorX, cursorY } = this.state;
+
+    // Store position for distinguishing click vs drag on release
+    this.setState({ nodeClickX: cursorX, nodeClickY: cursorY });
+
+    this.setState({
+      dragBg: true,
+
+      // Suppress drifting on repeated click
+      deltaX: null,
+      deltaY: null,
+      panX: null,
+      panY: null,
+      scale: 1
+    })
+  }
+
+  handleMouseUp = () => {
+    let { cursorX, nodeClickX, cursorY, nodeClickY, suppressCloseNode } = this.state;
+    this.setState({ dragBg: false, activeIds: [] });
+
+    // Distinguish bg click vs drag for setting closing opened node
+    if (!suppressCloseNode && cursorX === nodeClickX && cursorY === nodeClickY) {
+      this.setState({ openedNode: null });
+    } else if (this.state.suppressCloseNode) {
+      this.setState({ suppressCloseNode: false });
     }
   }
 
@@ -341,9 +382,10 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
           originX={originX}
           originY={originY}
           nodeMouseDown={() => this.handleClickNode(node.id)}
-          nodeMouseUp={this.handleReleaseNode}
+          nodeMouseUp={() => this.handleReleaseNode(node)}
           isActive={activeIds.includes(node.id)}
           showKindLabels={this.state.showKindLabels}
+          isOpen={node === this.state.openedNode}
 
           // Parameterized to allow setting to null
           setCurrentNode={(node: NodeType) => this.setState({ currentNode: node })}
@@ -391,24 +433,18 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
         isExpanded={this.state.isExpanded}
         ref={element => this.spaceRef = element}
         onMouseMove={this.handleMouseMove}
-        onMouseDown={() => this.setState({
-          dragBg: true,
-
-          // Suppress drifting on repeated click
-          deltaX: null,
-          deltaY: null,
-          panX: null,
-          panY: null,
-          scale: 1
-        })}
-        onMouseUp={() => this.setState({ dragBg: false, activeIds: [] })}
+        onMouseDown={this.state.suppressDisplayClicks ? null : this.handleMouseDown}
+        onMouseUp={this.state.suppressDisplayClicks ? null : this.handleMouseUp}
         onWheel={this.handleWheel}
       >
         {this.renderNodes()}
         {this.renderEdges()}
         {this.renderSelectRegion()}
 
-        <ButtonSection>
+        <ButtonSection
+          onMouseEnter={() => this.setState({ suppressDisplayClicks: true })}
+          onMouseLeave={() => this.setState({ suppressDisplayClicks: false })}
+        >
           <ToggleLabel
             onClick={() => this.setState({ showKindLabels: !this.state.showKindLabels })}
           >
@@ -426,8 +462,11 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
           </ExpandButton>
         </ButtonSection>
         <InfoPanel
+          setSuppressDisplayClicks={(x: boolean) => this.setState({ suppressDisplayClicks: x })}
           currentNode={this.state.currentNode}
           currentEdge={this.state.currentEdge}
+          openedNode={this.state.openedNode}
+          closeNode={() => this.setState({ openedNode: null })}
         />
       </StyledGraphDisplay>
     );
@@ -476,11 +515,12 @@ const ToggleLabel = styled.div`
 
 const ButtonSection = styled.div`
   position: absolute;
-  top: 17px;
+  top: 15px;
   right: 15px;
   display: flex;
   align-items: center;
   z-index: 999;
+  cursor: pointer;
 `;
 
 const ExpandButton = styled.div`

+ 74 - 11
dashboard/src/main/home/dashboard/expanded-chart/graph/InfoPanel.tsx

@@ -1,13 +1,18 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
+import yaml from 'js-yaml';
 
 import { kindToIcon, edgeColors } from '../../../../../shared/rosettaStone';
-import { NodeType, EdgeType} from '../../../../../shared/types';
-import Edge from './Edge';
+import { NodeType, EdgeType } from '../../../../../shared/types';
+
+import YamlEditor from '../../../../../components/YamlEditor';
 
 type PropsType = {
   currentNode: NodeType,
-  currentEdge: EdgeType
+  currentEdge: EdgeType,
+  openedNode: NodeType,
+  setSuppressDisplayClicks: (x: boolean) => void,
+  closeNode: () => void
 };
 
 type StateType = {
@@ -36,8 +41,27 @@ export default class InfoPanel extends Component<PropsType, StateType> {
   }
 
   renderContents = () => {
-    let { currentNode, currentEdge } = this.props;
-    if (currentNode) {
+    let { currentNode, currentEdge, openedNode } = this.props;
+    if (openedNode) {
+      return (
+        <Wrapped>
+          <Div>
+            {this.renderIcon(openedNode.kind)}
+            {openedNode.kind}
+            <ResourceName>
+              {openedNode.name}
+            </ResourceName>
+          </Div>
+          <YamlWrapper>
+            <YamlEditor
+              value={'# Placeholder resource YAML'}
+              readOnly={true}
+              height='100vw'
+            />
+          </YamlWrapper>
+        </Wrapped>
+      )
+    } else if (currentNode) {
       return (
         <Div>
           {this.renderIcon(currentNode.kind)}
@@ -80,13 +104,33 @@ export default class InfoPanel extends Component<PropsType, StateType> {
 
   render() {
     return (
-      <StyledInfoPanel>
+      <StyledInfoPanel
+        expanded={Boolean(this.props.openedNode)}
+        onMouseEnter={() => this.props.setSuppressDisplayClicks(true)}
+        onMouseLeave={() => this.props.setSuppressDisplayClicks(false)}
+      >
         {this.renderContents()}
+
+        {this.props.openedNode ? <i onClick={this.props.closeNode} className="material-icons">close</i> : null}
       </StyledInfoPanel>
     );
   }
 }
 
+const Wrapped = styled.div`
+  height: 100%;
+  position: relative;
+`;
+
+const YamlWrapper = styled.div`
+  width: 100%;
+  margin-top: 7px;
+  height: calc(100% - 44px);
+  overflow: hidden;
+  border-radius: 5px;
+  border: 1px solid #ffffff22;
+`;
+
 const ColorBlock = styled.div`
   width: 15px;
   height: 15px;
@@ -101,12 +145,16 @@ const ColorBlock = styled.div`
 
 const Div = styled.div`
   display: flex;
+  padding-left: 7px;
   align-items: center;
+  padding-right: 23px;
 `;
 
 const EdgeInfo = styled.div`
   display: flex;
   align-items: center;
+  padding-left: 7px;
+  padding-right: 23px;
   margin-top: 5px;
 `;
 
@@ -138,17 +186,32 @@ const StyledInfoPanel = styled.div`
   right: 15px;
   bottom: 15px;
   color: #ffffff66;
-  height: 40px;
-  width: 400px;
+  height: ${(props: { expanded: boolean }) => props.expanded ? 'calc(100% - 68px)' : '40px'};
+  width: ${(props: { expanded: boolean }) => props.expanded ? 'calc(50% - 68px)' : '400px'};
   max-width: 600px;
-  background: #44444699;
+  background: #34373Cdf;
   border-radius: 3px;
-  padding-left: 20px;
+  padding-left: 11px;
   display: inline-block;
   z-index: 999;
   padding-top: 7px;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
-  padding-right: 13px;
+  padding-right: 11px;
+  cursor: default;
+
+  > i {
+    position: absolute;
+    padding: 5px;
+    top: 6px;
+    right: 6px;
+    border-radius: 50px;
+    font-size: 17px;
+    cursor: pointer;
+    color: white;
+    :hover {
+      background: #ffffff22;
+    }
+  }
 `;

+ 7 - 4
dashboard/src/main/home/dashboard/expanded-chart/graph/Node.tsx

@@ -13,6 +13,7 @@ type PropsType = {
   isActive: boolean,
   showKindLabels: boolean,
   setCurrentNode: (node: NodeType) => void,
+  isOpen: boolean
 };
 
 type StateType = {
@@ -37,7 +38,6 @@ export default class Node extends Component<PropsType, StateType> {
         y={Math.round(originY - y - (h / 2))}
         w={Math.round(w)}
         h={Math.round(h)}
-        isActive={isActive}
       >
         <Kind>
           {this.props.showKindLabels ? kind : null}
@@ -47,6 +47,8 @@ export default class Node extends Component<PropsType, StateType> {
           onMouseUp={nodeMouseUp}
           onMouseEnter={() => this.props.setCurrentNode(this.props.node)}
           onMouseLeave={() => this.props.setCurrentNode(null)}
+          isActive={isActive}
+          isOpen={this.props.isOpen}
         >
           <i className="material-icons">{icon}</i>
         </NodeBlock>
@@ -98,6 +100,8 @@ const NodeBlock = styled.div`
   align-items: center;
   justify-content: center;
   border-radius: 100px;
+  border: ${(props: { isActive: boolean, isOpen: boolean }) => props.isOpen ? '3px solid #ffffff' : ''};
+  box-shadow: ${(props: { isActive: boolean, isOpen: boolean }) => props.isActive ? '0 0 10px #ffffff66' : '0px 0px 10px 2px #00000022'};
   z-index: 100;
   cursor: pointer;
   :hover {
@@ -116,9 +120,8 @@ const StyledNode: any = styled.div.attrs((props: NodeType) => ({
     },
 }))`
   position: absolute;
-  width: ${(props: NodeType) => props.w + 'px'};;
-  height: ${(props: NodeType) => props.h + 'px'};;
-  box-shadow: ${(props: any) => props.isActive ? '0 0 10px #ffffff66' : '0px 0px 10px 2px #00000022'};
+  width: ${(props: NodeType) => props.w + 'px'};
+  height: ${(props: NodeType) => props.h + 'px'};
   color: #ffffff22;
   border-radius: 100px;
   display: flex;

+ 1 - 1
dashboard/src/shared/types.tsx

@@ -42,7 +42,7 @@ export interface NodeType {
   id: number,
   name: string,
   kind: string,
-  yaml: string,
+  RawYAML?: string,
   x: number,
   y: number,
   w: number,