Parcourir la source

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 il y a 8 ans
Parent
commit
b0a0b5a11f

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

@@ -416,6 +416,25 @@ button {
 :global(.Dropdown-option.is-selected) {
   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) {
   p {
     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"] {
   height: 32px;
   border-radius: 4px;
@@ -803,7 +830,7 @@ input[type="text"], input[type="password"] {
   font-size: 14px;
   color: $gray-dark;
   padding: 0 8px 0 16px;
-  font-weight: $weight-regular;
+  font-weight: $weight-light;
   transition: all $animation-swift-out;
   box-sizing: border-box;
   &:hover {

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

@@ -42,8 +42,12 @@ class Helper extends Component {
     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) {

+ 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 withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './InfoIcon.scss';
-import ReactTooltip from 'react-tooltip'
 
 class InfoIcon extends Component {
   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() {
+    let className = s.root + ' ' + (this.props.className || '')
+    let icon = this.props.text ? <div className={s.icon} data-tip={this.props.text} /> : null
+
     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>
-    );
+    )
   }
-
 }
 
 export default withStyles(InfoIcon, s);

Fichier diff supprimé car celui-ci est trop grand
+ 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 ReactTooltip from 'react-tooltip'
 import Reflux from 'reflux';
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './WizardOptions.scss';
 import Dropdown from '../NewDropdown';
+import Helper from '../Helper'
 import WizardActions from '../../actions/WizardActions';
 import WizardStore from '../../stores/WizardStore';
 import NotificationActions from '../../actions/NotificationActions';
-
+import Switch from '../Switch'
+import InfoIcon from '../InfoIcon'
 
 const title = 'Migration Options';
 
@@ -121,10 +124,17 @@ class WizardOptions extends Reflux.Component {
     WizardActions.updateWizardState({ showAdvancedOptions: showAdvancedOptions })
   }
 
+  handleAdvancedOptionsSwitch(e) {
+    this.setState({ showAdvancedOptions: e.target.checked })
+    WizardActions.updateWizardState({ showAdvancedOptions: e.target.checked })
+  }
+
   handleOptionsFieldChange(e, field) {
     let destinationEnvironment = this.state.destination_environment
-    if (field.type == 'dropdown') {
+    if (field.type === 'dropdown') {
       destinationEnvironment[field.name] = e.value
+    } else if (field.type === 'switch') {
+      destinationEnvironment[field.name] = e.target.checked
     } else {
       destinationEnvironment[field.name] = e.target.value
     }
@@ -150,7 +160,10 @@ class WizardOptions extends Reflux.Component {
             className={"form-group " + extraClasses}
             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
               type="text"
               placeholder={field.label + (field.required ? " *" : "")}
@@ -166,7 +179,10 @@ class WizardOptions extends Reflux.Component {
             className={"form-group " + extraClasses}
             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
               type="password"
               placeholder={field.label + (field.required ? " *" : "")}
@@ -176,6 +192,25 @@ class WizardOptions extends Reflux.Component {
           </div>
         )
         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":
         let value = this.state.destination_environment[field.name]
 
@@ -188,7 +223,10 @@ class WizardOptions extends Reflux.Component {
             className={"form-group " + extraClasses}
             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
               options={field.options}
               onChange={(e) => this.handleOptionsFieldChange(e, field)}
@@ -254,21 +292,26 @@ class WizardOptions extends Reflux.Component {
   }
 
   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 (
       <div className={s.root}>
         <div className={s.container}>
+          {toggleAdvancedSwitch}
           <div className={s.containerCenter}>
             {this.renderOptionsFields(this.state.targetCloud.cloudRef["import_" + this.state.migrationType].fields)}
           </div>
-          {toggleAdvancedBtn}
         </div>
+        <ReactTooltip place="right" effect="solid" className="reactTooltip" />
       </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;
   }
 }
-.containerLeft {
-  width: 272px;
-  float: left;
-}
 .containerCenter {
-  width: 800px;
+  width: $wizard-content-width;
   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 {
+  display: flex;
+  flex-wrap: wrap;
+  margin-left: -304px;
+
   :global {
     .form-group {
-      float: left;
-      width: 40%;
       display: none;
-      &:nth-child(2n) {
-        float: right
-      }
+      min-width: 208px;
+      max-width: 208px;
+      flex-grow: 1;
+      margin-left: 304px;
+      margin-bottom: 32px;
       &.required, &.showAdvanced {
         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 => (
       <div className={s.row} key={"destination_environment_" + field.label}>
         <span>{field.label}</span>
-        <span>{field.value}</span>
+        <span>{field.value.toString()}</span>
       </div>
     ))
   }
 
   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) => (
       <div className="item" key={"VM_" + index}>
         <span className="cell">

+ 1 - 1
src/constants/CloudLabels.js

@@ -26,7 +26,7 @@ export const defaultLabels = {
   user_domain_name: "User Domain Name",
   project_name: "Project 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",
   container_format: "Container 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) {
         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
 
         case "string":

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff