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

project deletion boilerplate + fixed revision preview w/ podselectors

jusrhee 5 лет назад
Родитель
Сommit
9278ce13a2

+ 98 - 0
dashboard/src/components/ConfirmOverlay.tsx

@@ -0,0 +1,98 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+type PropsType = {
+  message: string,
+  show: boolean,
+  onYes: () => void,
+  onNo: () => void
+};
+
+type StateType = {
+};
+
+export default class ConfirmOverlay extends Component<PropsType, StateType> {
+  render() {
+    if (this.props.show) {
+      return (
+        <StyledConfirmOverlay>
+          {this.props.message}
+          <ButtonRow>
+            <ConfirmButton
+              onClick={this.props.onYes}
+            >
+              Yes
+          </ConfirmButton>
+            <ConfirmButton
+              onClick={this.props.onNo}
+            >
+              No
+          </ConfirmButton>
+          </ButtonRow>
+        </StyledConfirmOverlay>
+      );
+    }
+    return null;
+  }
+}
+
+const StyledConfirmOverlay = styled.div`
+  position: absolute;
+  top: 0px;
+  opacity: 100%;
+  left: 0px;
+  width: 100%;
+  height: 100%;
+  z-index: 999;
+  display: flex;
+  padding-bottom: 30px;
+  align-items: center;
+  justify-content: center;
+  font-family: 'Work Sans', sans-serif;
+  font-size: 18px;
+  font-weight: 500;
+  color: white;
+  flex-direction: column;
+  background: rgb(0,0,0,0.73);
+  opacity: 0;
+  animation: lindEnter 0.2s;
+  animation-fill-mode: forwards;
+
+  @keyframes lindEnter {
+    from { opacity: 0; }
+    to   { opacity: 1; }
+  }
+`;
+
+const ButtonRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 180px;
+  margin-top: 30px;
+`;
+
+const ConfirmButton = styled.div`
+  font-size: 18px;
+  padding: 10px 15px;
+  outline: none; 
+  border: 1px solid white;
+  border-radius: 10px; 
+  text-align: center; 
+  width: 80px;
+  cursor: pointer;
+  opacity: 0;
+  font-family: 'Work Sans', sans-serif;
+  font-size: 18px;
+  font-weight: 500;
+  animation: linEnter 0.3s 0.1s;
+  animation-fill-mode: forwards;
+  @keyframes linEnter {
+    from { transform: translateY(20px); opacity: 0; }
+    to   { transform: translateY(0px); opacity: 1; }
+  }
+  :hover {
+    background: white;
+    color: #232323;
+  }
+`;

+ 5 - 3
dashboard/src/components/SaveButton.tsx

@@ -6,7 +6,8 @@ type PropsType = {
   text: string,
   onClick: () => void,
   disabled?: boolean,
-  status?: string | null
+  status?: string | null,
+  color?: string,
 };
 
 type StateType = {
@@ -45,6 +46,7 @@ export default class SaveButton extends Component<PropsType, StateType> {
         <Button 
           disabled={this.props.disabled}
           onClick={this.props.onClick}
+          color={this.props.color || '#616FEEcc'}
         >
           {this.props.text}
         </Button>
@@ -105,12 +107,12 @@ const Button = styled.button`
   text-align: left;
   border: 0;
   border-radius: 5px;
-  background: ${(props) => (!props.disabled ? '#616FEEcc' : '#aaaabb')};
+  background: ${(props) => (!props.disabled ? props.color : '#aaaabb')};
   box-shadow: ${(props) => (!props.disabled ? '0 2px 5px 0 #00000030' : 'none')};
   cursor: ${(props) => (!props.disabled ? 'pointer' : 'default')};
   user-select: none;
   :focus { outline: 0 }
   :hover {
-    background: ${(props) => (!props.disabled ? '#616FEEff' : '#aaaabb')};
+    filter: ${(props) => (!props.disabled ? 'brightness(120%)' : '')};
   }
 `;

+ 4 - 2
dashboard/src/components/values-form/InputRow.tsx

@@ -9,6 +9,7 @@ type PropsType = {
   unit?: string
   placeholder?: string
   width?: string
+  disabled?: boolean
 };
 
 type StateType = {
@@ -22,6 +23,7 @@ export default class InputRow extends Component<PropsType, StateType> {
         <Label>{label}</Label>
         <InputWrapper>
           <Input
+            disabled={this.props.disabled}
             placeholder={placeholder}
             width={width}
             type={type}
@@ -53,8 +55,8 @@ const Input = styled.input`
   background: #ffffff11;
   border: 1px solid #ffffff55;
   border-radius: 3px;
-  width: ${(props: { width: string }) => props.width ? props.width : '270px'};
-  color: white;
+  width: ${(props: { disabled: boolean, width: string }) => props.width ? props.width : '270px'};
+  color: ${(props: { disabled: boolean, width: string }) => props.disabled ? '#ffffff44' : 'white'};
   padding: 5px 8px;
   margin-right: 8px;
   height: 30px;

+ 9 - 1
dashboard/src/main/home/Home.tsx

@@ -12,6 +12,7 @@ import Loading from '../../components/Loading';
 import Templates from './templates/Templates';
 import LaunchTemplateModal from './modals/LaunchTemplateModal';
 import CreateProjectModal from './modals/CreateProjectModal';
+import UpdateProjectModal from './modals/UpdateProjectModal';
 import ClusterInstructionsModal from './modals/ClusterInstructionsModal';
 
 type PropsType = {
@@ -139,7 +140,14 @@ export default class Home extends Component<PropsType, StateType> {
         >
           <ClusterInstructionsModal />
         </ReactModal>
-
+        <ReactModal
+          isOpen={currentModal === 'UpdateProjectModal'}
+          onRequestClose={() => setCurrentModal(null, null)}
+          style={ProjectModalStyles}
+          ariaHideApp={false}
+        >
+          <UpdateProjectModal />
+        </ReactModal>
 
         <Sidebar
           logOut={this.props.logOut}

+ 2 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -201,7 +201,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
         if (err) {
           console.log(err)
         } else {
-          this.setState({ components: res.data, podSelectors: res.data.PodSelectors });
+          this.setState({ components: res.data.Objects, podSelectors: res.data.PodSelectors });
         }
       });
 
@@ -600,6 +600,7 @@ const StyledExpandedChart = styled.div`
   animation-fill-mode: forwards;
   padding: 25px; 
   display: flex;
+  overflow: hidden;
   flex-direction: column;
 
   @keyframes floatIn {

+ 8 - 84
dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx

@@ -6,6 +6,8 @@ import api from '../../../../shared/api';
 import { Context } from '../../../../shared/Context';
 import { ChartType, StorageType } from '../../../../shared/types';
 
+import ConfirmOverlay from '../../../../components/ConfirmOverlay';
+
 type PropsType = {
   showRevisions: boolean,
   toggleShowRevisions: () => void,
@@ -149,28 +151,6 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     }
   }
 
-  renderConfirmOverlay = () => {
-    if (this.state.rollbackRevision) {
-      return (
-        <ConfirmOverlay>
-          {`Are you sure you want to revert to version ${this.state.rollbackRevision}?`}
-          <ButtonRow>
-            <ConfirmButton
-              onClick={() => this.handleRollback()}
-            >
-              Yes
-            </ConfirmButton>
-            <ConfirmButton
-              onClick={() => this.setState({ rollbackRevision: null })}
-            >
-              No
-            </ConfirmButton>
-          </ButtonRow>
-        </ConfirmOverlay>
-      );
-    }
-  }
-
   renderContents = () => {
     if (this.state.loading) {
       return (
@@ -205,7 +185,12 @@ export default class RevisionSection extends Component<PropsType, StateType> {
     return (
       <StyledRevisionSection showRevisions={this.props.showRevisions}>
         {this.renderContents()}
-        {this.renderConfirmOverlay()}
+        <ConfirmOverlay
+          show={this.state.rollbackRevision && true}
+          message={`Are you sure you want to revert to version ${this.state.rollbackRevision}?`}
+          onYes={this.handleRollback}
+          onNo={() => this.setState({ rollbackRevision: null })}
+        />
       </StyledRevisionSection>
     );
   }
@@ -240,67 +225,6 @@ const StatusWrapper = styled.div`
   margin-right: 25px;
 `;
 
-const ConfirmOverlay = styled.div`
-  position: absolute;
-  top: 0px;
-  opacity: 100%;
-  left: 0px;
-  width: 100%;
-  height: 100%;
-  z-index: 999;
-  display: flex;
-  padding-bottom: 30px;
-  align-items: center;
-  justify-content: center;
-  font-family: 'Work Sans', sans-serif;
-  font-size: 18px;
-  font-weight: 500;
-  color: white;
-  flex-direction: column;
-  background: rgb(0,0,0,0.73);
-  opacity: 0;
-  animation: lindEnter 0.2s;
-  animation-fill-mode: forwards;
-
-  @keyframes lindEnter {
-    from { opacity: 0; }
-    to   { opacity: 1; }
-  }
-`;
-
-const ButtonRow = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  width: 180px;
-  margin-top: 30px;
-`;
-
-const ConfirmButton = styled.div`
-  font-size: 18px;
-  padding: 10px 15px;
-  outline: none; 
-  border: 1px solid white;
-  border-radius: 10px; 
-  text-align: center; 
-  width: 80px;
-  cursor: pointer;
-  opacity: 0;
-  font-family: 'Work Sans', sans-serif;
-  font-size: 18px;
-  font-weight: 500;
-  animation: linEnter 0.3s 0.1s;
-  animation-fill-mode: forwards;
-  @keyframes linEnter {
-    from { transform: translateY(20px); opacity: 0; }
-    to   { transform: translateY(0px); opacity: 1; }
-  }
-  :hover {
-    background: white;
-    color: #232323;
-  }
-`;
-
 const RevisionList = styled.div`
   overflow-y: auto;
   max-height: 215px;

+ 8 - 8
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -3,8 +3,6 @@ import styled from 'styled-components';
 import gradient from '../../../assets/gradient.jpg';
 
 import { Context } from '../../../shared/Context';
-import { ChartType, StorageType } from '../../../shared/types';
-import api from '../../../shared/api';
 
 type PropsType = {
 };
@@ -13,9 +11,6 @@ type StateType = {
 };
 
 export default class Dashboard extends Component<PropsType, StateType> {
-  state = {
-  }
-
   renderDashboardIcon = () => {
     let { currentProject } = this.context;
     return (
@@ -33,7 +28,12 @@ export default class Dashboard extends Component<PropsType, StateType> {
         <TitleSection>
           {this.renderDashboardIcon()}
           <Title>{currentProject && currentProject.name}</Title>
-          <i className="material-icons">more_vert</i>
+          <i
+            className="material-icons"
+            onClick={() => this.context.setCurrentModal('UpdateProjectModal', { currentProject: currentProject })}
+          >
+            more_vert
+          </i>
         </TitleSection>
 
         <InfoSection>
@@ -48,7 +48,7 @@ export default class Dashboard extends Component<PropsType, StateType> {
         <LineBreak />
 
         <Placeholder>
-          🚧 Pipelines under construction.
+          🚀 Pipelines coming soon.
         </Placeholder>
       </div>
     );
@@ -223,7 +223,7 @@ const TitleSection = styled.div`
 
   > i {
     margin-left: 10px;
-    cursor: not-allowed;
+    cursor: pointer;
     font-size 18px;
     color: #858FAAaa;
     padding: 5px;

+ 175 - 0
dashboard/src/main/home/modals/UpdateProjectModal.tsx

@@ -0,0 +1,175 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import close from '../../../assets/close.png';
+import gradient from '../../../assets/gradient.jpg';
+
+import api from '../../../shared/api';
+import { Context } from '../../../shared/Context';
+
+import SaveButton from '../../../components/SaveButton';
+import InputRow from '../../../components/values-form/InputRow';
+import ConfirmOverlay from '../../../components/ConfirmOverlay';
+
+type PropsType = {
+};
+
+type StateType = {
+  projectName: string,
+  status: string | null,
+  showDeleteOverlay: boolean
+};
+
+export default class UpdateProjectModal extends Component<PropsType, StateType> {
+  state = {
+    projectName: this.context.currentModalData.currentProject.name,
+    status: null as string | null,
+    showDeleteOverlay: false,
+  };
+  
+  handleDelete = () => {
+    alert('ay lmao');
+  }
+
+  render() {
+    return (
+      <StyledCreateProjectModal>
+        <CloseButton onClick={() => {
+          this.context.setCurrentModal(null, null);
+        }}>
+          <CloseButtonImg src={close} />
+        </CloseButton>
+
+        <ModalTitle>Project Settings</ModalTitle>
+        <Subtitle>
+          Project name
+        </Subtitle>
+
+        <InputWrapper>
+          <ProjectIcon>
+            <ProjectImage src={gradient} />
+            <Letter>{this.state.projectName ? this.state.projectName[0].toUpperCase() : '-'}</Letter>
+          </ProjectIcon>
+          <InputRow
+            disabled={true}
+            type='string'
+            value={this.state.projectName}
+            setValue={(x: string) => this.setState({ projectName: x })}
+            placeholder='ex: perspective-vortex'
+            width='470px'
+          />
+        </InputWrapper>
+
+        <SaveButton
+          text='Delete Project'
+          color='#b91133'
+          onClick={() => this.setState({ showDeleteOverlay: true })}
+          status={this.state.status}
+        />
+
+        <ConfirmOverlay
+          show={this.state.showDeleteOverlay}
+          message={`Are you sure you want to delete ${this.state.projectName}?`}
+          onYes={this.handleDelete}
+          onNo={() => this.setState({ showDeleteOverlay: false })}
+        />
+      </StyledCreateProjectModal>
+      );
+  }
+}
+
+UpdateProjectModal.contextType = Context;
+
+const Letter = styled.div`
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  background: #00000028;
+  top: 0;
+  left: 0;
+  display: flex;
+  color: white;
+  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;
+  font-weight: 400;
+  margin-top: 14px;
+`;
+
+const InputWrapper = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const Subtitle = styled.div`
+  margin-top: 23px;
+  font-family: 'Work Sans', sans-serif;
+  font-size: 13px;
+  color: #aaaabb;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  margin-bottom: -10px;
+`;
+
+const ModalTitle = styled.div`
+  margin: 0px 0px 13px;
+  display: flex;
+  flex: 1;
+  font-family: 'Assistant';
+  font-size: 18px;
+  color: #ffffff;
+  user-select: none;
+  font-weight: 700;
+  align-items: center;
+  position: relative;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+const CloseButton = styled.div`
+  position: absolute;
+  display: block;
+  width: 40px;
+  height: 40px;
+  padding: 13px 0 12px 0;
+  z-index: 1;
+  text-align: center;
+  border-radius: 50%;
+  right: 15px;
+  top: 12px;
+  cursor: pointer;
+  :hover {
+    background-color: #ffffff11;
+  }
+`;
+
+const CloseButtonImg = styled.img`
+  width: 14px;
+  margin: 0 auto;
+`;
+
+const StyledCreateProjectModal= styled.div`
+  width: 100%;
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  padding: 25px 32px;
+  overflow: hidden;
+  border-radius: 6px;
+  background: #202227;
+`;

+ 9 - 9
dashboard/src/main/home/templates/Templates.tsx

@@ -129,7 +129,7 @@ export default class Templates extends Component<PropsType, StateType> {
 Templates.contextType = Context;
 
 const Placeholder = styled.div`
-  padding-top: 100px;
+  padding-top: 200px;
   width: 100%;
   display: flex;
   justify-content: center;
@@ -163,7 +163,7 @@ const Polymer = styled.div`
 
 const TemplateDescription = styled.div`
   margin-bottom: 26px;
-  color: #ffffff55;
+  color: #ffffff66;
   text-align: center;
   font-weight: default;
   padding: 0px 25px;
@@ -187,13 +187,11 @@ const TemplateTitle = styled.div`
 
 const TemplateBlock = styled.div`
   background: none;
-  border: 1px solid #ffffff44;
+  border: 1px solid #ffffff00;
   align-items: center;
   user-select: none;
   border-radius: 5px;
   display: flex;
-  color: #ffffff;
-  ma: 'Work Sans', sans-serif;
   font-size: 13px;
   font-weight: 500;
   padding: 3px 0px 5px;
@@ -204,6 +202,8 @@ const TemplateBlock = styled.div`
   cursor: pointer;
   color: #ffffff;
   position: relative;
+  background: #26282f;
+  box-shadow: 0 5px 8px 0px #00000033;
   :hover {
     background: #ffffff11;
   }
@@ -220,8 +220,8 @@ const TemplateList = styled.div`
   margin-top: 35px;
   padding-bottom: 150px;
   display: grid;
-  grid-column-gap: 15px;
-  grid-row-gap: 15px;
+  grid-column-gap: 25px;
+  grid-row-gap: 25px;
   grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 `;
 
@@ -254,7 +254,7 @@ const StyledTemplates = styled.div`
 `;
 
 const TemplatesWrapper = styled.div`
-  width: calc(90% - 30px);
+  width: calc(90% - 150px);
   min-width: 300px;
-  padding-top: 20px;
+  padding-top: 30px;
 `;

+ 22 - 179
dashboard/src/main/home/templates/expanded-template/ExpandedTemplate.tsx

@@ -1,212 +1,55 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
-import launch from '../../../../assets/launch.svg';
-import Markdown from 'markdown-to-jsx';
-
-import { Context } from '../../../../shared/Context';
 
 import { PorterChart } from '../../../../shared/types';
 
+import TemplateInfo from './TemplateInfo';
+import LaunchTemplate from './LaunchTemplate';
+
 type PropsType = {
   currentTemplate: PorterChart,
   setCurrentTemplate: (x: PorterChart) => void
 };
 
 type StateType = {
+  showLaunchTemplate: boolean
 };
 
 export default class ExpandedTemplate extends Component<PropsType, StateType> {
   state = {
+    showLaunchTemplate: false
   }
 
-  renderIcon = (icon: string) => {
-    if (icon) {
-      return <Icon src={icon} />
-    }
-
-    return (
-      <Polymer><i className="material-icons">layers</i></Polymer>
-    );
-  }
-
-  renderTagList = () => {
-    return this.props.currentTemplate.Form.Tags.map((tag: string, i: number) => {
-      return (
-        <Tag key={i}>{tag}</Tag>
-      )
-    });
-  }
-
-  renderMarkdown = () => {
-    let { currentTemplate } = this.props;
-    if (currentTemplate.Markdown) {
+  renderContents = () => {
+    if (this.state.showLaunchTemplate) {
       return (
-        <Markdown>{currentTemplate.Markdown}</Markdown>
+        <LaunchTemplate
+          currentTemplate={this.props.currentTemplate}
+          hideLaunch={() => this.setState({ showLaunchTemplate: false })}
+        />
       );
-    } else if (currentTemplate.Form.Description) {
-      return currentTemplate.Form.Description;
     }
 
-    return currentTemplate.Description;
+    return (
+      <TemplateInfo
+        currentTemplate={this.props.currentTemplate}
+        setCurrentTemplate={this.props.setCurrentTemplate}
+        launchTemplate={() => this.setState({ showLaunchTemplate: true })}
+      />
+    );
   }
 
   render() {
-    let { Name, Icon, Description } = this.props.currentTemplate.Form;
-    let { currentTemplate } = this.props;
-    let name = Name ? Name : currentTemplate.Name;
-
     return (
       <StyledExpandedTemplate>
-        <TitleSection>
-          <Flex>
-            <i className="material-icons" onClick={() => this.props.setCurrentTemplate(null)}>
-              keyboard_backspace
-            </i>
-            {Icon ? this.renderIcon(Icon) : this.renderIcon(currentTemplate.Icon)}
-            <Title>{name}</Title>
-          </Flex>
-          <Button onClick={() => this.context.setCurrentModal('LaunchTemplateModal', { template: currentTemplate })}>
-            <img src={launch} />
-            Launch Template
-          </Button>
-        </TitleSection>
-        <TagSection>
-          <i className="material-icons">local_offer</i>
-          {this.renderTagList()}
-        </TagSection>
-        <ContentSection>
-          {this.renderMarkdown()}
-        </ContentSection>
+        {this.renderContents()}
       </StyledExpandedTemplate>
     );
   }
 }
 
-ExpandedTemplate.contextType = Context;
-
-const ContentSection = styled.div`
-  margin-top: 50px;
-  font-size: 14px;
-  line-height: 1.8em;
-  padding-bottom: 100px;
-  overflow: hidden;
-`;
-
-const Tag = styled.div`
-  border: 1px solid #ffffff44;
-  border-radius: 3px;
-  display: flex;
-  margin-right: 7px;
-  align-items: center;
-  padding: 5px 10px;
-`;
-
-const TagSection = styled.div`
-  margin-top: 20px;
-  display: flex;
-  font-size: 13px;
-  font-family: 'Work Sans', sans-serif;
-  align-items: center;
-
-  > i {
-    font-size: 20px;
-    margin-right: 10px;
-    color: #aaaabb;
-  }
-`;
-
-const Flex = styled.div`
-  display: flex;
-  align-items: center;
-
-  > i {
-    cursor: pointer;
-    font-size 24px;
-    color: #969Fbbaa;
-    padding: 3px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-  }
-`;
-
-const Button = styled.div`
-  height: 100%;
-  background: #616FEEcc;
-  :hover {
-    background: #505edddd;
-  }
-  color: white;
-  font-weight: 500;
-  font-size: 13px;
-  padding: 10px 15px;
-  border-radius: 3px;
-  cursor: pointer;
-  box-shadow: 0 5px 8px 0px #00000010;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-
-  > img {
-    width: 20px;
-    height: 20px;
-    display: flex;
-    align-items: center;
-    margin-right: 10px;
-    justify-content: center;
-  }
-`;
-
-const Icon = styled.img`
-  width: 27px;
-  margin-left: 14px;
-  margin-right: 4px;
-  margin-bottom: -1px;
-`;
-
-
-const Polymer = styled.div`
-  margin-bottom: -3px;
-
-  > i {
-    color: ${props => props.theme.containerIcon};
-    font-size: 24px;
-    margin-left: 12px;
-    margin-right: 3px;
-  }
-`;
-
-const Description = styled.div`
-  font-size: 14px;
-  font-family: 'Work Sans', sans-serif;
-  margin-left: 30px;
-  width: calc(100% - 60px);
-  height: 4em;
-  border-radius: 2px;
-  color: #aaaabb;
-  padding: 5px 10px;
-`;
-
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: 'Work Sans', sans-serif;
-  margin-left: 10px;
-  border-radius: 2px;
-  color: #ffffff;
-`;
-
-const TitleSection = styled.div`
-  display: flex;
-  margin-left: -42px;
-  flex-direction: row;
-  justify-content: space-between;
-  width: calc(100% + 42px);
-  align-items: center;
-`;
-
 const StyledExpandedTemplate = styled.div`
-  width: calc(90% - 70px);
-  padding-top: 20px;
+  width: calc(90% - 150px);
+  min-width: 300px;
+  padding-top: 30px;
 `;

+ 185 - 0
dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx

@@ -0,0 +1,185 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../../../shared/Context';
+import api from '../../../../shared/api';
+
+import { PorterChart, RepoType, Cluster } from '../../../../shared/types';
+import Selector from '../../../../components/Selector';
+
+type PropsType = {
+  currentTemplate: PorterChart,
+  hideLaunch: () => void,
+};
+
+type StateType = {
+  currentView: string,
+  clusterOptions: { label: string, value: string }[],
+  selectedCluster: string,
+  selectedRepo: RepoType | null,
+  selectedBranch: string,
+  subdirectory: string,
+};
+
+export default class LaunchTemplate extends Component<PropsType, StateType> {
+  state = {
+    currentView: 'repo',
+    clusterOptions: [] as { label: string, value: string }[],
+    selectedCluster: this.context.currentCluster.name,
+    selectedRepo: null as RepoType | null,
+    selectedBranch: '',
+    subdirectory: '',
+  };
+
+  componentDidMount() {
+    let { currentProject } = this.context;
+
+    // TODO: query with selected filter once implemented
+    api.getClusters('<token>', {}, { id: currentProject.id }, (err: any, res: any) => {
+      if (err) {
+        // console.log(err)
+      } else if (res.data) {
+        let clusterOptions = res.data.map((x: Cluster) => { return { label: x.name, value: x.name } });
+        if (res.data.length > 0) {
+          this.setState({ clusterOptions });
+        }
+      }
+    });
+  }
+
+  renderIcon = (icon: string) => {
+    if (icon) {
+      return <Icon src={icon} />
+    }
+
+    return (
+      <Polymer><i className="material-icons">layers</i></Polymer>
+    );
+  }
+
+  render() {
+    let { Name, Icon, Description } = this.props.currentTemplate.Form;
+    let { currentTemplate } = this.props;
+    let name = Name ? Name : currentTemplate.Name;
+
+    return (
+      <StyledLaunchTemplate>
+        <TitleSection>
+          <Flex>
+            <i className="material-icons" onClick={this.props.hideLaunch}>
+              keyboard_backspace
+            </i>
+            <Title>Launch Template</Title>
+          </Flex>
+        </TitleSection>
+        <ClusterSection>
+          <Template>
+            {Icon ? this.renderIcon(Icon) : this.renderIcon(currentTemplate.Icon)}
+            {name}
+          </Template>
+          <i className="material-icons">arrow_right_alt</i>
+          <ClusterLabel>
+            <i className="material-icons">device_hub</i>Cluster
+          </ClusterLabel>
+          <Selector
+            activeValue={this.state.selectedCluster}
+            setActiveValue={(cluster: string) => this.setState({ selectedCluster: cluster })}
+            options={this.state.clusterOptions}
+            width='250px'
+            dropdownWidth='335px'
+            closeOverlay={true}
+          />
+        </ClusterSection>
+      </StyledLaunchTemplate>
+    );
+  }
+}
+
+LaunchTemplate.contextType = Context;
+
+const ClusterLabel = styled.div`
+  margin-right: 10px;
+  display: flex;
+  align-items: center;
+  > i {
+    font-size: 16px;
+    margin-right: 6px;
+  }
+`;
+
+const Icon = styled.img`
+  width: 21px;
+  margin-right: 10px;
+`;
+
+
+const Polymer = styled.div`
+  margin-bottom: -3px;
+
+  > i {
+    color: ${props => props.theme.containerIcon};
+    font-size: 18px;
+    margin-right: 10px;
+  }
+`;
+
+const Template = styled.div`
+  display: flex;
+  align-items: center;
+  margin-right: 13px;
+`;
+
+const ClusterSection = styled.div`
+  display: flex;
+  align-items: center;
+  color: #ffffff;
+  font-family: 'Work Sans', sans-serif;
+  font-size: 14px;
+  font-weight: 500;
+  margin-top: 20px;
+
+  > i {
+    font-size: 25px;
+    color: #ffffff44;
+    margin-right: 13px;
+  }
+`;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+
+  > i {
+    cursor: pointer;
+    font-size 24px;
+    color: #969Fbbaa;
+    padding: 3px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+  }
+`;
+
+const Title = styled.div`
+  font-size: 24px;
+  font-weight: 600;
+  font-family: 'Work Sans', sans-serif;
+  margin-left: 11px;
+  border-radius: 2px;
+  color: #ffffff;
+`;
+
+const TitleSection = styled.div`
+  display: flex;
+  margin-left: -42px;
+  height: 40px;
+  flex-direction: row;
+  justify-content: space-between;
+  width: calc(100% + 42px);
+  align-items: center;
+`;
+
+const StyledLaunchTemplate = styled.div`
+  width: 100%;
+`;

+ 198 - 0
dashboard/src/main/home/templates/expanded-template/TemplateInfo.tsx

@@ -0,0 +1,198 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import launch from '../../../../assets/launch.svg';
+import Markdown from 'markdown-to-jsx';
+
+import { Context } from '../../../../shared/Context';
+
+import { PorterChart } from '../../../../shared/types';
+
+type PropsType = {
+  currentTemplate: PorterChart,
+  setCurrentTemplate: (x: PorterChart) => void,
+  launchTemplate: () => void
+};
+
+type StateType = {
+};
+
+export default class TemplateInfo extends Component<PropsType, StateType> {
+  renderIcon = (icon: string) => {
+    if (icon) {
+      return <Icon src={icon} />
+    }
+
+    return (
+      <Polymer><i className="material-icons">layers</i></Polymer>
+    );
+  }
+
+  renderTagList = () => {
+    return this.props.currentTemplate.Form.Tags.map((tag: string, i: number) => {
+      return (
+        <Tag key={i}>{tag}</Tag>
+      )
+    });
+  }
+
+  renderMarkdown = () => {
+    let { currentTemplate } = this.props;
+    if (currentTemplate.Markdown) {
+      return (
+        <Markdown>{currentTemplate.Markdown}</Markdown>
+      );
+    } else if (currentTemplate.Form.Description) {
+      return currentTemplate.Form.Description;
+    }
+
+    return currentTemplate.Description;
+  }
+
+  render() {
+    let { Name, Icon } = this.props.currentTemplate.Form;
+    let { currentTemplate } = this.props;
+    let name = Name ? Name : currentTemplate.Name;
+    return (
+      <StyledExpandedTemplate>
+        <TitleSection>
+          <Flex>
+            <i className="material-icons" onClick={() => this.props.setCurrentTemplate(null)}>
+              keyboard_backspace
+            </i>
+            {Icon ? this.renderIcon(Icon) : this.renderIcon(currentTemplate.Icon)}
+            <Title>{name}</Title>
+          </Flex>
+          <Button onClick={() => this.props.launchTemplate()}>
+            <img src={launch} />
+            Launch Template
+          </Button>
+        </TitleSection>
+        <TagSection>
+          <i className="material-icons">local_offer</i>
+          {this.renderTagList()}
+        </TagSection>
+        <ContentSection>
+          {this.renderMarkdown()}
+        </ContentSection>
+      </StyledExpandedTemplate>
+    );
+  }
+}
+
+TemplateInfo.contextType = Context;
+
+const ContentSection = styled.div`
+  margin-top: 50px;
+  font-size: 14px;
+  line-height: 1.8em;
+  padding-bottom: 100px;
+  overflow: hidden;
+`;
+
+const Tag = styled.div`
+  border: 1px solid #ffffff44;
+  border-radius: 3px;
+  display: flex;
+  margin-right: 7px;
+  align-items: center;
+  padding: 5px 10px;
+`;
+
+const TagSection = styled.div`
+  margin-top: 20px;
+  display: flex;
+  font-size: 13px;
+  font-family: 'Work Sans', sans-serif;
+  align-items: center;
+
+  > i {
+    font-size: 20px;
+    margin-right: 10px;
+    color: #aaaabb;
+  }
+`;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+
+  > i {
+    cursor: pointer;
+    font-size 24px;
+    color: #969Fbbaa;
+    padding: 3px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+  }
+`;
+
+const Button = styled.div`
+  height: 100%;
+  background: #616FEEcc;
+  :hover {
+    background: #505edddd;
+  }
+  color: white;
+  font-weight: 500;
+  font-size: 13px;
+  padding: 10px 15px;
+  border-radius: 3px;
+  cursor: pointer;
+  box-shadow: 0 5px 8px 0px #00000010;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+
+  > img {
+    width: 20px;
+    height: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 10px;
+    justify-content: center;
+  }
+`;
+
+const Icon = styled.img`
+  width: 27px;
+  margin-left: 14px;
+  margin-right: 4px;
+  margin-bottom: -1px;
+`;
+
+
+const Polymer = styled.div`
+  margin-bottom: -3px;
+
+  > i {
+    color: ${props => props.theme.containerIcon};
+    font-size: 24px;
+    margin-left: 12px;
+    margin-right: 3px;
+  }
+`;
+
+const Title = styled.div`
+  font-size: 24px;
+  font-weight: 600;
+  font-family: 'Work Sans', sans-serif;
+  margin-left: 10px;
+  border-radius: 2px;
+  color: #ffffff;
+`;
+
+const TitleSection = styled.div`
+  display: flex;
+  margin-left: -42px;
+  flex-direction: row;
+  height: 40px;
+  justify-content: space-between;
+  width: calc(100% + 42px);
+  align-items: center;
+`;
+
+const StyledExpandedTemplate = styled.div`
+  width: 100%;
+`;