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

project organization boilerplate

jusrhee 5 лет назад
Родитель
Сommit
8e4c887509

+ 21 - 10
dashboard/src/components/Selector.tsx

@@ -6,6 +6,7 @@ type PropsType = {
   options: { value: string, label: string }[],
   setActiveValue: (x: string) => void,
   width: string,
+  height?: string,
   dropdownLabel?: string,
   dropdownWidth?: string,
   dropdownMaxHeight?: string,
@@ -41,6 +42,16 @@ export default class Selector extends Component<PropsType, StateType> {
     });
   }
 
+  renderDropdownLabel = () => {
+    if (this.props.dropdownLabel && this.props.dropdownLabel !== '') {
+      return (
+        <DropdownLabel>
+          {this.props.dropdownLabel}
+        </DropdownLabel>
+      );
+    }
+  }
+
   renderDropdown = () => {
     if (this.state.expanded) {
       return (
@@ -50,9 +61,7 @@ export default class Selector extends Component<PropsType, StateType> {
             dropdownWidth={this.props.dropdownWidth ? this.props.dropdownWidth : this.props.width}
             dropdownMaxHeight={this.props.dropdownMaxHeight}
           >
-            <DropdownLabel>
-              {this.props.dropdownLabel}
-            </DropdownLabel>
+            {this.renderDropdownLabel()}
             {this.renderOptionList()}
           </Dropdown>
         </div>
@@ -75,6 +84,7 @@ export default class Selector extends Component<PropsType, StateType> {
           onClick={() => this.setState({ expanded: !this.state.expanded })}
           expanded={this.state.expanded}
           width={this.props.width}
+          height={this.props.height}
         >
           <TextWrap>
             {activeValue === '' ? 'All' : this.getLabel(activeValue)}
@@ -91,6 +101,7 @@ const TextWrap = styled.div`
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+  z-index: 999;
 `;
 
 const DropdownLabel = styled.div`
@@ -102,6 +113,7 @@ const DropdownLabel = styled.div`
 
 const Option = styled.div` 
   width: 100%;
+  border-top: 1px solid #00000000;
   border-bottom: 1px solid ${(props: { selected: boolean, lastItem: boolean }) => props.lastItem ? '#ffffff00' : '#ffffff15'};
   height: 35px;
   font-size: 13px;
@@ -136,12 +148,11 @@ const Dropdown = styled.div`
   background: #26282f;
   width: ${(props: { dropdownWidth: string, dropdownMaxHeight: string }) => props.dropdownWidth};
   max-height: ${(props: { dropdownWidth: string, dropdownMaxHeight: string }) => props.dropdownMaxHeight ? props.dropdownMaxHeight : '300px'};
-  padding-bottom: 10px;
   border-radius: 3px;
   z-index: 999;
   overflow-y: auto;
   margin-bottom: 20px;
-  box-shadow: 0 8px 20px 0px #00000055;
+  box-shadow: 0 8px 20px 0px #00000088;
 `;
 
 const StyledSelector = styled.div`
@@ -149,8 +160,8 @@ const StyledSelector = styled.div`
 `;
 
 const MainSelector = styled.div`
-  width: ${(props: { expanded: boolean, width: string }) => props.width};
-  height: 30px;
+  width: ${(props: { expanded: boolean, width: string, height?: string }) => props.width};
+  height: ${(props: { expanded: boolean, width: string, height?: string }) => props.height ? props.height : '30px'};
   border: 1px solid #ffffff55;
   font-size: 13px;
   padding: 5px 10px;
@@ -160,13 +171,13 @@ const MainSelector = styled.div`
   align-items: center;
   justify-content: space-between;
   cursor: pointer;
-  background: ${(props: { expanded: boolean, width: string }) => props.expanded ? '#ffffff33' : '#ffffff11'};
+  background: ${(props: { expanded: boolean, width: string, height?: string }) => props.expanded ? '#ffffff33' : '#ffffff11'};
   :hover {
-    background: ${(props: { expanded: boolean, width: string }) => props.expanded ? '#ffffff33' : '#ffffff22'};
+    background: ${(props: { expanded: boolean, width: string, height?: string }) => props.expanded ? '#ffffff33' : '#ffffff22'};
   }
 
   > i {
     font-size: 20px;
-    transform: ${(props: { expanded: boolean, width: string }) => props.expanded ? 'rotate(180deg)' : ''};
+    transform: ${(props: { expanded: boolean, width: string, height?: string }) => props.expanded ? 'rotate(180deg)' : ''};
   }
 `;

+ 1 - 0
dashboard/src/components/repo-selector/BranchList.tsx

@@ -107,5 +107,6 @@ const LoadingWrapper = styled.div`
   display: flex;
   align-items: center;
   justify-content: center;
+  font-size: 13px;
   color: #ffffff44;
 `;

+ 2 - 0
dashboard/src/components/repo-selector/ContentsList.tsx

@@ -149,6 +149,7 @@ const BackLabel = styled.div`
   font-size: 16px;
   padding-left: 16px;
   margin-top: -4px;
+  padding-bottom: 4px;
 `;
 
 const Item = styled.div`
@@ -189,6 +190,7 @@ const FileItem = styled(Item)`
 const LoadingWrapper = styled.div`
   padding: 30px 0px;
   background: #ffffff11;
+  font-size: 13px;
   display: flex;
   align-items: center;
   justify-content: center;

+ 1 - 0
dashboard/src/components/repo-selector/RepoSelector.tsx

@@ -233,6 +233,7 @@ const LoadingWrapper = styled.div`
   background: #ffffff11;
   display: flex;
   align-items: center;
+  font-size: 13px;
   justify-content: center;
   color: #ffffff44;
 `;

+ 1 - 1
dashboard/src/components/values-form/ValuesForm.tsx

@@ -169,7 +169,7 @@ const StyledValuesForm = styled.div`
   height: 100%;
   background: #ffffff11;
   color: #ffffff;
-  padding: 0px 35px 30px;
+  padding: 0px 35px 25px;
   position: relative;
   border-radius: 5px;
   font-size: 13px;

+ 30 - 6
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -50,6 +50,24 @@ export default class Dashboard extends Component<PropsType, StateType> {
     });
   }
 
+  renderDashboardIcon = () => {
+    if (false) {
+      let { currentCluster } = this.props;
+      return (
+        <DashboardIcon>
+          <DashboardImage src={gradient} />
+          <Overlay>{currentCluster && currentCluster[0].toUpperCase()}</Overlay>
+        </DashboardIcon>
+      );
+    }
+
+    return (
+      <DashboardIcon>
+        <i className="material-icons">device_hub</i>
+      </DashboardIcon>
+    );
+  }
+
   renderContents = () => {
     let { currentCluster, setSidebar } = this.props;
 
@@ -67,10 +85,7 @@ export default class Dashboard extends Component<PropsType, StateType> {
     return (
       <div>
         <TitleSection>
-          <ProjectIcon>
-            <ProjectImage src={gradient} />
-            <Overlay>{currentCluster && currentCluster[0].toUpperCase()}</Overlay>
-          </ProjectIcon>
+          {this.renderDashboardIcon()}
           <Title>{currentCluster}</Title>
           <i className="material-icons">more_vert</i>
         </TitleSection>
@@ -250,17 +265,26 @@ const Overlay = styled.div`
   color: white;
 `;
 
-const ProjectImage = styled.img`
+const DashboardImage = styled.img`
   height: 45px;
   width: 45px;
   border-radius: 5px;
 `;
 
-const ProjectIcon = styled.div`
+const DashboardIcon = styled.div`
   position: relative;
   height: 45px;
   width: 45px;
   border-radius: 5px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #676C7C;
+  border: 2px solid #8e94aa;
+
+  > i {
+    font-size: 22px;
+  }
 `;
 
 const Title = styled.div`

+ 1 - 1
dashboard/src/main/home/dashboard/NamespaceSelector.tsx

@@ -61,7 +61,7 @@ export default class NamespaceSelector extends Component<PropsType, StateType> {
           activeValue={this.props.namespace}
           setActiveValue={(namespace) => this.props.setNamespace(namespace)}
           options={this.state.namespaceOptions}
-          dropdownLabel='Namespace:'
+          dropdownLabel='Namespace'
           width='150px'
           dropdownWidth='230px'
           closeOverlay={true}

+ 3 - 3
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -116,14 +116,14 @@ export default class ClusterSection extends Component<PropsType, StateType> {
             </DropdownIcon>
           </DrawerButton>
         </ClusterSelector>
-      )
+      );
     }
 
     return (
       <InitializeButton onClick={this.showClusterConfigModal}>
         <Plus>+</Plus> Add a Cluster
       </InitializeButton>
-    )
+    );
   };
 
   render() {
@@ -198,7 +198,7 @@ const ClusterName = styled.div`
   display: inline-block;
   width: 130px;
   margin-left: 3px;
-  font-weight: 600;
+  font-weight: 400;
 `;
 
 const DropdownIcon = styled.span`

+ 256 - 0
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -0,0 +1,256 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import gradient from '../../../assets/gradient.jpg';
+
+import api from '../../../shared/api';
+import { Context } from '../../../shared/Context';
+import { KubeContextConfig } from '../../../shared/types';
+
+import Selector from '../../../components/Selector';
+import Drawer from './Drawer';
+
+type PropsType = {
+  setWelcome?: (x: boolean) => void,
+  setCurrentView?: (x: string) => void,
+};
+
+type StateType = {
+  projects: any[],
+  expanded: boolean
+};
+
+const options = [
+  { label: 'Thunder', value: 'z' },
+  { label: 'Lightning', value: 'x' },
+  { label: 'Storm', value: 'qq' },
+  { label: 'Backlog', value: 'd' },
+]
+
+export default class ProjectSection extends Component<PropsType, StateType> {
+  state = {
+    projects: [] as any[],
+    expanded: false,
+  };
+
+  updateProjects = () => {
+    this.context.setCurrentProject('z');
+  }
+
+  componentDidMount() {
+    this.updateProjects();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (prevProps !== this.props) {
+    }
+  }
+  
+  showProjectCreateModal = () => {
+    this.context.setCurrentModal('ClusterConfigModal', { updateClusters: null });
+  }
+
+  handleOptionClick = (option: { value: string, label: string }) => {
+    this.context.setCurrentProject(option.value);
+  }
+
+
+  renderOptionList = () => {
+    return options.map((option: { value: string, label: string }, i: number) => {
+      return (
+        <Option
+          key={i}
+          selected={option.value === this.context.currentProject}
+          onClick={() => this.handleOptionClick(option)}
+          lastItem={i === options.length - 1}
+        >
+          {option.label}
+        </Option>
+      );
+    });
+  }
+
+  renderDropdown = () => {
+    if (this.state.expanded) {
+      return (
+        <div>
+          <CloseOverlay onClick={() => this.setState({ expanded: false })} />
+          <Dropdown>
+            {this.renderOptionList()}
+          </Dropdown>
+        </div>
+      )
+    }
+  }
+
+  getLabel = (value: string): any => {
+    let tgt = options.find((element: { value: string, label: string }) => element.value === value);
+    if (tgt) {
+      return tgt.label;
+    }
+  }
+
+  render() {
+    if (this.context.currentProject) {
+      let projectName = this.getLabel(this.context.currentProject);
+      return (
+        <StyledProjectSection>
+          <MainSelector
+            onClick={() => this.setState({ expanded: !this.state.expanded })}
+            expanded={this.state.expanded}
+          >
+            <ProjectIcon>
+              <ProjectImage src={gradient} />
+              <Letter>{projectName && projectName[0].toUpperCase()}</Letter>
+            </ProjectIcon>
+            <ProjectName>{projectName}</ProjectName>
+            <i className="material-icons">arrow_drop_down</i>
+          </MainSelector>
+          {this.renderDropdown()}
+        </StyledProjectSection>
+      );
+    }
+    return (
+      <InitializeButton onClick={this.showProjectCreateModal}>
+        <Plus>+</Plus> Create a Project
+      </InitializeButton>
+    );
+  }
+}
+
+ProjectSection.contextType = Context;
+
+const Plus = styled.div`
+  margin-right: 10px;
+  font-size: 15px;
+`;
+
+const InitializeButton = styled.div`
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: calc(100% - 30px);
+  height: 38px;
+  margin: 20px 15px 0;
+  font-size: 13px;
+  font-weight: 500;
+  border-radius: 3px;
+  color: #ffffff;
+  padding-bottom: 1px;
+  cursor: pointer;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+  }
+`;
+
+const Option = styled.div` 
+  width: 100%;
+  border-top: 1px solid #00000000;
+  border-bottom: 1px solid ${(props: { selected: boolean, lastItem: boolean }) => props.lastItem ? '#ffffff00' : '#ffffff15'};
+  height: 35px;
+  font-size: 13px;
+  padding-top: 9px;
+  align-items: center;
+  padding-left: 15px;
+  cursor: pointer;
+  padding-right: 10px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  background: ${(props: { selected: boolean, lastItem: boolean }) => props.selected ? '#ffffff11' : ''};
+
+  :hover {
+    background: #ffffff22;
+  }
+`;
+
+const CloseOverlay = styled.div`
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 999;
+`;
+
+const Dropdown = styled.div`
+  position: absolute;
+  right: 10px;
+  top: calc(100% + 5px);
+  background: #26282f;
+  width: 180px;
+  max-height: 300px;
+  border-radius: 3px;
+  z-index: 999;
+  overflow-y: auto;
+  margin-bottom: 20px;
+  box-shadow: 0 8px 20px 0px #00000088;
+`;
+
+const ProjectName = styled.div`
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+const Letter = styled.div`
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  background: #00000028;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const ProjectImage = styled.img`
+  width: 100%;
+  height: 100%;
+`;
+
+const ProjectIcon = styled.div`
+  width: 25px;
+  min-width: 25px;
+  height: 25px;
+  border-radius: 3px;
+  overflow: hidden;
+  position: relative;
+  margin-right: 10px;
+  margin-left: 20px;
+  font-weight: 400;
+`;
+
+const StyledProjectSection = styled.div`
+  position: relative;
+`;
+
+const MainSelector = styled.div`
+  display: flex;
+  align-items: center;
+  margin: 10px 0 0;
+  font-size: 14px;
+  font-family: 'Work Sans', sans-serif;
+  font-weight: 600;
+  cursor: pointer;
+  padding: 10px 0;
+  :hover {
+    > i {
+      background: #ffffff11;
+    }
+  }
+
+  > i {
+    margin-left: 7px;
+    margin-right: 12px;
+    font-size: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 20px;
+    background: ${(props: { expanded: boolean }) => props.expanded ? '#ffffff11' : ''};
+  }
+`;

+ 57 - 28
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -9,6 +9,7 @@ import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
 
 import ClusterSection from './ClusterSection';
+import ProjectSection from './ProjectSection';
 
 type PropsType = {
   logOut: () => void,
@@ -102,29 +103,10 @@ export default class Sidebar extends Component<PropsType, StateType> {
     }); 
   }
 
-  // SidebarBg is separate to cover retracted drawer
-  render() {
-    return (
-      <div>
-        {this.renderPullTab()}
-        <StyledSidebar showSidebar={this.state.showSidebar}>
-          <SidebarBg />
-          <CollapseButton 
-            onClick={this.toggleSidebar} 
-            onMouseOver={() => { this.setState({ showTooltip: true }) }}
-            onMouseOut={() => { this.setState({ showTooltip: false }) }}
-          >
-            {this.renderTooltip()}
-            <i className="material-icons">double_arrow</i>
-          </CollapseButton>
-
-          <UserSection>
-            <RingWrapper>
-              <UserIcon src={gradient} />
-            </RingWrapper>
-            <UserName>{this.context.user.email}</UserName>
-          </UserSection>
-
+  renderProjectContents = () => {
+    if (this.context.currentProject) {
+      return (
+        <div>
           <SidebarLabel>Home</SidebarLabel>
           <NavButton
             onClick={() => this.props.setCurrentView('templates')}
@@ -152,10 +134,43 @@ export default class Sidebar extends Component<PropsType, StateType> {
             setCurrentView={this.props.setCurrentView}
             isSelected={this.props.currentView === 'dashboard'}
           />
+        </div>
+      );
+    }
+
+    // Render placeholder if no project exists
+    return (
+      <ProjectPlaceholder>
+        You have no projects.
+      </ProjectPlaceholder>
+    );
+  }
+
+  // SidebarBg is separate to cover retracted drawer
+  render() {
+    return (
+      <div>
+        {this.renderPullTab()}
+        <StyledSidebar showSidebar={this.state.showSidebar}>
+          <SidebarBg />
+          <CollapseButton 
+            onClick={this.toggleSidebar} 
+            onMouseOver={() => { this.setState({ showTooltip: true }) }}
+            onMouseOut={() => { this.setState({ showTooltip: false }) }}
+          >
+            {this.renderTooltip()}
+            <i className="material-icons">double_arrow</i>
+          </CollapseButton>
+
+          <ProjectSection />
+
+          <br />
+
+          {this.renderProjectContents()}
 
           <BottomSection>
             <LogOutButton onClick={this.handleLogout}>
-              Log Out <i className="material-icons">keyboard_return</i>
+            Log Out <i className="material-icons">keyboard_return</i>
             </LogOutButton>
           </BottomSection>
         </StyledSidebar>
@@ -166,14 +181,28 @@ export default class Sidebar extends Component<PropsType, StateType> {
 
 Sidebar.contextType = Context;
 
+const ProjectPlaceholder = styled.div`
+  background: #ffffff11;
+  border-radius: 5px;
+  margin: 0 15px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: calc(100% - 180px);
+  font-size: 13px;
+  font-family: 'Work Sans', sans-serif;
+  color: #ffffff44;
+  margin-top: 20px;
+`;
+
 const NavButton = styled.div`
   display: block;
   position: relative;
   text-decoration: none;
   height: 42px;
-  padding: 10px 35px 12px 53px;
+  padding: 12px 35px 1px 53px;
   font-size: 14px;
-  font-family: 'Hind Siliguri', sans-serif;
+  font-family: 'Work Sans', sans-serif;
   color: #ffffff;
   overflow: hidden;
   white-space: nowrap;
@@ -256,7 +285,7 @@ const SidebarLabel = styled.div`
 const UserSection = styled.div`
   width: 100%;
   height: 40px;
-  margin: 6px 0px 25px;
+  margin: 6px 0px 17px;
   display: flex;
   flex: 1;
   flex-direction: row;
@@ -349,7 +378,7 @@ const CollapseButton = styled.div`
   top: 8px;
   height: 23px;
   width: 23px;
-  background: #525563;
+  background: #525563aa;
   border-top-left-radius: 3px;
   border-bottom-left-radius: 3px;
   cursor: pointer;

+ 1 - 0
dashboard/src/main/home/templates/expanded-template/ExpandedTemplate.tsx

@@ -89,6 +89,7 @@ const ContentSection = styled.div`
   font-size: 14px;
   line-height: 1.8em;
   padding-bottom: 100px;
+  overflow: hidden;
 `;
 
 const Tag = styled.div`

+ 10 - 6
dashboard/src/shared/Context.tsx

@@ -25,24 +25,28 @@ const ContextConsumer = Context.Consumer;
 class ContextProvider extends Component {
   state = {
     currentModal: null as string | null,
-    setCurrentModal: (currentModal: string, currentModalData?: any): void => {
+    currentModalData: null as any,
+    setCurrentModal: (currentModal: string, currentModalData?: any) => {
       this.setState({ currentModal, currentModalData });
     },
-    currentModalData: null as any,
     currentError: null as string | null,
-    setCurrentError: (currentError: string): void => {
+    setCurrentError: (currentError: string) => {
       this.setState({ currentError });
     },
     currentCluster: null as string | null,
-    setCurrentCluster: (currentCluster: string): void => {
+    setCurrentCluster: (currentCluster: string) => {
       this.setState({ currentCluster });
     },
+    currentProject: null as string | null,
+    setCurrentProject: (currentProject: string) => {
+      this.setState({ currentProject });
+    },
     user: null as any,
-    setUser: (userId: number, email: string): void => {
+    setUser: (userId: number, email: string) => {
       this.setState({ user: {userId, email} });
     },
     devOpsMode: true,
-    setDevOpsMode: (devOpsMode: boolean): void => {
+    setDevOpsMode: (devOpsMode: boolean) => {
       this.setState({ devOpsMode });
     }
   };