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

Redo Azure Plugin layout into a single page

Fix modal scroll related issues on component state updates.
Sergiu Miclea 8 лет назад
Родитель
Сommit
1cb715403f

+ 12 - 6
src/components/molecules/Modal/Modal.jsx

@@ -69,8 +69,14 @@ class NewModal extends React.Component {
     window.removeEventListener('resize', this.positionModal, true)
   }
 
-  handleChildUpdate() {
-    setTimeout(this.positionModal, 100)
+  handleChildUpdate(scrollableRef) {
+    if (scrollableRef) {
+      this.scrollableRef = scrollableRef
+    }
+
+    setTimeout(() => {
+      this.positionModal()
+    }, 100)
   }
 
   handleModalOpen() {
@@ -95,8 +101,8 @@ class NewModal extends React.Component {
     if (!contentNode) {
       return
     }
-
-    let scrollTop = contentNode.scrollTop
+    let scrollableNode = this.scrollableRef || contentNode
+    let scrollTop = scrollableNode.scrollTop
     contentNode.style.height = 'auto'
     let left = (pageNode.offsetWidth / 2) - (contentNode.offsetWidth / 2)
     let top = (pageNode.offsetHeight / 2) - (contentNode.offsetHeight / 2)
@@ -111,7 +117,7 @@ class NewModal extends React.Component {
     contentNode.style.top = `${top}px`
     contentNode.style.height = height
     contentNode.style.opacity = 1
-    contentNode.scrollTo(0, scrollTop)
+    scrollableNode.scrollTo(0, scrollTop)
   }
 
   renderTitle() {
@@ -157,7 +163,7 @@ class NewModal extends React.Component {
 
     let children = React.Children.map(this.props.children,
       child => React.cloneElement(child, {
-        onResizeUpdate: () => { this.handleChildUpdate() },
+        onResizeUpdate: scrollableRef => { this.handleChildUpdate(scrollableRef) },
       })
     )
 

+ 34 - 2
src/components/organisms/Endpoint/Endpoint.jsx

@@ -23,6 +23,8 @@ import {
   CopyButton,
   Tooltip,
   StatusImage,
+  Button,
+  LoadingButton,
 } from 'components'
 import NotificationActions from '../../../actions/NotificationActions'
 import EndpointStore from '../../../stores/EndpointStore'
@@ -45,6 +47,7 @@ const Status = styled.div`
   display: flex;
   flex-direction: column;
   align-items: center;
+  flex-shrink: 0;
 `
 const StatusHeader = styled.div`
   display: flex;
@@ -90,6 +93,13 @@ const LoadingText = styled.div`
   font-size: 18px;
   margin-top: 32px;
 `
+const Buttons = styled.div`
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  margin-top: 32px;
+  flex-shrink: 0;
+`
 
 class Endpoint extends React.Component {
   static propTypes = {
@@ -159,7 +169,7 @@ class Endpoint extends React.Component {
       })
     }
 
-    this.props.onResizeUpdate()
+    this.props.onResizeUpdate(this.scrollableRef)
   }
 
   componentWillUnmount() {
@@ -300,6 +310,26 @@ class Endpoint extends React.Component {
     )
   }
 
+  renderButtons() {
+    let actionButton = <Button large onClick={() => this.handleValidateClick()}>Validate and save</Button>
+
+    let message = 'Validating Endpoint ...'
+    if (this.state.validating || (this.props.endpointStore.validation && this.props.endpointStore.validation.valid)) {
+      if (this.props.endpointStore.validation && this.props.endpointStore.validation.valid) {
+        message = 'Saving ...'
+      }
+
+      actionButton = <LoadingButton large>{message}</LoadingButton>
+    }
+
+    return (
+      <Buttons>
+        <Button large secondary onClick={() => { this.handleCancelClick() }}>{this.props.cancelButtonText}</Button>
+        {actionButton}
+      </Buttons>
+    )
+  }
+
   renderContent() {
     if (this.props.providerStore.connectionSchemaLoading) {
       return null
@@ -321,9 +351,11 @@ class Endpoint extends React.Component {
           handleFieldsChange: fields => { this.handleFieldsChange(fields) },
           handleValidateClick: () => { this.handleValidateClick() },
           handleCancelClick: () => { this.handleCancelClick() },
+          scrollableRef: ref => { this.scrollableRef = ref },
           onRef: ref => { this.contentPluginRef = ref },
-          onResizeUpdate: () => { this.props.onResizeUpdate() },
+          onResizeUpdate: () => { this.props.onResizeUpdate(this.scrollableRef) },
         })}
+        {this.renderButtons()}
         <Tooltip />
         {Tooltip.rebuild()}
       </Content>

+ 88 - 204
src/plugins/endpoint/azure/ContentPlugin.jsx

@@ -16,62 +16,30 @@ import React from 'react'
 import styled from 'styled-components'
 import PropTypes from 'prop-types'
 
-import NotificationActions from '../../../actions/NotificationActions'
+import { TextArea } from 'components'
 import Palette from '../../../components/styleUtils/Palette'
 import StyleProps from '../../../components/styleUtils/StyleProps'
-import {
-  Button,
-  LoadingButton,
-  TextArea,
-  RadioInput,
-} from '../../../components'
-import { Wrapper, Fields, FieldStyled, Buttons, Row } from '../default/ContentPlugin'
+import { Wrapper, Fields, FieldStyled, Row } from '../default/ContentPlugin'
 
 const RadioGroup = styled.div`
   width: 100%;
 `
-const CloudProfile = styled.div``
-const ConfigLabel = styled.div`
-  font-size: 11px;
-  color: ${Palette.grayscale[3]};
-  margin-top: -10px;
-`
-const CustomConfigWrapper = styled.div`
-  width: 100%;
+const PasteWrapper = styled.div``
+const PasteLabel = styled.div`
   display: flex;
-  flex-direction: column;
-  min-height: 0;
-`
-const CustomConfigTitle = styled.div`
-  text-align: center;
-`
-const CustomInputType = styled.div`
-  margin-top: 32px;
-  > div {
-    margin-bottom: 16px;
-  }
-`
-const PasteField = styled.div`
-  margin-top: 32px;
-  overflow: auto;
-`
-const PasteFieldLabel = styled.div`
   font-size: 10px;
+`
+const PasteLabelText = styled.div`
   font-weight: ${StyleProps.fontWeights.medium};
   color: ${Palette.grayscale[3]};
   text-transform: uppercase;
   margin-bottom: 4px;
 `
-const PasteFieldInput = styled.div``
-
-const Pages = {
-  main: 'main',
-  custom: 'custom',
-}
-const CustomTypes = {
-  manual: 'manual',
-  json: 'json',
-}
+const PasteLabelShowMore = styled.div`
+  color: ${Palette.primary};
+  margin-left: 5px;
+  cursor: pointer;
+`
 
 const fieldNameMapper = {
   activeDirectory: 'active_directory_url',
@@ -107,15 +75,15 @@ class ContentPlugin extends React.Component {
     highlightRequired: PropTypes.func,
     onRef: PropTypes.func,
     onResizeUpdate: PropTypes.func,
+    scrollableRef: PropTypes.func,
   }
 
   constructor() {
     super()
 
     this.state = {
-      currentPage: Pages.main,
-      customType: CustomTypes.json,
       jsonConfig: '',
+      showPasteInput: false,
     }
   }
 
@@ -124,8 +92,9 @@ class ContentPlugin extends React.Component {
   }
 
   componentDidUpdate(prevProps, prevState) {
-    if (prevState.customType !== this.state.customType || prevState.currentPage !== this.state.currentPage) {
-      this.props.onResizeUpdate()
+    if (this.cloudProfileChanged || prevState.showPasteInput !== this.state.showPasteInput) {
+      this.props.onResizeUpdate(this.fieldsRef)
+      this.cloudProfileChanged = false
     }
   }
 
@@ -133,14 +102,6 @@ class ContentPlugin extends React.Component {
     this.props.onRef(undefined)
   }
 
-  handleNextClick() {
-    if (!this.props.highlightRequired()) {
-      this.setState({ currentPage: Pages.custom })
-    } else {
-      NotificationActions.notify('Please fill all the required fields', 'error')
-    }
-  }
-
   findInvalidFields = () => {
     let invalidFields = []
     const find = fields => {
@@ -159,7 +120,7 @@ class ContentPlugin extends React.Component {
     let selectedLoginTypeField = loginTypeField.items.find(f => f.name === this.props.getFieldValue(loginTypeField))
     find(selectedLoginTypeField.fields)
 
-    if (this.state.currentPage === Pages.custom) {
+    if (this.props.getFieldValue({ name: 'cloud_profile' }) === 'CustomCloud') {
       let customCloudFields = this.props.connectionInfoSchema.find(f => f.name === 'cloud_profile').custom_cloud_fields
       find(customCloudFields)
     }
@@ -167,12 +128,15 @@ class ContentPlugin extends React.Component {
     return invalidFields
   }
 
-  handleJsonConfigChange(value) {
-    this.setState({ jsonConfig: value })
+  handleJsonConfigBlur() {
+    if (this.lastBlurValue && this.lastBlurValue === this.state.jsonConfig) {
+      return
+    }
+    this.lastBlurValue = this.state.jsonConfig
 
     let json
     try {
-      json = JSON.parse(value)
+      json = JSON.parse(this.state.jsonConfig)
     } catch (e) {
       return
     }
@@ -196,15 +160,11 @@ class ContentPlugin extends React.Component {
     this.props.handleFieldsChange(updatedFields)
   }
 
-  handleJsonPaste() {
-    if (this.pasteTimeout) {
-      clearTimeout(this.pasteTimeout)
-      this.pasteTimeout = null
+  handleFieldChange(field, value) {
+    if (field.name === 'cloud_profile') {
+      this.cloudProfileChanged = true
     }
-
-    this.pasteTimeout = setTimeout(() => {
-      this.setState({ customType: CustomTypes.manual })
-    }, 1000)
+    this.props.handleFieldChange(field, value)
   }
 
   renderField(field, customProps) {
@@ -217,13 +177,13 @@ class ContentPlugin extends React.Component {
         password={field.name === 'password'}
         highlight={this.props.invalidFields.findIndex(fn => fn === field.name) > -1}
         value={this.props.getFieldValue(field)}
-        onChange={value => { this.props.handleFieldChange(field, value) }}
+        onChange={value => { this.handleFieldChange(field, value) }}
         {...customProps}
       />
     )
   }
 
-  renderFieldGroup(fields) {
+  renderFieldRows(fields) {
     const rows = []
     let lastField
     fields.forEach((field, i) => {
@@ -235,6 +195,12 @@ class ContentPlugin extends React.Component {
             {currentField}
           </Row>
         ))
+      } else if (i === fields.length - 1) {
+        rows.push((
+          <Row key={field.name}>
+            {currentField}
+          </Row>
+        ))
       }
       lastField = currentField
     })
@@ -242,154 +208,74 @@ class ContentPlugin extends React.Component {
     return rows
   }
 
-  renderCustomPage() {
-    if (this.state.currentPage !== Pages.custom) {
-      return null
-    }
-
-    let fields = null
-
-    if (this.state.customType === CustomTypes.manual) {
-      fields = (
-        <Fields>
-          {this.renderFieldGroup(this.props.connectionInfoSchema.find(f => f.name === 'cloud_profile').custom_cloud_fields)}
-        </Fields>
-      )
-    } else {
-      fields = (
-        <PasteField>
-          <PasteFieldLabel>Azure Stack Profile Configuration JSON</PasteFieldLabel>
-          <PasteFieldInput>
-            <TextArea
-              width="100%"
-              height="164px"
-              placeholder="Paste JSON output here"
-              value={this.state.jsonConfig}
-              onChange={e => { this.handleJsonConfigChange(e.target.value) }}
-              onPaste={() => { this.handleJsonPaste() }}
-            />
-          </PasteFieldInput>
-        </PasteField>
-      )
-    }
-
-    let title = <CustomConfigTitle>Azure Stack Additional Configuration</CustomConfigTitle>
-    if (this.props.validating || this.props.validation) {
-      title = null
-    }
+  renderPasteField() {
+    const textArea = (
+      <TextArea
+        width="100%"
+        height="96px"
+        placeholder="Use the Azure CLI to get the details of a registered cloud and paste it here"
+        value={this.state.jsonConfig}
+        onBlur={() => { this.handleJsonConfigBlur() }}
+        onChange={e => { this.setState({ jsonConfig: e.target.value }) }}
+        disabled={this.props.disabled}
+      />
+    )
 
     return (
-      <CustomConfigWrapper>
-        {title}
-        <CustomInputType>
-          <RadioInput
-            checked={this.state.customType === CustomTypes.json}
-            label="Paste Configuration"
-            onChange={e => { if (e.target.checked) this.setState({ customType: CustomTypes.json }) }}
-          />
-          <RadioInput
-            checked={this.state.customType === CustomTypes.manual}
-            label="Manual Input"
-            onChange={e => { if (e.target.checked) this.setState({ customType: CustomTypes.manual }) }}
-          />
-        </CustomInputType>
-        {fields}
-      </CustomConfigWrapper>
+      <PasteWrapper>
+        <PasteLabel>
+          <PasteLabelText>Paste Configuration (optional)</PasteLabelText>
+          <PasteLabelShowMore
+            onClick={() => { this.setState({ showPasteInput: !this.state.showPasteInput }) }}
+          >{this.state.showPasteInput ? 'Hide' : 'Show'}</PasteLabelShowMore>
+        </PasteLabel>
+        {this.state.showPasteInput ? textArea : null}
+      </PasteWrapper>
     )
   }
 
-  renderMainPage() {
-    if (this.state.currentPage === Pages.custom) {
-      return null
-    }
-
+  renderFields() {
     const fields = this.props.connectionInfoSchema
+    const cloudProfileField = fields.find(f => f.name === 'cloud_profile')
+    const loginTypeField = fields.find(f => f.name === 'login_type')
+    const allowUntrustedField = loginTypeField.items.find(f => f.name === this.props.getFieldValue(loginTypeField)).fields.find(f => f.name === 'allow_untrusted')
+
+    let renderedFields = this.renderFieldRows(fields.filter(f => f.name !== loginTypeField.name && f.name !== cloudProfileField.name))
+
+    const radioGroupRow = (
+      <Row key="radio-group-row">
+        <RadioGroup key="radio-group">
+          {loginTypeField.items.map(field =>
+            this.renderField(field, {
+              value: this.props.getFieldValue(loginTypeField) === field.name,
+              onChange: value => { if (value) this.props.handleFieldChange(loginTypeField, field.name) },
+            })
+          )}
+        </RadioGroup>
+        {this.renderField(allowUntrustedField)}
+      </Row>
+    )
 
-    let renderedFields = this.renderFieldGroup(fields.filter(f => f.name !== 'login_type' && f.name !== 'cloud_profile'))
-
-    let loginTypeField = fields.find(f => f.name === 'login_type')
-
-    renderedFields.push((
-      <RadioGroup key="radio-group">
-        {loginTypeField.items.map(field =>
-          this.renderField(field, {
-            value: this.props.getFieldValue(loginTypeField) === field.name,
-            onChange: value => { if (value) this.props.handleFieldChange(loginTypeField, field.name) },
-          })
-        )}
-      </RadioGroup>
+    renderedFields.push(radioGroupRow)
+    renderedFields = renderedFields.concat(this.renderFieldRows(
+      loginTypeField.items.find(f => f.name === this.props.getFieldValue(loginTypeField)).fields
+        .filter(f => f.name !== allowUntrustedField.name)
+        .concat([cloudProfileField])
     ))
 
-    renderedFields = renderedFields.concat(this.renderFieldGroup(loginTypeField.items.find(f => f.name === this.props.getFieldValue(loginTypeField)).fields))
-
-    const cloudProfileWrapper = (
-      <CloudProfile key="cloudProfile">
-        {this.renderField(fields.find(f => f.name === 'cloud_profile'))}
-        {this.renderAdditionalConfigLabel()}
-      </CloudProfile>
-    )
-    renderedFields.push(cloudProfileWrapper)
+    const isCustomCloud = this.props.getFieldValue(cloudProfileField) === 'CustomCloud'
+    if (isCustomCloud) {
+      renderedFields = renderedFields.concat(this.renderFieldRows(cloudProfileField.custom_cloud_fields))
+    }
 
     return (
-      <Fields>
+      <Fields innerRef={ref => { this.props.scrollableRef(ref) }}>
         {renderedFields}
+        {isCustomCloud ? this.renderPasteField() : null}
       </Fields>
     )
   }
 
-  renderAdditionalConfigLabel() {
-    const fields = this.props.connectionInfoSchema
-
-    if (fields.length === 0 ||
-      this.props.getFieldValue(this.props.connectionInfoSchema.find(f => f.name === 'cloud_profile')) !== 'CustomCloud') {
-      return null
-    }
-
-    return <ConfigLabel>* Additional configuration required</ConfigLabel>
-  }
-
-  renderActionButton() {
-    let cloudProfileField = this.props.connectionInfoSchema.find(f => f.name === 'cloud_profile')
-
-    if (this.props.getFieldValue(cloudProfileField) === 'CustomCloud' && this.state.currentPage === Pages.main) {
-      return <Button large onClick={() => this.handleNextClick()}>Next</Button>
-    }
-
-    let actionButton = <Button large onClick={() => this.props.handleValidateClick()}>Validate and save</Button>
-
-    let message = 'Validating Endpoint ...'
-    if (this.props.validating || (this.props.validation && this.props.validation.valid)) {
-      if (this.props.validation && this.props.validation.valid) {
-        message = 'Saving ...'
-      }
-
-      actionButton = <LoadingButton large>{message}</LoadingButton>
-    }
-
-    return actionButton
-  }
-
-  renderCancelButton() {
-    let cancelButton
-
-    if (this.state.currentPage === Pages.main) {
-      cancelButton = <Button large secondary onClick={() => { this.props.handleCancelClick() }}>{this.props.cancelButtonText}</Button>
-    } else {
-      cancelButton = <Button large secondary onClick={() => { this.setState({ currentPage: Pages.main }) }}>Back</Button>
-    }
-
-    return cancelButton
-  }
-
-  renderButtons() {
-    return (
-      <Buttons>
-        {this.renderCancelButton()}
-        {this.renderActionButton()}
-      </Buttons>
-    )
-  }
-
   render() {
     const fields = this.props.connectionInfoSchema
     if (fields.length === 0) {
@@ -398,9 +284,7 @@ class ContentPlugin extends React.Component {
 
     return (
       <Wrapper>
-        {this.renderMainPage()}
-        {this.renderCustomPage()}
-        {this.renderButtons()}
+        {this.renderFields()}
       </Wrapper>
     )
   }

+ 7 - 29
src/plugins/endpoint/default/ContentPlugin.jsx

@@ -16,7 +16,7 @@ import React from 'react'
 import styled from 'styled-components'
 import PropTypes from 'prop-types'
 
-import { EndpointField, Button, LoadingButton } from '../../../components'
+import { EndpointField } from '../../../components'
 
 export const Wrapper = styled.div`
   display: flex;
@@ -34,13 +34,6 @@ export const FieldStyled = styled(EndpointField)`
   max-width: 224px;
   margin-bottom: 16px;
 `
-export const Buttons = styled.div`
-  display: flex;
-  justify-content: space-between;
-  width: 100%;
-  margin-top: 32px;
-  flex-shrink: 0;
-`
 export const Row = styled.div`
   display: flex;
   flex-shrink: 0;
@@ -104,6 +97,12 @@ class ContentPlugin extends React.Component {
             {currentField}
           </Row>
         ))
+      } else if (i === this.props.connectionInfoSchema.length - 1) {
+        rows.push((
+          <Row key={field.name}>
+            {currentField}
+          </Row>
+        ))
       }
       lastField = currentField
     })
@@ -115,31 +114,10 @@ class ContentPlugin extends React.Component {
     )
   }
 
-  renderButtons() {
-    let actionButton = <Button large onClick={() => this.props.handleValidateClick()}>Validate and save</Button>
-
-    let message = 'Validating Endpoint ...'
-    if (this.props.validating || (this.props.validation && this.props.validation.valid)) {
-      if (this.props.validation && this.props.validation.valid) {
-        message = 'Saving ...'
-      }
-
-      actionButton = <LoadingButton large>{message}</LoadingButton>
-    }
-
-    return (
-      <Buttons>
-        <Button large secondary onClick={() => { this.props.handleCancelClick() }}>{this.props.cancelButtonText}</Button>
-        {actionButton}
-      </Buttons>
-    )
-  }
-
   render() {
     return (
       <Wrapper>
         {this.renderFields()}
-        {this.renderButtons()}
       </Wrapper>
     )
   }