Explorar el Código

Refactor and restyle the options page according to design CORWEB-18.

Implemented ability to add description to fields and display it in tooltips. Also added a mockup description for 'flavor_name'.

Use a switch component for all boolean fields. Refactored ConnectionStore.js to no longer convert boolean fields to strings.

Use the switch component to switch between simple and advanced options.

Refactored wizard's options style to no longer use floats, instead use CSS Flex.

Removed unused variable in WizardSummary.
Sergiu Miclea hace 8 años
padre
commit
b0a0b5a11f

+ 28 - 1
src/components/App/App.scss

@@ -416,6 +416,25 @@ button {
 :global(.Dropdown-option.is-selected) {
 :global(.Dropdown-option.is-selected) {
   font-weight: 500;
   font-weight: 500;
 }
 }
+
+:global(.reactTooltip) {
+  color: $gray-dark !important;
+  background: $gray-light !important;
+  width: 128px;
+  padding: 8px !important;
+  box-shadow: 0 6px 9px 1px rgba(32, 34, 52, 0.25);
+  margin-left: 12px !important;
+  margin-top: 10px !important;
+  &:after {
+    border-right-color: $gray-light !important;
+    border-right-width: 8px !important;
+    left: -8px !important;
+    border-top-width: 8px !important;
+    border-bottom-width: 8px !important;
+    margin-top: -18px !important;
+  }
+}
+
 :global(.detailViewHead) {
 :global(.detailViewHead) {
   p {
   p {
     margin: 0;
     margin: 0;
@@ -795,6 +814,14 @@ button {
   }
   }
 }
 }
 
 
+:global(.input-label) {
+  font-size: 14px;
+  color: $black;
+  height: 40px;
+  width: 248px;
+  display: flex;
+}
+
 input[type="text"], input[type="password"] {
 input[type="text"], input[type="password"] {
   height: 32px;
   height: 32px;
   border-radius: 4px;
   border-radius: 4px;
@@ -803,7 +830,7 @@ input[type="text"], input[type="password"] {
   font-size: 14px;
   font-size: 14px;
   color: $gray-dark;
   color: $gray-dark;
   padding: 0 8px 0 16px;
   padding: 0 8px 0 16px;
-  font-weight: $weight-regular;
+  font-weight: $weight-light;
   transition: all $animation-swift-out;
   transition: all $animation-swift-out;
   box-sizing: border-box;
   box-sizing: border-box;
   &:hover {
   &:hover {

+ 6 - 2
src/components/Helper/Helper.js

@@ -42,8 +42,12 @@ class Helper extends Component {
     return hiddenPass
     return hiddenPass
   }
   }
 
 
-  static convertCloudFieldLabel(label) {
-    return defaultLabels[label] || label
+  static convertCloudFieldLabel(field) {
+    return (defaultLabels[field] && defaultLabels[field].label) || defaultLabels[field] || field
+  }
+
+  static getCloudFieldDescription(field) {
+    return defaultLabels[field] && defaultLabels[field].description
   }
   }
 
 
   static convertCloudLabel(label) {
   static convertCloudLabel(label) {

+ 8 - 23
src/components/InfoIcon/InfoIcon.js

@@ -18,38 +18,23 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React, { Component, PropTypes } from 'react';
 import React, { Component, PropTypes } from 'react';
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './InfoIcon.scss';
 import s from './InfoIcon.scss';
-import ReactTooltip from 'react-tooltip'
 
 
 class InfoIcon extends Component {
 class InfoIcon extends Component {
   static propTypes = {
   static propTypes = {
-    text: PropTypes.string,
-    place: PropTypes.string
-  }
-
-  static defaultProps = {
-    text: "Missing 'text' property",
-    place: "right"
-  }
-
-  componentWillMount() {
-
+    text: PropTypes.string.isRequired,
+    className: PropTypes.string
   }
   }
 
 
   render() {
   render() {
+    let className = s.root + ' ' + (this.props.className || '')
+    let icon = this.props.text ? <div className={s.icon} data-tip={this.props.text} /> : null
+
     return (
     return (
-      <div data-tip={this.props.text} className={s.root}>
-        <ReactTooltip
-          className={s.infoIcon}
-          multiline={true} // eslint-disable-line react/jsx-boolean-value
-          type="light"
-          place={this.props.place}
-          effect="solid"
-          border={true} // eslint-disable-line react/jsx-boolean-value
-        />
+      <div className={className}>
+        {icon}
       </div>
       </div>
-    );
+    )
   }
   }
-
 }
 }
 
 
 export default withStyles(InfoIcon, s);
 export default withStyles(InfoIcon, s);

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
src/components/InfoIcon/InfoIcon.scss


+ 5 - 5
src/components/InfoIcon/package.json

@@ -1,6 +1,6 @@
 {
 {
-  "name": "InfoIcon",
-  "version": "0.0.0",
-  "private": true,
-  "main": "./InfoIcon.js"
-}
+    "name": "InfoIcon",
+    "version": "0.0.0",
+    "private": true,
+    "main": "./InfoIcon.js"
+}

+ 74 - 0
src/components/Switch/Switch.js

@@ -0,0 +1,74 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React, { Component, PropTypes } from 'react';
+import withStyles from 'isomorphic-style-loader/lib/withStyles';
+import s from './Switch.scss';
+
+class Switch extends Component {
+  static propTypes = {
+    onChange: PropTypes.func,
+    checked: PropTypes.bool,
+    checkedLabel: PropTypes.string,
+    uncheckedLabel: PropTypes.string
+  }
+
+  constructor(props) {
+    super(props)
+
+    this.state = {
+      checked: props && props.checked
+    }
+  }
+
+  handleChange(e) {
+    if (this.props.onChange) {
+      this.props.onChange(e)
+    }
+
+    this.setState({ checked: e.target.checked })
+  }
+
+  render() {
+    let renderLabel = () => {
+      if ((this.state.checked && this.props.checkedLabel) || (!this.state.checked && this.props.uncheckedLabel)) {
+        return (
+          <div className={s.label}>
+            {this.state.checked ? this.props.checkedLabel : this.props.uncheckedLabel}
+          </div>
+        )
+      }
+      return null
+    }
+
+    return (
+      <div className={s.root}>
+        <input
+          type="checkbox"
+          className={s.input}
+          checked={this.state.checked}
+          className="ios-switch tinyswitch"
+          onChange={this.handleChange.bind(this)}
+        />
+        <div><div></div></div>
+        {renderLabel()}
+      </div>
+    )
+  }
+}
+
+export default withStyles(Switch, s);

+ 66 - 0
src/components/Switch/Switch.scss

@@ -0,0 +1,66 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+@import '../variables.scss';
+
+.root {
+  display: flex;
+  align-items: center;
+  position: relative;
+
+    .label {
+      margin-left: 16px;
+      color: $gray-dark;
+      font-weight: $weight-light;
+    }
+
+    input[type="checkbox"] {
+      width: 32px;
+      height: 16px;
+      cursor: pointer;
+    }
+
+    input[type="checkbox"]:global(.tinyswitch.ios-switch) + div {
+      width: 30px;	
+      height: 14px;
+      background-color: $task-gray-light;
+      border: 1px solid $task-gray-light;
+    }
+
+    input[type="checkbox"]:global(.ios-switch):checked + div {
+      background-color: $blue;
+      border: 1px solid $blue;
+      box-shadow: none;
+    }
+
+    input[type="checkbox"]:global(.ios-switch) + div { 
+      transition-duration: 0.2s;
+    }
+    
+    input[type="checkbox"]:global(.ios-switch) + div > div {
+      transition-timing-function: ease;
+      transition-duration: 0.2s;
+    }
+
+    input[type="checkbox"]:global(.tinyswitch.ios-switch) + div > div {
+      height: 14px;
+      width: 14px;
+      margin-left: 0px;
+      margin-top: 0px;
+      box-shadow: none;
+    }
+}

+ 6 - 0
src/components/Switch/package.json

@@ -0,0 +1,6 @@
+{
+    "name": "Switch",
+    "version": "0.0.0",
+    "private": true,
+    "main": "./Switch.js"
+}

+ 55 - 12
src/components/WizardOptions/WizardOptions.js

@@ -16,14 +16,17 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
 import React, { PropTypes } from 'react';
 import React, { PropTypes } from 'react';
+import ReactTooltip from 'react-tooltip'
 import Reflux from 'reflux';
 import Reflux from 'reflux';
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './WizardOptions.scss';
 import s from './WizardOptions.scss';
 import Dropdown from '../NewDropdown';
 import Dropdown from '../NewDropdown';
+import Helper from '../Helper'
 import WizardActions from '../../actions/WizardActions';
 import WizardActions from '../../actions/WizardActions';
 import WizardStore from '../../stores/WizardStore';
 import WizardStore from '../../stores/WizardStore';
 import NotificationActions from '../../actions/NotificationActions';
 import NotificationActions from '../../actions/NotificationActions';
-
+import Switch from '../Switch'
+import InfoIcon from '../InfoIcon'
 
 
 const title = 'Migration Options';
 const title = 'Migration Options';
 
 
@@ -121,10 +124,17 @@ class WizardOptions extends Reflux.Component {
     WizardActions.updateWizardState({ showAdvancedOptions: showAdvancedOptions })
     WizardActions.updateWizardState({ showAdvancedOptions: showAdvancedOptions })
   }
   }
 
 
+  handleAdvancedOptionsSwitch(e) {
+    this.setState({ showAdvancedOptions: e.target.checked })
+    WizardActions.updateWizardState({ showAdvancedOptions: e.target.checked })
+  }
+
   handleOptionsFieldChange(e, field) {
   handleOptionsFieldChange(e, field) {
     let destinationEnvironment = this.state.destination_environment
     let destinationEnvironment = this.state.destination_environment
-    if (field.type == 'dropdown') {
+    if (field.type === 'dropdown') {
       destinationEnvironment[field.name] = e.value
       destinationEnvironment[field.name] = e.value
+    } else if (field.type === 'switch') {
+      destinationEnvironment[field.name] = e.target.checked
     } else {
     } else {
       destinationEnvironment[field.name] = e.target.value
       destinationEnvironment[field.name] = e.target.value
     }
     }
@@ -150,7 +160,10 @@ class WizardOptions extends Reflux.Component {
             className={"form-group " + extraClasses}
             className={"form-group " + extraClasses}
             key={"cloudField_" + field.name}
             key={"cloudField_" + field.name}
           >
           >
-            <h3>{field.label + (field.required ? " *" : "")}</h3>
+            <div className="input-label">
+              {field.label + (field.required ? " *" : "")}
+              <InfoIcon text={Helper.getCloudFieldDescription(field.name)} />
+            </div>
             <input
             <input
               type="text"
               type="text"
               placeholder={field.label + (field.required ? " *" : "")}
               placeholder={field.label + (field.required ? " *" : "")}
@@ -166,7 +179,10 @@ class WizardOptions extends Reflux.Component {
             className={"form-group " + extraClasses}
             className={"form-group " + extraClasses}
             key={"cloudField_" + field.name}
             key={"cloudField_" + field.name}
           >
           >
-            <h3>{field.label + (field.required ? " *" : "")}</h3>
+            <div className="input-label">{
+              field.label + (field.required ? " *" : "")}
+              <InfoIcon text={Helper.getCloudFieldDescription(field.name)} />
+            </div>
             <input
             <input
               type="password"
               type="password"
               placeholder={field.label + (field.required ? " *" : "")}
               placeholder={field.label + (field.required ? " *" : "")}
@@ -176,6 +192,25 @@ class WizardOptions extends Reflux.Component {
           </div>
           </div>
         )
         )
         break;
         break;
+      case 'switch':
+        returnValue = (
+          <div
+            className={"form-group " + extraClasses}
+            key={"cloudField_" + field.name}
+          >
+            <div className="input-label">
+              {field.label + (field.required ? " *" : "")}
+              <InfoIcon text={Helper.getCloudFieldDescription(field.name)} />
+            </div>
+            <Switch
+              checked={this.state.destination_environment[field.name] === true}
+              onChange={(e) => this.handleOptionsFieldChange(e, field)}
+              checkedLabel="Yes"
+              uncheckedLabel="No"
+            />
+          </div>
+        )
+        break
       case "dropdown":
       case "dropdown":
         let value = this.state.destination_environment[field.name]
         let value = this.state.destination_environment[field.name]
 
 
@@ -188,7 +223,10 @@ class WizardOptions extends Reflux.Component {
             className={"form-group " + extraClasses}
             className={"form-group " + extraClasses}
             key={"cloudField_" + field.name}
             key={"cloudField_" + field.name}
           >
           >
-            <h3>{field.label + (field.required ? " *" : "")}</h3>
+            <div className="input-label">
+              {field.label + (field.required ? " *" : "")}
+              <InfoIcon text={Helper.getCloudFieldDescription(field.name)} />
+            </div>
             <Dropdown
             <Dropdown
               options={field.options}
               options={field.options}
               onChange={(e) => this.handleOptionsFieldChange(e, field)}
               onChange={(e) => this.handleOptionsFieldChange(e, field)}
@@ -254,21 +292,26 @@ class WizardOptions extends Reflux.Component {
   }
   }
 
 
   render() {
   render() {
-    let toggleAdvancedBtn = (<button
-      onClick={(e) => this.toggleAdvancedOptions(e)}
-      className={s.toggleAdvancedBtn + " wire"}
-    >
-      {this.state.showAdvancedOptions ? "Hide" : "Show"} Advanced Options
-    </button>)
+    let toggleAdvancedSwitch = (
+      <div className={s.toggleAdvancedSwitch}>
+        <div className={s.toggleAdvancedLabel}>Simple</div>
+        <Switch
+          checked={this.state.showAdvancedOptions}
+          onChange={this.handleAdvancedOptionsSwitch.bind(this)}
+        />
+        <div className={s.toggleAdvancedLabel}>Advanced</div>
+      </div>
+    )
 
 
     return (
     return (
       <div className={s.root}>
       <div className={s.root}>
         <div className={s.container}>
         <div className={s.container}>
+          {toggleAdvancedSwitch}
           <div className={s.containerCenter}>
           <div className={s.containerCenter}>
             {this.renderOptionsFields(this.state.targetCloud.cloudRef["import_" + this.state.migrationType].fields)}
             {this.renderOptionsFields(this.state.targetCloud.cloudRef["import_" + this.state.migrationType].fields)}
           </div>
           </div>
-          {toggleAdvancedBtn}
         </div>
         </div>
+        <ReactTooltip place="right" effect="solid" className="reactTooltip" />
       </div>
       </div>
     );
     );
   }
   }

+ 21 - 35
src/components/WizardOptions/WizardOptions.scss

@@ -50,46 +50,37 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
     margin-left: 16px;
     margin-left: 16px;
   }
   }
 }
 }
-.containerLeft {
-  width: 272px;
-  float: left;
-}
 .containerCenter {
 .containerCenter {
-  width: 800px;
+  width: $wizard-content-width;
   margin: 0 auto;
   margin: 0 auto;
 }
 }
-.containerRight {
-  width: 272px;
-  float: right;
-  :global(.Dropdown-root) {
-    width: 100%;
-  }
-}
-.containerRight, .containerLeft {
-  &:after {
-    display: block;
-    height: 0;
-    clear: both;
-    content: " ";
-  }
-}
 
 
-.toggleAdvancedBtn {
-  clear: both;
-  width: 240px;
-  margin: 24px auto 0;
-  display: block;
+.toggleAdvancedSwitch {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 47px;
+
+  .toggleAdvancedLabel {
+    font-weight: $weight-light;
+    color: $gray-dark;
+    margin: 0 16px;
+  }
 }
 }
 
 
 .optionsFieldsContainer {
 .optionsFieldsContainer {
+  display: flex;
+  flex-wrap: wrap;
+  margin-left: -304px;
+
   :global {
   :global {
     .form-group {
     .form-group {
-      float: left;
-      width: 40%;
       display: none;
       display: none;
-      &:nth-child(2n) {
-        float: right
-      }
+      min-width: 208px;
+      max-width: 208px;
+      flex-grow: 1;
+      margin-left: 304px;
+      margin-bottom: 32px;
       &.required, &.showAdvanced {
       &.required, &.showAdvanced {
         display: block;
         display: block;
       }
       }
@@ -103,9 +94,4 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
       }
       }
     }
     }
   }
   }
-  &:after {
-    content: " ";
-    display: block;
-    clear: both;
-  }
 }
 }

+ 1 - 13
src/components/WizardSummary/WizardSummary.js

@@ -61,24 +61,12 @@ class WizardSummary extends Component {
     return fields.map(field => (
     return fields.map(field => (
       <div className={s.row} key={"destination_environment_" + field.label}>
       <div className={s.row} key={"destination_environment_" + field.label}>
         <span>{field.label}</span>
         <span>{field.label}</span>
-        <span>{field.value}</span>
+        <span>{field.value.toString()}</span>
       </div>
       </div>
     ))
     ))
   }
   }
 
 
   render() {
   render() {
-    let schedules = this.props.summary.schedules.map((item, index) => (
-        <div className="item" key={"schedule_" + index}>
-          <span className="cell">
-            {this.dateTypes.indexOf(item.type) == -1 ? item.type : moment(item.date).format("MMM Do YYYY")}
-          </span>
-          <span className="cell">
-            {item.type != "Execute Now" &&
-              (item.hour.label + ":" + item.minute.label + " " + item.tod + " " + item.timezone)}
-          </span>
-        </div>
-      ), this)
-
     let instances = this.props.summary.selectedInstances.map((vm, index) => (
     let instances = this.props.summary.selectedInstances.map((vm, index) => (
       <div className="item" key={"VM_" + index}>
       <div className="item" key={"VM_" + index}>
         <span className="cell">
         <span className="cell">

+ 1 - 1
src/constants/CloudLabels.js

@@ -26,7 +26,7 @@ export const defaultLabels = {
   user_domain_name: "User Domain Name",
   user_domain_name: "User Domain Name",
   project_name: "Project Name",
   project_name: "Project Name",
   project_domain_name: "Project Domain Name",
   project_domain_name: "Project Domain Name",
-  flavor_name: "Flavor Name",
+  flavor_name: { label: "Flavor Name", description: "Let Coriolis store a flavor name" },
   hypervisor_type: "Hypervisor Type",
   hypervisor_type: "Hypervisor Type",
   container_format: "Container Format",
   container_format: "Container Format",
   disk_format: "Disk Format",
   disk_format: "Disk Format",

+ 1 - 7
src/stores/ConnectionsStore/ConnectionsStore.js

@@ -271,13 +271,7 @@ class ConnectionsStore extends Reflux.Store
       }
       }
       switch (cloudData.properties[propName].type) {
       switch (cloudData.properties[propName].type) {
         case "boolean":
         case "boolean":
-          // Values need to be strings, due to a limitation in react-dropdown
-          field.options = [
-            {label: 'Yes', value: 'true'}, 
-            {label: 'No', value: 'false'}
-          ]
-
-          field.default = (field.default === 'true' || field.default === true) ? 'true' : 'false'
+          field.type = "switch"
           break
           break
 
 
         case "string":
         case "string":

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio