diff options
Diffstat (limited to 'openecomp-ui/src/nfvo-components')
41 files changed, 3470 insertions, 2419 deletions
diff --git a/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx index c15cd1d0e8..56382d6325 100644 --- a/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx +++ b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx @@ -13,11 +13,11 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import ListGroupItem from 'react-bootstrap/lib/ListGroupItem.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; -import {Collapse} from 'react-bootstrap'; +import { Collapse } from 'react-bootstrap'; /** * parsing and showing the following Java Response object * @@ -42,121 +42,151 @@ import {Collapse} from 'react-bootstrap'; } */ class SubmitErrorResponse extends Component { + render() { + let { + validationResponse: { + vspErrors, + licensingDataErrors, + questionnaireValidationResult, + uploadDataErrors + } + } = this.props; + return ( + <div className="submit-error-response-view"> + {vspErrors && this.renderVspErrors(vspErrors)} + {licensingDataErrors && + this.renderVspErrors(licensingDataErrors)} + {questionnaireValidationResult && + this.renderComponentsErrors(questionnaireValidationResult)} + {uploadDataErrors && + this.renderUploadDataErrors(uploadDataErrors)} + </div> + ); + } + renderVspErrors(errors) { + return ( + <ErrorBlock errorType={i18n('VSP Errors')}> + <div> + {errors.length && + errors.map((error, i) => { + return ( + <ErrorMessage key={i} error={error.message} /> + ); + })} + </div> + </ErrorBlock> + ); + } - render() { - let {validationResponse : {vspErrors, licensingDataErrors, questionnaireValidationResult, uploadDataErrors}} = this.props; - return ( - <div className='submit-error-response-view'> - {vspErrors && this.renderVspErrors(vspErrors)} - {licensingDataErrors && this.renderVspErrors(licensingDataErrors)} - {questionnaireValidationResult && this.renderComponentsErrors(questionnaireValidationResult)} - {uploadDataErrors && this.renderUploadDataErrors(uploadDataErrors)} - </div> - ); - } - - renderVspErrors(errors) { - return ( - <ErrorBlock errorType={i18n('VSP Errors')}> - <div> - {errors.length && errors.map((error, i) => {return (<ErrorMessage key={i} error={error.message}/>);})} - </div> - </ErrorBlock> - ); - } - - - renderComponentsErrors(errors) { - return ( - <ErrorBlock errorType={i18n('Components Errors')}> - <div> - {errors.validationData.length && errors.validationData.map((item, i) =>{ return (<ComponentError key={i} item={item}/>);})} - </div> - </ErrorBlock> - ); - } + renderComponentsErrors(errors) { + return ( + <ErrorBlock errorType={i18n('Components Errors')}> + <div> + {errors.validationData.length && + errors.validationData.map((item, i) => { + return <ComponentError key={i} item={item} />; + })} + </div> + </ErrorBlock> + ); + } - renderUploadDataErrors(uploadDataErrors) { - return ( - <ErrorBlock errorType={i18n('Upload Data Errors')}> - <div> - <UploadErrorList items={uploadDataErrors}/> - </div> - </ErrorBlock> - ); - } + renderUploadDataErrors(uploadDataErrors) { + return ( + <ErrorBlock errorType={i18n('Upload Data Errors')}> + <div> + <UploadErrorList items={uploadDataErrors} /> + </div> + </ErrorBlock> + ); + } } - -const ComponentError = ({item}) => { - return ( - <div> - <div className='component-name-header'>{item.entityName}</div> - {item.errors.map((error, i) => {return(<ErrorMessage key={i} error={error}/>);})} - </div> - ); +const ComponentError = ({ item }) => { + return ( + <div> + <div className="component-name-header">{item.entityName}</div> + {item.errors.map((error, i) => { + return <ErrorMessage key={i} error={error} />; + })} + </div> + ); }; function* entries(obj) { - for (let key of Object.keys(obj)) { - yield {header: key, list: obj[key]}; - } + for (let key of Object.keys(obj)) { + yield { header: key, list: obj[key] }; + } } -const UploadErrorList = ({items}) => { - let generator = entries(items); - - let errors = []; - for (let item of generator) {errors.push( - <div key={item.header}> - <div className='component-name-header'>{item.header}</div> - {item.list.map((error, i) => <ErrorMessage key={i} warning={error.level === 'WARNING'} error={error.message}/> )} - </div> - );} +const UploadErrorList = ({ items }) => { + let generator = entries(items); + + let errors = []; + for (let item of generator) { + errors.push( + <div key={item.header}> + <div className="component-name-header">{item.header}</div> + {item.list.map((error, i) => ( + <ErrorMessage + key={i} + warning={error.level === 'WARNING'} + error={error.message} + /> + ))} + </div> + ); + } - return ( - <div> - {errors} - </div> - ); + return <div>{errors}</div>; }; class ErrorBlock extends React.Component { - state = { - collapsed: false - }; - - render() { - let {errorType, children} = this.props; - return ( - <div className='error-block'> - <ErrorHeader collapsed={this.state.collapsed} onClick={()=>{this.setState({collapsed: !this.state.collapsed});}} errorType={errorType}/> - <Collapse in={this.state.collapsed}> - {children} - </Collapse> - </div> - ); - } + state = { + collapsed: false + }; + + render() { + let { errorType, children } = this.props; + return ( + <div className="error-block"> + <ErrorHeader + collapsed={this.state.collapsed} + onClick={() => { + this.setState({ collapsed: !this.state.collapsed }); + }} + errorType={errorType} + /> + <Collapse in={this.state.collapsed}>{children}</Collapse> + </div> + ); + } } -const ErrorHeader = ({errorType, collapsed, onClick}) => { - return( - <div onClick={onClick} className='error-block-header'> - <SVGIcon iconClassName={collapsed ? '' : 'collapse-right' } name='chevronDown' label={errorType} labelPosition='right'/> - </div> - ); +const ErrorHeader = ({ errorType, collapsed, onClick }) => { + return ( + <div onClick={onClick} className="error-block-header"> + <SVGIcon + iconClassName={collapsed ? '' : 'collapse-right'} + name="chevronDown" + label={errorType} + labelPosition="right" + /> + </div> + ); }; -const ErrorMessage = ({error, warning}) => { - return ( - <ListGroupItem className='error-code-list-item'> - <SVGIcon - name={warning ? 'exclamationTriangleLine' : 'error'} - color={warning ? 'warning' : 'negative'} /> - <span className='icon-label'>{error}</span> - </ListGroupItem> - ); +const ErrorMessage = ({ error, warning }) => { + return ( + <ListGroupItem className="error-code-list-item"> + <SVGIcon + name={warning ? 'exclamationTriangleLine' : 'error'} + color={warning ? 'warning' : 'negative'} + /> + <span className="icon-label">{error}</span> + </ListGroupItem> + ); }; export default SubmitErrorResponse; diff --git a/openecomp-ui/src/nfvo-components/accordion/Accordion.jsx b/openecomp-ui/src/nfvo-components/accordion/Accordion.jsx index ac19072307..72f8de0d23 100644 --- a/openecomp-ui/src/nfvo-components/accordion/Accordion.jsx +++ b/openecomp-ui/src/nfvo-components/accordion/Accordion.jsx @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,35 +18,38 @@ import React from 'react'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; import PropTypes from 'prop-types'; - class Accordion extends React.Component { + static propTypes = { + title: PropTypes.string, + children: PropTypes.node + }; - static propTypes = { - title: PropTypes.string, - children: PropTypes.node - } - - constructor(props) { - super(props); - this.state = { - open: false - }; - } - render() { - const {children, title} = this.props; - const {open} = this.state; - return ( - <div className='accordion'> - <div onClick={()=>this.setState({open: !open})} className='accordion-header'> - <SVGIcon name='chevronUp' iconClassName={open ? 'down' : ''}/> - <div className='title'>{title}</div> - </div> - <div className={`accordion-body ${open ? 'open' : ''}`}> - {children} - </div> - </div> - ); - } + constructor(props) { + super(props); + this.state = { + open: false + }; + } + render() { + const { children, title } = this.props; + const { open } = this.state; + return ( + <div className="accordion"> + <div + onClick={() => this.setState({ open: !open })} + className="accordion-header"> + <SVGIcon + name="chevronUp" + iconClassName={open ? 'down' : ''} + /> + <div className="title">{title}</div> + </div> + <div className={`accordion-body ${open ? 'open' : ''}`}> + {children} + </div> + </div> + ); + } } -export default Accordion;
\ No newline at end of file +export default Accordion; diff --git a/openecomp-ui/src/nfvo-components/datepicker/Datepicker.jsx b/openecomp-ui/src/nfvo-components/datepicker/Datepicker.jsx index 25e7e7e02d..b4bc8be9ec 100644 --- a/openecomp-ui/src/nfvo-components/datepicker/Datepicker.jsx +++ b/openecomp-ui/src/nfvo-components/datepicker/Datepicker.jsx @@ -5,72 +5,105 @@ import moment from 'moment'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; class CustomInput extends React.Component { + static propTypes = { + placeHolderText: PropTypes.string, + onChange: PropTypes.func, + onClick: PropTypes.func, + value: PropTypes.string + }; - static propTypes = { - placeHolderText: PropTypes.string, - onChange: PropTypes.func, - onClick: PropTypes.func, - value: PropTypes.string - }; - - render() { - const {placeholderText, onClick, onClear, inputRef, value: date} = this.props; - const text = date ? date : placeholderText; - const textStyle = date ? '' : 'placeholder'; - return ( - <div onClick={onClick} ref={inputRef} className='datepicker-custom-input'> - <div className={`datepicker-text ${textStyle}`}>{text}</div> - {date && <SVGIcon onClick={e => {e.stopPropagation(); onClear();}} name='close' className='clear-input'/>} - <SVGIcon name='calendar'/> - </div> - ); - } -}; + render() { + const { + placeholderText, + onClick, + onClear, + inputRef, + value: date + } = this.props; + const text = date ? date : placeholderText; + const textStyle = date ? '' : 'placeholder'; + return ( + <div + onClick={onClick} + ref={inputRef} + className="datepicker-custom-input"> + <div className={`datepicker-text ${textStyle}`}>{text}</div> + {date && ( + <SVGIcon + onClick={e => { + e.stopPropagation(); + onClear(); + }} + name="close" + className="clear-input" + /> + )} + <SVGIcon name="calendar" /> + </div> + ); + } +} const parseDate = (date, format) => { - return typeof date === 'number' ? moment.unix(date) : moment(date, format); + return typeof date === 'number' ? moment.unix(date) : moment(date, format); }; class Datepicker extends React.Component { - static propTypes = { - date: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - format: PropTypes.string, - onChange: PropTypes.func, - selectsStart: PropTypes.bool, - selectsEnd: PropTypes.bool, - startDate: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - disabled: PropTypes.bool, - label: PropTypes.string, - isRequired: PropTypes.bool - } - render() { - let {date, format, onChange, selectsStart = false, startDate = null, endDate = null, selectsEnd = false, - disabled = false, inputRef} = this.props; - const placeholderText = 'Enter a date'; - const props = { - format, - onChange, - disabled, - selected: date ? parseDate(date, format) : date, - selectsStart, - selectsEnd, - placeholderText, - startDate: startDate ? parseDate(startDate, format) : startDate, - endDate: endDate ? parseDate(endDate, format) : endDate - }; + static propTypes = { + date: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + format: PropTypes.string, + onChange: PropTypes.func, + selectsStart: PropTypes.bool, + selectsEnd: PropTypes.bool, + startDate: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + disabled: PropTypes.bool, + label: PropTypes.string, + isRequired: PropTypes.bool + }; + render() { + let { + date, + format, + onChange, + selectsStart = false, + startDate = null, + endDate = null, + selectsEnd = false, + disabled = false, + inputRef + } = this.props; + const placeholderText = 'Enter a date'; + const props = { + format, + onChange, + disabled, + selected: date ? parseDate(date, format) : date, + selectsStart, + selectsEnd, + placeholderText, + startDate: startDate ? parseDate(startDate, format) : startDate, + endDate: endDate ? parseDate(endDate, format) : endDate + }; - return ( - <div className='customized-date-picker'> - <DatePicker - calendarClassName='customized-date-picker-calendar' - customInput={<CustomInput inputRef={inputRef} onClear={() => onChange(undefined)} placeholderText={placeholderText}/>} - minDate={selectsEnd && props.startDate} - maxDate={selectsStart && props.endDate} - {...props}/> - </div> - ); - } + return ( + <div className="customized-date-picker"> + <DatePicker + calendarClassName="customized-date-picker-calendar" + customInput={ + <CustomInput + inputRef={inputRef} + onClear={() => onChange(undefined)} + placeholderText={placeholderText} + /> + } + minDate={selectsEnd && props.startDate} + maxDate={selectsStart && props.endDate} + {...props} + /> + </div> + ); + } } export default Datepicker; diff --git a/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx index 3c9ceed0d8..9fcd7042bc 100644 --- a/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx +++ b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx @@ -20,79 +20,106 @@ import VersionController from 'nfvo-components/panel/versionController/VersionCo import NavigationSideBar from 'nfvo-components/panel/NavigationSideBar.jsx'; export default class TabulatedEditor extends React.Component { + render() { + const { + navigationBarProps, + onToggle, + onVersionSwitching, + onMoreVersionsClick, + onCreate, + onSave, + onClose, + onVersionControllerAction, + onNavigate, + children, + meta, + onManagePermissions, + onOpenCommentCommitModal, + onOpenPermissions, + onOpenRevisionsModal + } = this.props; + let { versionControllerProps } = this.props; + const { className = '' } = React.Children.only(children).props; + const child = this.prepareChild(); - render() { - const {navigationBarProps, onToggle, onVersionSwitching, onMoreVersionsClick, onCreate, onSave, onClose, - onVersionControllerAction, onNavigate, children, meta, onManagePermissions, onOpenCommentCommitModal, onOpenPermissions, onOpenRevisionsModal} = this.props; - let {versionControllerProps} = this.props; - const {className = ''} = React.Children.only(children).props; - const child = this.prepareChild(); + if (onClose) { + versionControllerProps = { + ...versionControllerProps, + onClose: () => onClose(versionControllerProps) + }; + } + return ( + <div className="software-product-view"> + <div className="software-product-navigation-side-bar"> + <NavigationSideBar + {...navigationBarProps} + onSelect={onNavigate} + onToggle={onToggle} + /> + </div> + <div className="software-product-landing-view-right-side flex-column"> + <VersionController + {...versionControllerProps} + onVersionSwitching={version => + onVersionSwitching(version, meta) + } + onMoreVersionsClick={onMoreVersionsClick} + onManagePermissions={onManagePermissions} + onOpenCommentCommitModal={onOpenCommentCommitModal} + onOpenPermissions={onOpenPermissions} + onOpenRevisionsModal={onOpenRevisionsModal} + callVCAction={(action, version, comment) => + onVersionControllerAction( + action, + version, + comment, + meta + ) + } + onCreate={onCreate && this.handleCreate} + onSave={onSave && this.handleSave} + /> + <div className={classnames('content-area', `${className}`)}> + {child} + </div> + </div> + </div> + ); + } - if(onClose) { - versionControllerProps = { - ...versionControllerProps, - onClose: () => onClose(versionControllerProps) - }; - } - return ( - <div className='software-product-view'> - <div className='software-product-navigation-side-bar'> - <NavigationSideBar {...navigationBarProps} onSelect={onNavigate} onToggle={onToggle}/> - </div> - <div className='software-product-landing-view-right-side flex-column'> - <VersionController - {...versionControllerProps} - onVersionSwitching={version => onVersionSwitching(version, meta)} - onMoreVersionsClick={onMoreVersionsClick} - onManagePermissions={onManagePermissions} - onOpenCommentCommitModal={onOpenCommentCommitModal} - onOpenPermissions={onOpenPermissions} - onOpenRevisionsModal={onOpenRevisionsModal} - callVCAction={(action, version, comment) => onVersionControllerAction(action, version, comment, meta)} - onCreate={onCreate && this.handleCreate} - onSave={onSave && this.handleSave}/> - <div className={classnames('content-area', `${className}`)}> - { - child - } - </div> - </div> - </div> - ); - } + prepareChild() { + const { onSave, onCreate, children } = this.props; - prepareChild() { - const {onSave, onCreate, children} = this.props; + const additionalChildProps = { ref: 'editor' }; + if (onSave) { + additionalChildProps.onSave = onSave; + } + if (onCreate) { + additionalChildProps.onCreate = onCreate; + } - const additionalChildProps = {ref: 'editor'}; - if (onSave) { - additionalChildProps.onSave = onSave; - } - if (onCreate) { - additionalChildProps.onCreate = onCreate; - } + const child = React.cloneElement( + React.Children.only(children), + additionalChildProps + ); + return child; + } - const child = React.cloneElement(React.Children.only(children), additionalChildProps); - return child; - } + handleSave = () => { + const childInstance = this.refs.editor.getWrappedInstance(); + if (childInstance.save) { + return childInstance.save(); + } else { + return this.props.onSave(); + } + }; - - - handleSave = () => { - const childInstance = this.refs.editor.getWrappedInstance(); - if (childInstance.save) { - return childInstance.save(); - } else { - return this.props.onSave(); - } - }; - - handleCreate = () => { - const childInstance = this.refs.editor.getWrappedInstance(); - if (childInstance.create) { - childInstance.create(); - } else { - this.props.onCreate(); - } - } + handleCreate = () => { + const childInstance = this.refs.editor.getWrappedInstance(); + if (childInstance.create) { + childInstance.create(); + } else { + this.props.onCreate(); + } + }; } diff --git a/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx b/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx index 5b4e0a8bee..0d47d859d9 100644 --- a/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx +++ b/openecomp-ui/src/nfvo-components/fileupload/DraggableUploadFileBox.jsx @@ -26,20 +26,33 @@ * or * https://github.com/JedWatson/react-select */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import i18n from 'nfvo-utils/i18n/i18n.js'; import Button from 'sdc-ui/lib/react/Button.js'; class DraggableUploadFileBox extends Component { - render() { - let {className, onClick, dataTestId, isReadOnlyMode} = this.props; - return ( - <div className={`file-upload-box ${className} ${isReadOnlyMode ? 'disabled' : ''}`}> - <div className={`drag-text ${isReadOnlyMode ? 'disabled' : ''}`}>{i18n('Drag & drop for upload')}</div> - <div className='or-text'>{i18n('or')}</div> - <Button type='button' data-test-id={dataTestId} btnType='outline' onClick={onClick} disabled={isReadOnlyMode}>{i18n('Select File')}</Button> - </div> - ); - } + render() { + let { className, onClick, dataTestId, isReadOnlyMode } = this.props; + return ( + <div + className={`file-upload-box ${className} ${ + isReadOnlyMode ? 'disabled' : '' + }`}> + <div + className={`drag-text ${isReadOnlyMode ? 'disabled' : ''}`}> + {i18n('Drag & drop for upload')} + </div> + <div className="or-text">{i18n('or')}</div> + <Button + type="button" + data-test-id={dataTestId} + btnType="outline" + onClick={onClick} + disabled={isReadOnlyMode}> + {i18n('Select File')} + </Button> + </div> + ); + } } export default DraggableUploadFileBox; diff --git a/openecomp-ui/src/nfvo-components/grid/GridItem.jsx b/openecomp-ui/src/nfvo-components/grid/GridItem.jsx index c62e042bf4..9723cde2c0 100644 --- a/openecomp-ui/src/nfvo-components/grid/GridItem.jsx +++ b/openecomp-ui/src/nfvo-components/grid/GridItem.jsx @@ -15,12 +15,21 @@ */ import React from 'react'; -const GridItem = ({colSpan = 1, children, lastColInRow = false, stretch = false, className = ''}) => ( - <div className={`grid-col-${colSpan} ${lastColInRow ? 'last-col-in-row' : ''} ${className}`}> - <div className={`grid-item${stretch ? '-stretch' : ''}`}> - {children} - </div> - </div> +const GridItem = ({ + colSpan = 1, + children, + lastColInRow = false, + stretch = false, + className = '' +}) => ( + <div + className={`grid-col-${colSpan} ${ + lastColInRow ? 'last-col-in-row' : '' + } ${className}`}> + <div className={`grid-item${stretch ? '-stretch' : ''}`}> + {children} + </div> + </div> ); export default GridItem; diff --git a/openecomp-ui/src/nfvo-components/grid/GridSection.jsx b/openecomp-ui/src/nfvo-components/grid/GridSection.jsx index 8f4a024fdb..f2e3588d9e 100644 --- a/openecomp-ui/src/nfvo-components/grid/GridSection.jsx +++ b/openecomp-ui/src/nfvo-components/grid/GridSection.jsx @@ -17,21 +17,32 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -const GridSection = ({title, children, className = '', titleClassName, hasLastColSet = false}) => { - return ( - <div className={classnames('grid-section', className, {'has-last-col-set': hasLastColSet})}> - {title && <div className={`section-title ${titleClassName || ''}`}>{title}</div>} - <div className='grid-items'> - {children} - </div> - </div> - ); +const GridSection = ({ + title, + children, + className = '', + titleClassName, + hasLastColSet = false +}) => { + return ( + <div + className={classnames('grid-section', className, { + 'has-last-col-set': hasLastColSet + })}> + {title && ( + <div className={`section-title ${titleClassName || ''}`}> + {title} + </div> + )} + <div className="grid-items">{children}</div> + </div> + ); }; GridSection.propTypes = { - title: PropTypes.string, - titleClassName: PropTypes.string, - hasLastColSet: PropTypes.bool + title: PropTypes.string, + titleClassName: PropTypes.string, + hasLastColSet: PropTypes.bool }; export default GridSection; diff --git a/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx index 82fbe1deed..3973ae8c5d 100644 --- a/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx +++ b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx @@ -19,99 +19,118 @@ import ReactDOM from 'react-dom'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; import Input from 'nfvo-components/input/validation/InputWrapper.jsx'; -const ExpandableInputClosed = ({iconType, onClick}) => ( - <SVGIcon className='expandable-input-wrapper closed' data-test-id='expandable-input-closed' name={iconType} onClick={onClick} /> +const ExpandableInputClosed = ({ iconType, onClick }) => ( + <SVGIcon + className="expandable-input-wrapper closed" + data-test-id="expandable-input-closed" + name={iconType} + onClick={onClick} + /> ); class ExpandableInputOpened extends React.Component { - componentDidMount(){ - this.rawDomNode = ReactDOM.findDOMNode(this.searchInputNode.inputWrapper); - this.rawDomNode.focus(); - } + componentDidMount() { + this.rawDomNode = ReactDOM.findDOMNode( + this.searchInputNode.inputWrapper + ); + this.rawDomNode.focus(); + } - componentWillReceiveProps(newProps){ - if (!newProps.value){ - if (!(document.activeElement === this.rawDomNode)){ - this.props.handleBlur(); - } - } - } + componentWillReceiveProps(newProps) { + if (!newProps.value) { + if (!(document.activeElement === this.rawDomNode)) { + this.props.handleBlur(); + } + } + } - handleClose(){ - this.props.onChange(''); - this.rawDomNode.focus(); - } + handleClose() { + this.props.onChange(''); + this.rawDomNode.focus(); + } - handleKeyDown(e){ - if (e.key === 'Escape'){ - e.preventDefault(); - if (this.props.value) { - this.handleClose(); - } else { - this.rawDomNode.blur(); - } - }; - } + handleKeyDown(e) { + if (e.key === 'Escape') { + e.preventDefault(); + if (this.props.value) { + this.handleClose(); + } else { + this.rawDomNode.blur(); + } + } + } - render() { - let {iconType, value, onChange, handleBlur} = this.props; - return ( - <div className='expandable-input-wrapper opened' key='expandable'> - <Input - type='text' - data-test-id='expandable-input-opened' - value={value} - ref={(input) => this.searchInputNode = input} - className='expandable-active' - groupClassName='expandable-input-control' - onChange={e => onChange(e)} - onKeyDown={e => this.handleKeyDown(e)} - onBlur={handleBlur}/> - {value && <SVGIcon data-test-id='expandable-input-close-btn' onClick={() => this.handleClose()} name='close' />} - {!value && <SVGIcon name={iconType} onClick={handleBlur}/>} - </div> - ); - } + render() { + let { iconType, value, onChange, handleBlur } = this.props; + return ( + <div className="expandable-input-wrapper opened" key="expandable"> + <Input + type="text" + data-test-id="expandable-input-opened" + value={value} + ref={input => (this.searchInputNode = input)} + className="expandable-active" + groupClassName="expandable-input-control" + onChange={e => onChange(e)} + onKeyDown={e => this.handleKeyDown(e)} + onBlur={handleBlur} + /> + {value && ( + <SVGIcon + data-test-id="expandable-input-close-btn" + onClick={() => this.handleClose()} + name="close" + /> + )} + {!value && <SVGIcon name={iconType} onClick={handleBlur} />} + </div> + ); + } } class ExpandableInput extends React.Component { + static propTypes = { + iconType: PropTypes.string, + onChange: PropTypes.func, + value: PropTypes.string + }; - static propTypes = { - iconType: PropTypes.string, - onChange: PropTypes.func, - value: PropTypes.string - }; + state = { showInput: false }; - state = {showInput: false}; + closeInput() { + if (!this.props.value) { + this.setState({ showInput: false }); + } + } - closeInput(){ - if (!this.props.value) { - this.setState({showInput: false}); - } - } + getValue() { + return this.props.value; + } - getValue(){ - return this.props.value; - } - - render(){ - let {iconType, value, onChange = false} = this.props; - return ( - <div className='expandable-input-top'> - {this.state.showInput && - <ExpandableInputOpened - key='open' - iconType={iconType} - onChange={onChange} - value={value} - handleKeyDown={(e) => this.handleKeyDown(e)} - handleBlur={() => this.closeInput()}/> - } - {!this.state.showInput && <ExpandableInputClosed key='closed' iconType={iconType} onClick={() => this.setState({showInput: true})} />} - </div> - ); - } + render() { + let { iconType, value, onChange = false } = this.props; + return ( + <div className="expandable-input-top"> + {this.state.showInput && ( + <ExpandableInputOpened + key="open" + iconType={iconType} + onChange={onChange} + value={value} + handleKeyDown={e => this.handleKeyDown(e)} + handleBlur={() => this.closeInput()} + /> + )} + {!this.state.showInput && ( + <ExpandableInputClosed + key="closed" + iconType={iconType} + onClick={() => this.setState({ showInput: true })} + /> + )} + </div> + ); + } } - export default ExpandableInput; diff --git a/openecomp-ui/src/nfvo-components/input/SelectInput.jsx b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx index 03c727379e..b0e0d87d7c 100644 --- a/openecomp-ui/src/nfvo-components/input/SelectInput.jsx +++ b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx @@ -26,43 +26,51 @@ * or * https://github.com/JedWatson/react-select */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import Select from 'react-select'; class SelectInput extends Component { + inputValue = []; - inputValue = []; + render() { + let { label, value, ...other } = this.props; + const dataTestId = this.props['data-test-id'] + ? { 'data-test-id': this.props['data-test-id'] } + : {}; + return ( + <div + {...dataTestId} + className="validation-input-wrapper dropdown-multi-select"> + <div className="form-group"> + {label && <label className="control-label">{label}</label>} + <Select + ref="_myInput" + onChange={value => this.onSelectChanged(value)} + {...other} + value={value} + /> + </div> + </div> + ); + } - render() { - let {label, value, ...other} = this.props; - const dataTestId = this.props['data-test-id'] ? {'data-test-id': this.props['data-test-id']} : {}; - return ( - <div {...dataTestId} className='validation-input-wrapper dropdown-multi-select'> - <div className='form-group'> - {label && <label className='control-label'>{label}</label>} - <Select ref='_myInput' onChange={value => this.onSelectChanged(value)} {...other} value={value} /> - </div> - </div> - ); - } + getValue() { + return this.inputValue && this.inputValue.length ? this.inputValue : ''; + } - getValue() { - return this.inputValue && this.inputValue.length ? this.inputValue : ''; - } + onSelectChanged(value) { + this.props.onMultiSelectChanged(value); + } - onSelectChanged(value) { - this.props.onMultiSelectChanged(value); - } - - componentDidMount() { - let {value} = this.props; - this.inputValue = value ? value : []; - } - componentDidUpdate() { - if (this.inputValue !== this.props.value) { - this.inputValue = this.props.value; - } - } + componentDidMount() { + let { value } = this.props; + this.inputValue = value ? value : []; + } + componentDidUpdate() { + if (this.inputValue !== this.props.value) { + this.inputValue = this.props.value; + } + } } export default SelectInput; diff --git a/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx index 31a8a66d86..947570fa29 100644 --- a/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx +++ b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx @@ -16,55 +16,60 @@ import React from 'react'; import PropTypes from 'prop-types'; -export default -class ToggleInput extends React.Component { +export default class ToggleInput extends React.Component { + static propTypes = { + label: PropTypes.node, + value: PropTypes.bool, + onChange: PropTypes.func, + disabled: PropTypes.bool + }; - static propTypes = { - label: PropTypes.node, - value: PropTypes.bool, - onChange: PropTypes.func, - disabled: PropTypes.bool - } + static defaultProps = { + value: false, + label: '' + }; - static defaultProps = { - value: false, - label: '' - } + state = { + value: this.props.value + }; - state = { - value: this.props.value - } + status() { + return this.state.value ? 'on' : 'off'; + } - status() { - return this.state.value ? 'on' : 'off'; - } + render() { + let { label, disabled } = this.props; + let checked = this.status() === 'on'; + //TODO check onclick + return ( + <div + className="toggle-input-wrapper form-group" + onClick={!disabled && this.click}> + <div className="toggle-input-label">{label}</div> + <div className="toggle-switch"> + <input + className="toggle toggle-round-flat" + type="checkbox" + checked={checked} + readOnly + /> + <label /> + </div> + </div> + ); + } - render() { - let {label, disabled} = this.props; - let checked = this.status() === 'on'; - //TODO check onclick - return ( - <div className='toggle-input-wrapper form-group' onClick={!disabled && this.click}> - <div className='toggle-input-label'>{label}</div> - <div className='toggle-switch'> - <input className='toggle toggle-round-flat' type='checkbox' checked={checked} readOnly/> - <label></label> - </div> - </div> - ); - } + click = () => { + let value = !this.state.value; + this.setState({ value }); - click = () => { - let value = !this.state.value; - this.setState({value}); + let onChange = this.props.onChange; + if (onChange) { + onChange(value); + } + }; - let onChange = this.props.onChange; - if (onChange) { - onChange(value); - } - } - - getValue() { - return this.state.value; - } + getValue() { + return this.state.value; + } } diff --git a/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx index a689c50778..7ab4c8242c 100644 --- a/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx +++ b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx @@ -19,136 +19,224 @@ import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; import Input from 'nfvo-components/input/validation/InputWrapper.jsx'; class DualListboxView extends React.Component { - - static propTypes = { - - availableList: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - })), - filterTitle: PropTypes.shape({ - left: PropTypes.string, - right: PropTypes.string - }), - selectedValuesList: PropTypes.arrayOf(PropTypes.string), - - onChange: PropTypes.func.isRequired - }; - - static defaultProps = { - selectedValuesList: [], - availableList: [], - filterTitle: { - left: '', - right: '' - } - }; - - state = { - availableListFilter: '', - selectedValuesListFilter: '', - selectedValues: [] - }; - - render() { - let {availableList, selectedValuesList, filterTitle, isReadOnlyMode} = this.props; - let {availableListFilter, selectedValuesListFilter} = this.state; - - let unselectedList = availableList.filter(availableItem => !selectedValuesList.find(value => value === availableItem.id)); - let selectedList = availableList.filter(availableItem => selectedValuesList.find(value => value === availableItem.id)); - selectedList = selectedList.sort((a, b) => selectedValuesList.indexOf(a.id) - selectedValuesList.indexOf(b.id)); - return ( - <div className='dual-list-box'> - {this.renderListbox(filterTitle.left, unselectedList, { - value: availableListFilter, - ref: 'availableListFilter', - disabled: isReadOnlyMode, - onChange: (value) => this.setState({availableListFilter: value}) - }, {ref: 'availableValues', disabled: isReadOnlyMode, testId: 'available',})} - {this.renderOperationsBar(isReadOnlyMode)} - {this.renderListbox(filterTitle.right, selectedList, { - value: selectedValuesListFilter, - ref: 'selectedValuesListFilter', - disabled: isReadOnlyMode, - onChange: (value) => this.setState({selectedValuesListFilter: value}) - }, {ref: 'selectedValues', disabled: isReadOnlyMode, testId: 'selected'})} - </div> - ); - } - - renderListbox(filterTitle, list, filterProps, props) { - let regExFilter = new RegExp(escape(filterProps.value), 'i'); - let matchedItems = list.filter(item => item.name.match(regExFilter)); - let unMatchedItems = list.filter(item => !item.name.match(regExFilter)); - return ( - <div className='dual-search-multi-select-section'> - <p>{filterTitle}</p> - <div className='dual-text-box-search search-wrapper'> - <Input data-test-id={`${props.testId}-search-input`} - name='search-input-control' type='text' - groupClassName='search-input-control' - {...filterProps}/> - <SVGIcon name='search' className='search-icon'/> - </div> - <Input - multiple - onChange={(event) => this.onSelectItems(event.target.selectedOptions)} - groupClassName='dual-list-box-multi-select' - type='select' - name='dual-list-box-multi-select' - data-test-id={`${props.testId}-select-input`} - disabled={props.disabled} - ref={props.ref}> - {matchedItems.map(item => this.renderOption(item.id, item.name))} - {matchedItems.length && unMatchedItems.length && <option style={{pointerEvents: 'none'}}>--------------------</option>} - {unMatchedItems.map(item => this.renderOption(item.id, item.name))} - </Input> - </div> - ); - } - - onSelectItems(selectedOptions) { - let selectedValues = Object.keys(selectedOptions).map((k) => selectedOptions[k].value); - this.setState({selectedValues}); - } - - renderOption(value, name) { - return (<option className='dual-list-box-multi-select-text' key={value} value={value}>{name}</option>); - } - - renderOperationsBar(isReadOnlyMode) { - return ( - <div className={`dual-list-options-bar${isReadOnlyMode ? ' disabled' : ''}`}> - {this.renderOperationBarButton(() => this.addToSelectedList(), 'angleRight')} - {this.renderOperationBarButton(() => this.removeFromSelectedList(), 'angleLeft')} - {this.renderOperationBarButton(() => this.addAllToSelectedList(), 'angleDoubleRight')} - {this.renderOperationBarButton(() => this.removeAllFromSelectedList(), 'angleDoubleLeft')} - </div> - ); - } - - renderOperationBarButton(onClick, iconName){ - return (<div className='dual-list-option' data-test-id={`operation-icon-${iconName}`} onClick={onClick}><SVGIcon name={iconName}/></div>); - } - - addToSelectedList() { - this.props.onChange(this.props.selectedValuesList.concat(this.state.selectedValues)); - this.setState({selectedValues: []}); - } - - removeFromSelectedList() { - const selectedValues = this.state.selectedValues; - this.props.onChange(this.props.selectedValuesList.filter(value => !selectedValues.find(selectedValue => selectedValue === value))); - this.setState({selectedValues: []}); - } - - addAllToSelectedList() { - this.props.onChange(this.props.availableList.map(item => item.id)); - } - - removeAllFromSelectedList() { - this.props.onChange([]); - } + static propTypes = { + availableList: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }) + ), + filterTitle: PropTypes.shape({ + left: PropTypes.string, + right: PropTypes.string + }), + selectedValuesList: PropTypes.arrayOf(PropTypes.string), + + onChange: PropTypes.func.isRequired + }; + + static defaultProps = { + selectedValuesList: [], + availableList: [], + filterTitle: { + left: '', + right: '' + } + }; + + state = { + availableListFilter: '', + selectedValuesListFilter: '', + selectedValues: [] + }; + + render() { + let { + availableList, + selectedValuesList, + filterTitle, + isReadOnlyMode + } = this.props; + let { availableListFilter, selectedValuesListFilter } = this.state; + + let unselectedList = availableList.filter( + availableItem => + !selectedValuesList.find(value => value === availableItem.id) + ); + let selectedList = availableList.filter(availableItem => + selectedValuesList.find(value => value === availableItem.id) + ); + selectedList = selectedList.sort( + (a, b) => + selectedValuesList.indexOf(a.id) - + selectedValuesList.indexOf(b.id) + ); + return ( + <div className="dual-list-box"> + {this.renderListbox( + filterTitle.left, + unselectedList, + { + value: availableListFilter, + ref: 'availableListFilter', + disabled: isReadOnlyMode, + onChange: value => + this.setState({ availableListFilter: value }) + }, + { + ref: 'availableValues', + disabled: isReadOnlyMode, + testId: 'available' + } + )} + {this.renderOperationsBar(isReadOnlyMode)} + {this.renderListbox( + filterTitle.right, + selectedList, + { + value: selectedValuesListFilter, + ref: 'selectedValuesListFilter', + disabled: isReadOnlyMode, + onChange: value => + this.setState({ selectedValuesListFilter: value }) + }, + { + ref: 'selectedValues', + disabled: isReadOnlyMode, + testId: 'selected' + } + )} + </div> + ); + } + + renderListbox(filterTitle, list, filterProps, props) { + let regExFilter = new RegExp(escape(filterProps.value), 'i'); + let matchedItems = list.filter(item => item.name.match(regExFilter)); + let unMatchedItems = list.filter(item => !item.name.match(regExFilter)); + return ( + <div className="dual-search-multi-select-section"> + <p>{filterTitle}</p> + <div className="dual-text-box-search search-wrapper"> + <Input + data-test-id={`${props.testId}-search-input`} + name="search-input-control" + type="text" + groupClassName="search-input-control" + {...filterProps} + /> + <SVGIcon name="search" className="search-icon" /> + </div> + <Input + multiple + onChange={event => + this.onSelectItems(event.target.selectedOptions) + } + groupClassName="dual-list-box-multi-select" + type="select" + name="dual-list-box-multi-select" + data-test-id={`${props.testId}-select-input`} + disabled={props.disabled} + ref={props.ref}> + {matchedItems.map(item => + this.renderOption(item.id, item.name) + )} + {matchedItems.length && + unMatchedItems.length && ( + <option style={{ pointerEvents: 'none' }}> + -------------------- + </option> + )} + {unMatchedItems.map(item => + this.renderOption(item.id, item.name) + )} + </Input> + </div> + ); + } + + onSelectItems(selectedOptions) { + let selectedValues = Object.keys(selectedOptions).map( + k => selectedOptions[k].value + ); + this.setState({ selectedValues }); + } + + renderOption(value, name) { + return ( + <option + className="dual-list-box-multi-select-text" + key={value} + value={value}> + {name} + </option> + ); + } + + renderOperationsBar(isReadOnlyMode) { + return ( + <div + className={`dual-list-options-bar${ + isReadOnlyMode ? ' disabled' : '' + }`}> + {this.renderOperationBarButton( + () => this.addToSelectedList(), + 'angleRight' + )} + {this.renderOperationBarButton( + () => this.removeFromSelectedList(), + 'angleLeft' + )} + {this.renderOperationBarButton( + () => this.addAllToSelectedList(), + 'angleDoubleRight' + )} + {this.renderOperationBarButton( + () => this.removeAllFromSelectedList(), + 'angleDoubleLeft' + )} + </div> + ); + } + + renderOperationBarButton(onClick, iconName) { + return ( + <div + className="dual-list-option" + data-test-id={`operation-icon-${iconName}`} + onClick={onClick}> + <SVGIcon name={iconName} /> + </div> + ); + } + + addToSelectedList() { + this.props.onChange( + this.props.selectedValuesList.concat(this.state.selectedValues) + ); + this.setState({ selectedValues: [] }); + } + + removeFromSelectedList() { + const selectedValues = this.state.selectedValues; + this.props.onChange( + this.props.selectedValuesList.filter( + value => + !selectedValues.find( + selectedValue => selectedValue === value + ) + ) + ); + this.setState({ selectedValues: [] }); + } + + addAllToSelectedList() { + this.props.onChange(this.props.availableList.map(item => item.id)); + } + + removeAllFromSelectedList() { + this.props.onChange([]); + } } export default DualListboxView; diff --git a/openecomp-ui/src/nfvo-components/input/validation/Form.jsx b/openecomp-ui/src/nfvo-components/input/validation/Form.jsx index 6df0bf9009..62fc29a55c 100644 --- a/openecomp-ui/src/nfvo-components/input/validation/Form.jsx +++ b/openecomp-ui/src/nfvo-components/input/validation/Form.jsx @@ -19,130 +19,160 @@ import PropTypes from 'prop-types'; import ValidationButtons from './ValidationButtons.jsx'; class Form extends React.Component { - - static defaultProps = { - hasButtons : true, - onSubmit : null, - onReset : null, - labledButtons: true, - onValidChange : null, - isValid: true, - submitButtonText: null, - cancelButtonText: null - }; - - static propTypes = { - isValid : PropTypes.bool, - formReady : PropTypes.bool, - isReadOnlyMode : PropTypes.bool, - hasButtons : PropTypes.bool, - onSubmit : PropTypes.func, - onReset : PropTypes.func, - labledButtons: PropTypes.bool, - submitButtonText: PropTypes.string, - cancelButtonText: PropTypes.string, - onValidChange : PropTypes.func, - onValidityChanged: PropTypes.func, - onValidateForm: PropTypes.func - }; - - constructor(props) { - super(props); - } - - - render() { - // eslint-disable-next-line no-unused-vars - let {isValid, onValidChange, onValidityChanged, onDataChanged, formReady, onValidateForm, isReadOnlyMode, hasButtons, onSubmit, labledButtons, submitButtonText, - cancelButtonText, children, ...formProps} = this.props; - return ( - <form {...formProps} ref={(form) => this.form = form} onSubmit={event => this.handleFormValidation(event)}> - <div className='validation-form-content'> - <fieldset disabled={isReadOnlyMode}> - {children} - </fieldset> - </div> - {hasButtons && - <ValidationButtons - labledButtons={labledButtons} - submitButtonText={submitButtonText} - cancelButtonText={cancelButtonText} - ref={(buttons) => this.buttons = buttons} - isReadOnlyMode={isReadOnlyMode}/>} - </form> - ); - } - - handleFormValidation(event) { - event.preventDefault(); - if (this.props.onValidateForm && !this.props.formReady){ - return this.props.onValidateForm(); - } else { - return this.handleFormSubmit(event); - } - } - handleFormSubmit(event) { - if (event) { - event.preventDefault(); - } - if(this.props.onSubmit) { - return this.props.onSubmit(event); - } - } - - componentDidMount() { - if (this.props.hasButtons) { - this.buttons.setState({isValid: this.props.isValid}); - } - } - - - - componentDidUpdate(prevProps) { - // only handling this programatically if the validation of the form is done outside of the view - // (example with a form that is dependent on the state of other forms) - if (prevProps.isValid !== this.props.isValid) { - if (this.props.hasButtons) { - this.buttons.setState({isValid: this.props.isValid}); - } - // callback in case form is part of bigger picture in view - if (this.props.onValidChange) { - this.props.onValidChange(this.props.isValid); - } - - // TODO - maybe this has to be part of componentWillUpdate - if(this.props.onValidityChanged) { - this.props.onValidityChanged(this.props.isValid); - } - } - if (this.props.formReady) { // if form validation succeeded -> continue with submit - this.handleFormSubmit(); - } - } - + static defaultProps = { + hasButtons: true, + onSubmit: null, + onReset: null, + labledButtons: true, + onValidChange: null, + isValid: true, + submitButtonText: null, + cancelButtonText: null + }; + + static propTypes = { + isValid: PropTypes.bool, + formReady: PropTypes.bool, + isReadOnlyMode: PropTypes.bool, + hasButtons: PropTypes.bool, + onSubmit: PropTypes.func, + onReset: PropTypes.func, + labledButtons: PropTypes.bool, + submitButtonText: PropTypes.string, + cancelButtonText: PropTypes.string, + onValidChange: PropTypes.func, + onValidityChanged: PropTypes.func, + onValidateForm: PropTypes.func + }; + + constructor(props) { + super(props); + } + render() { + /* eslint-disable no-unused-vars */ + let { + isValid, + onValidChange, + onValidityChanged, + onDataChanged, + formReady, + onValidateForm, + isReadOnlyMode, + hasButtons, + onSubmit, + labledButtons, + submitButtonText, + cancelButtonText, + children, + ...formProps + } = this.props; + /* eslint-enable no-unused-vars */ + return ( + <form + {...formProps} + ref={form => (this.form = form)} + onSubmit={event => this.handleFormValidation(event)}> + <div className="validation-form-content"> + <fieldset disabled={isReadOnlyMode}>{children}</fieldset> + </div> + {hasButtons && ( + <ValidationButtons + labledButtons={labledButtons} + submitButtonText={submitButtonText} + cancelButtonText={cancelButtonText} + ref={buttons => (this.buttons = buttons)} + isReadOnlyMode={isReadOnlyMode} + /> + )} + </form> + ); + } + + handleFormValidation(event) { + event.preventDefault(); + if (this.props.onValidateForm && !this.props.formReady) { + return this.props.onValidateForm(); + } else { + return this.handleFormSubmit(event); + } + } + handleFormSubmit(event) { + if (event) { + event.preventDefault(); + } + if (this.props.onSubmit) { + return this.props.onSubmit(event); + } + } + + componentDidMount() { + if (this.props.hasButtons) { + this.buttons.setState({ isValid: this.props.isValid }); + } + } + + componentDidUpdate(prevProps) { + // only handling this programatically if the validation of the form is done outside of the view + // (example with a form that is dependent on the state of other forms) + if (prevProps.isValid !== this.props.isValid) { + if (this.props.hasButtons) { + this.buttons.setState({ isValid: this.props.isValid }); + } + // callback in case form is part of bigger picture in view + if (this.props.onValidChange) { + this.props.onValidChange(this.props.isValid); + } + + // TODO - maybe this has to be part of componentWillUpdate + if (this.props.onValidityChanged) { + this.props.onValidityChanged(this.props.isValid); + } + } + if (this.props.formReady) { + // if form validation succeeded -> continue with submit + this.handleFormSubmit(); + } + } } export class TabsForm extends Form { - render() { - // eslint-disable-next-line no-unused-vars - let {submitButtonText, cancelButtonText, isValid, formReady, onValidateForm, isReadOnlyMode, hasButtons, onSubmit, labledButtons, onValidChange, onValidityChanged, onDataChanged, children, - ...formProps} = this.props; - return ( - <form {...formProps} ref={(form) => this.form = form} onSubmit={event => this.handleFormValidation(event)}> - <div className='validation-form-content'> - {children} - </div> - {hasButtons && - <ValidationButtons - labledButtons={labledButtons} - submitButtonText={submitButtonText} - cancelButtonText={cancelButtonText} - ref={buttons => this.buttons = buttons} - isReadOnlyMode={isReadOnlyMode}/> - } - </form> - ); - } + render() { + /* eslint-disable no-unused-vars */ + let { + submitButtonText, + cancelButtonText, + isValid, + formReady, + onValidateForm, + isReadOnlyMode, + hasButtons, + onSubmit, + labledButtons, + onValidChange, + onValidityChanged, + onDataChanged, + children, + ...formProps + } = this.props; + /* eslint-enable no-unused-vars */ + return ( + <form + {...formProps} + ref={form => (this.form = form)} + onSubmit={event => this.handleFormValidation(event)}> + <div className="validation-form-content">{children}</div> + {hasButtons && ( + <ValidationButtons + labledButtons={labledButtons} + submitButtonText={submitButtonText} + cancelButtonText={cancelButtonText} + ref={buttons => (this.buttons = buttons)} + isReadOnlyMode={isReadOnlyMode} + /> + )} + </form> + ); + } } export default Form; diff --git a/openecomp-ui/src/nfvo-components/input/validation/Input.jsx b/openecomp-ui/src/nfvo-components/input/validation/Input.jsx index 33cea933b5..a5d6f4fd7a 100644 --- a/openecomp-ui/src/nfvo-components/input/validation/Input.jsx +++ b/openecomp-ui/src/nfvo-components/input/validation/Input.jsx @@ -25,191 +25,249 @@ import Tooltip from 'react-bootstrap/lib/Tooltip.js'; import Datepicker from 'nfvo-components/datepicker/Datepicker.jsx'; class Input extends React.Component { + state = { + value: this.props.value, + checked: this.props.checked, + selectedValues: [] + }; - state = { - value: this.props.value, - checked: this.props.checked, - selectedValues: [] - }; + render() { + /* eslint-disable no-unused-vars */ + const { + label, + isReadOnlyMode, + value, + onBlur, + onKeyDown, + type, + disabled, + checked, + name + } = this.props; + const { + groupClassName, + isValid = true, + errorText, + isRequired, + overlayPos, + ...inputProps + } = this.props; + const { + dateFormat, + startDate, + endDate, + selectsStart, + selectsEnd + } = this.props; // Date Props + /* eslint-enable no-unused-vars */ + let wrapperClassName = + type !== 'radio' + ? 'validation-input-wrapper' + : 'validation-radio-wrapper'; + if (disabled) { + wrapperClassName += ' disabled'; + } + return ( + <div className={wrapperClassName}> + <FormGroup + className={classNames('form-group', [groupClassName], { + required: isRequired, + 'has-error': !isValid + })}> + {label && + (type !== 'checkbox' && type !== 'radio') && ( + <label className="control-label">{label}</label> + )} + {type === 'text' && ( + <FormControl + bsClass={'form-control input-options-other'} + onChange={e => this.onChange(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + onBlur={onBlur} + onKeyDown={onKeyDown} + value={value || ''} + inputRef={input => (this.input = input)} + type={type} + data-test-id={this.props['data-test-id']} + /> + )} + {type === 'number' && ( + <FormControl + bsClass={'form-control input-options-other'} + onChange={e => this.onChange(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + onBlur={onBlur} + onKeyDown={onKeyDown} + value={value !== undefined ? value : ''} + inputRef={input => (this.input = input)} + type={type} + data-test-id={this.props['data-test-id']} + /> + )} - render() { - const {label, isReadOnlyMode, value, onBlur, onKeyDown, type, disabled, checked, name} = this.props; - // eslint-disable-next-line no-unused-vars - const {groupClassName, isValid = true, errorText, isRequired, overlayPos, ...inputProps} = this.props; - const {dateFormat, startDate, endDate, selectsStart, selectsEnd} = this.props; // Date Props - let wrapperClassName = (type !== 'radio') ? 'validation-input-wrapper' : 'validation-radio-wrapper'; - if (disabled) { - wrapperClassName += ' disabled'; - } - return( - <div className={wrapperClassName}> - <FormGroup className={classNames('form-group', [groupClassName], {'required' : isRequired , 'has-error' : !isValid})} > - {(label && (type !== 'checkbox' && type !== 'radio')) && <label className='control-label'>{label}</label>} - {type === 'text' && - <FormControl - bsClass={'form-control input-options-other'} - onChange={(e) => this.onChange(e)} - disabled={isReadOnlyMode || Boolean(disabled)} - onBlur={onBlur} - onKeyDown={onKeyDown} - value={value || ''} - inputRef={(input) => this.input = input} - type={type} - data-test-id={this.props['data-test-id']}/>} - {type === 'number' && - <FormControl - bsClass={'form-control input-options-other'} - onChange={(e) => this.onChange(e)} - disabled={isReadOnlyMode || Boolean(disabled)} - onBlur={onBlur} - onKeyDown={onKeyDown} - value={(value !== undefined) ? value : ''} - inputRef={(input) => this.input = input} - type={type} - data-test-id={this.props['data-test-id']}/>} + {type === 'textarea' && ( + <FormControl + className="form-control input-options-other" + disabled={isReadOnlyMode || Boolean(disabled)} + value={value || ''} + onBlur={onBlur} + onKeyDown={onKeyDown} + componentClass={type} + onChange={e => this.onChange(e)} + inputRef={input => (this.input = input)} + data-test-id={this.props['data-test-id']} + /> + )} - {type === 'textarea' && - <FormControl - className='form-control input-options-other' - disabled={isReadOnlyMode || Boolean(disabled)} - value={value || ''} - onBlur={onBlur} - onKeyDown={onKeyDown} - componentClass={type} - onChange={(e) => this.onChange(e)} - inputRef={(input) => this.input = input} - data-test-id={this.props['data-test-id']}/>} + {type === 'checkbox' && ( + <Checkbox + className={classNames({ + required: isRequired, + 'has-error': !isValid + })} + onChange={e => this.onChangeCheckBox(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + checked={checked} + data-test-id={this.props['data-test-id']}> + {label} + </Checkbox> + )} - {type === 'checkbox' && - <Checkbox - className={classNames({'required' : isRequired , 'has-error' : !isValid})} - onChange={(e)=>this.onChangeCheckBox(e)} - disabled={isReadOnlyMode || Boolean(disabled)} - checked={checked} - data-test-id={this.props['data-test-id']}>{label}</Checkbox>} + {type === 'radio' && ( + <Radio + name={name} + checked={checked} + disabled={isReadOnlyMode || Boolean(disabled)} + value={value} + onChange={isChecked => + this.onChangeRadio(isChecked) + } + inputRef={input => (this.input = input)} + label={label} + data-test-id={this.props['data-test-id']} + /> + )} + {type === 'select' && ( + <FormControl + onClick={e => this.optionSelect(e)} + className="custom-select" + componentClass={type} + inputRef={input => (this.input = input)} + name={name} + {...inputProps} + data-test-id={this.props['data-test-id']} + /> + )} + {type === 'date' && ( + <Datepicker + date={value} + format={dateFormat} + startDate={startDate} + endDate={endDate} + inputRef={input => (this.input = input)} + onChange={this.props.onChange} + disabled={isReadOnlyMode || Boolean(disabled)} + data-test-id={this.props['data-test-id']} + selectsStart={selectsStart} + selectsEnd={selectsEnd} + /> + )} + </FormGroup> + {this.renderErrorOverlay()} + </div> + ); + } - {type === 'radio' && - <Radio name={name} - checked={checked} - disabled={isReadOnlyMode || Boolean(disabled)} - value={value} - onChange={(isChecked)=>this.onChangeRadio(isChecked)} - inputRef={(input) => this.input = input} - label={label} - data-test-id={this.props['data-test-id']} />} - {type === 'select' && - <FormControl onClick={ (e) => this.optionSelect(e) } - className='custom-select' - componentClass={type} - inputRef={(input) => this.input = input} - name={name} {...inputProps} - data-test-id={this.props['data-test-id']}/>} - {type === 'date' && - <Datepicker - date={value} - format={dateFormat} - startDate={startDate} - endDate={endDate} - inputRef={(input) => this.input = input} - onChange={this.props.onChange} - disabled={isReadOnlyMode || Boolean(disabled)} - data-test-id={this.props['data-test-id']} - selectsStart={selectsStart} - selectsEnd={selectsEnd} />} - </FormGroup> - { this.renderErrorOverlay() } - </div> - ); - } + getValue() { + return this.props.type !== 'select' + ? this.state.value + : this.state.selectedValues; + } - getValue() { - return this.props.type !== 'select' ? this.state.value : this.state.selectedValues; - } + getChecked() { + return this.state.checked; + } - getChecked() { - return this.state.checked; - } + optionSelect(e) { + let selectedValues = []; + if (e.target.value) { + selectedValues.push(e.target.value); + } + this.setState({ + selectedValues + }); + } - optionSelect(e) { - let selectedValues = []; - if (e.target.value) { - selectedValues.push(e.target.value); - } - this.setState({ - selectedValues - }); - } + onChange(e) { + const { onChange, type } = this.props; + let value = e.target.value; + if (type === 'number') { + if (value === '') { + value = undefined; + } else { + value = Number(value); + } + } + this.setState({ + value + }); + onChange(value); + } - onChange(e) { - const {onChange, type} = this.props; - let value = e.target.value; - if (type === 'number') { - if (value === '') { - value = undefined; - } else { - value = Number(value); - } - } - this.setState({ - value - }); - onChange(value); - } + onChangeCheckBox(e) { + let { onChange } = this.props; + let checked = e.target.checked; + this.setState({ + checked + }); + onChange(checked); + } - onChangeCheckBox(e) { - let {onChange} = this.props; - let checked = e.target.checked; - this.setState({ - checked - }); - onChange(checked); - } + onChangeRadio(isChecked) { + let { onChange } = this.props; + this.setState({ + checked: isChecked + }); + onChange(this.state.value); + } - onChangeRadio(isChecked) { - let {onChange} = this.props; - this.setState({ - checked: isChecked - }); - onChange(this.state.value); - } + focus() { + ReactDOM.findDOMNode(this.input).focus(); + } - focus() { - ReactDOM.findDOMNode(this.input).focus(); - } + renderErrorOverlay() { + let position = 'right'; + const { errorText = '', isValid = true, type, overlayPos } = this.props; - renderErrorOverlay() { - let position = 'right'; - const {errorText = '', isValid = true, type, overlayPos} = this.props; - - if (overlayPos) { - position = overlayPos; - } - else if (type === 'text' - || type === 'email' - || type === 'number' - || type === 'radio' - || type === 'password' - || type === 'date') { - position = 'bottom'; - } - - return ( - <Overlay - show={!isValid} - placement={position} - target={() => { - let target = ReactDOM.findDOMNode(this.input); - return target.offsetParent ? target : undefined; - }} - container={this}> - <Tooltip - id={`error-${errorText.replace(' ', '-')}`} - className='validation-error-message'> - {errorText} - </Tooltip> - </Overlay> - ); - } + if (overlayPos) { + position = overlayPos; + } else if ( + type === 'text' || + type === 'email' || + type === 'number' || + type === 'radio' || + type === 'password' || + type === 'date' + ) { + position = 'bottom'; + } + return ( + <Overlay + show={!isValid} + placement={position} + target={() => { + let target = ReactDOM.findDOMNode(this.input); + return target.offsetParent ? target : undefined; + }} + container={this}> + <Tooltip + id={`error-${errorText.replace(' ', '-')}`} + className="validation-error-message"> + {errorText} + </Tooltip> + </Overlay> + ); + } } -export default Input; +export default Input; diff --git a/openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx b/openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx index 11b07ba9da..019b6a5c70 100644 --- a/openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx +++ b/openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx @@ -22,260 +22,341 @@ import Select from 'nfvo-components/input/SelectInput.jsx'; import Overlay from 'react-bootstrap/lib/Overlay.js'; import Tooltip from 'react-bootstrap/lib/Tooltip.js'; -export const other = {OTHER: 'Other'}; +export const other = { OTHER: 'Other' }; class InputOptions extends React.Component { + static propTypes = { + values: PropTypes.arrayOf( + PropTypes.shape({ + enum: PropTypes.string, + title: PropTypes.string + }) + ), + isEnabledOther: PropTypes.bool, + label: PropTypes.string, + selectedValue: PropTypes.string, + multiSelectedEnum: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.array + ]), + selectedEnum: PropTypes.string, + otherValue: PropTypes.string, + overlayPos: PropTypes.string, + onEnumChange: PropTypes.func, + onOtherChange: PropTypes.func, + onBlur: PropTypes.func, + isRequired: PropTypes.bool, + isMultiSelect: PropTypes.bool, + isValid: PropTypes.bool, + disabled: PropTypes.bool + }; - static propTypes = { - values: PropTypes.arrayOf(PropTypes.shape({ - enum: PropTypes.string, - title: PropTypes.string - })), - isEnabledOther: PropTypes.bool, - label: PropTypes.string, - selectedValue: PropTypes.string, - multiSelectedEnum: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.array - ]), - selectedEnum: PropTypes.string, - otherValue: PropTypes.string, - overlayPos: PropTypes.string, - onEnumChange: PropTypes.func, - onOtherChange: PropTypes.func, - onBlur: PropTypes.func, - isRequired: PropTypes.bool, - isMultiSelect: PropTypes.bool, - isValid: PropTypes.bool, - disabled: PropTypes.bool - }; + state = { + otherInputDisabled: !this.props.otherValue + }; - state = { - otherInputDisabled: !this.props.otherValue - }; + oldProps = { + selectedEnum: '', + otherValue: '', + multiSelectedEnum: [] + }; - oldProps = { - selectedEnum: '', - otherValue: '', - multiSelectedEnum: [] - }; + render() { + let { + label, + isRequired, + values, + otherValue, + onOtherChange, + isMultiSelect, + onBlur, + multiSelectedEnum, + selectedEnum, + isValid, + children, + isReadOnlyMode + } = this.props; + const dataTestId = this.props['data-test-id'] + ? { 'data-test-id': this.props['data-test-id'] } + : {}; + let currentMultiSelectedEnum = []; + let currentSelectedEnum = ''; + let otherInputDisabled = + (isMultiSelect && + (multiSelectedEnum === undefined || + multiSelectedEnum.length === 0 || + multiSelectedEnum[0] !== other.OTHER)) || + (!isMultiSelect && + (selectedEnum === undefined || selectedEnum !== other.OTHER)); + if (isMultiSelect) { + currentMultiSelectedEnum = multiSelectedEnum; + if (!otherInputDisabled) { + currentSelectedEnum = multiSelectedEnum + ? multiSelectedEnum.toString() + : undefined; + } + } else if (selectedEnum) { + currentSelectedEnum = selectedEnum; + } + if (!onBlur) { + onBlur = () => {}; + } - render() { - let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, isValid, children, isReadOnlyMode} = this.props; - const dataTestId = this.props['data-test-id'] ? {'data-test-id': this.props['data-test-id']} : {}; - let currentMultiSelectedEnum = []; - let currentSelectedEnum = ''; - let otherInputDisabled = (isMultiSelect && (multiSelectedEnum === undefined || multiSelectedEnum.length === 0 || multiSelectedEnum[0] !== other.OTHER)) - || (!isMultiSelect && (selectedEnum === undefined || selectedEnum !== other.OTHER)); - if (isMultiSelect) { - currentMultiSelectedEnum = multiSelectedEnum; - if(!otherInputDisabled) { - currentSelectedEnum = multiSelectedEnum ? multiSelectedEnum.toString() : undefined; - } - } - else if(selectedEnum){ - currentSelectedEnum = selectedEnum; - } - if (!onBlur) { - onBlur = () => {}; - } + return ( + <div className="validation-input-wrapper"> + <div + className={classNames('form-group', { + required: isRequired, + 'has-error': !isValid + })}> + {label && <label className="control-label">{label}</label>} + {isMultiSelect && otherInputDisabled ? ( + <Select + {...dataTestId} + ref={input => (this.input = input)} + value={currentMultiSelectedEnum} + className="options-input" + clearable={false} + required={isRequired} + disabled={ + isReadOnlyMode || Boolean(this.props.disabled) + } + onBlur={() => onBlur()} + onMultiSelectChanged={value => + this.multiSelectEnumChanged(value) + } + options={this.renderMultiSelectOptions(values)} + multi + /> + ) : ( + <div + className={classNames('input-options', { + 'has-error': !isValid + })}> + <select + {...dataTestId} + ref={input => (this.input = input)} + label={label} + className="form-control input-options-select" + value={currentSelectedEnum} + style={{ + width: otherInputDisabled ? '100%' : '100px' + }} + onBlur={() => onBlur()} + disabled={ + isReadOnlyMode || + Boolean(this.props.disabled) + } + onChange={value => this.enumChanged(value)} + type="select"> + {children || + (values && + values.length && + values.map((val, index) => + this.renderOptions(val, index) + ))} + {onOtherChange && ( + <option key="other" value={other.OTHER}> + {i18n(other.OTHER)} + </option> + )} + </select> - return( - <div className='validation-input-wrapper' > - <div className={classNames('form-group', {'required' : isRequired, 'has-error' : !isValid})} > - {label && <label className='control-label'>{label}</label>} - {isMultiSelect && otherInputDisabled ? - <Select - {...dataTestId} - ref={(input) => this.input = input} - value={currentMultiSelectedEnum} - className='options-input' - clearable={false} - required={isRequired} - disabled={isReadOnlyMode || Boolean(this.props.disabled)} - onBlur={() => onBlur()} - onMultiSelectChanged={value => this.multiSelectEnumChanged(value)} - options={this.renderMultiSelectOptions(values)} - multi/> : - <div className={classNames('input-options',{'has-error' : !isValid})} > - <select - {...dataTestId} - ref={(input) => this.input = input} - label={label} - className='form-control input-options-select' - value={currentSelectedEnum} - style={{'width' : otherInputDisabled ? '100%' : '100px'}} - onBlur={() => onBlur()} - disabled={isReadOnlyMode || Boolean(this.props.disabled)} - onChange={ value => this.enumChanged(value)} - type='select'> - {children || (values && values.length && values.map((val, index) => this.renderOptions(val, index)))} - {onOtherChange && <option key='other' value={other.OTHER}>{i18n(other.OTHER)}</option>} - </select> + {!otherInputDisabled && ( + <div className="input-options-separator" /> + )} + <input + className="form-control input-options-other" + placeholder={i18n('other')} + ref={otherValue => + (this.otherValue = otherValue) + } + style={{ + display: otherInputDisabled + ? 'none' + : 'block' + }} + disabled={ + isReadOnlyMode || + Boolean(this.props.disabled) + } + value={otherValue || ''} + onBlur={() => onBlur()} + onChange={() => this.changedOtherInput()} + /> + </div> + )} + </div> + {this.renderErrorOverlay()} + </div> + ); + } - {!otherInputDisabled && <div className='input-options-separator'/>} - <input - className='form-control input-options-other' - placeholder={i18n('other')} - ref={(otherValue) => this.otherValue = otherValue} - style={{'display' : otherInputDisabled ? 'none' : 'block'}} - disabled={isReadOnlyMode || Boolean(this.props.disabled)} - value={otherValue || ''} - onBlur={() => onBlur()} - onChange={() => this.changedOtherInput()}/> - </div> - } - </div> - { this.renderErrorOverlay() } - </div> - ); - } + renderOptions(val, index) { + return ( + <option key={index} value={val.enum}> + {val.title} + </option> + ); + } - renderOptions(val, index){ - return ( - <option key={index} value={val.enum}>{val.title}</option> - ); - } + renderMultiSelectOptions(values) { + let { onOtherChange } = this.props; + let optionsList = []; + if (onOtherChange) { + optionsList = values + .map(option => { + return { + label: option.title, + value: option.enum + }; + }) + .concat([ + { + label: i18n(other.OTHER), + value: i18n(other.OTHER) + } + ]); + } else { + optionsList = values.map(option => { + return { + label: option.title, + value: option.enum + }; + }); + } + if (optionsList.length > 0 && optionsList[0].value === '') { + optionsList.shift(); + } + return optionsList; + } + renderErrorOverlay() { + let position = 'right'; + const { errorText = '', isValid = true, type, overlayPos } = this.props; - renderMultiSelectOptions(values) { - let {onOtherChange} = this.props; - let optionsList = []; - if (onOtherChange) { - optionsList = values.map(option => { - return { - label: option.title, - value: option.enum, - }; - }).concat([{ - label: i18n(other.OTHER), - value: i18n(other.OTHER), - }]); - } - else { - optionsList = values.map(option => { - return { - label: option.title, - value: option.enum, - }; - }); - } - if (optionsList.length > 0 && optionsList[0].value === '') { - optionsList.shift(); - } - return optionsList; - } + if (overlayPos) { + position = overlayPos; + } else if ( + type === 'text' || + type === 'email' || + type === 'number' || + type === 'password' + ) { + position = 'bottom'; + } - renderErrorOverlay() { - let position = 'right'; - const {errorText = '', isValid = true, type, overlayPos} = this.props; + return ( + <Overlay + show={!isValid} + placement={position} + target={() => { + let { otherInputDisabled } = this.state; + let target = otherInputDisabled + ? ReactDOM.findDOMNode(this.input) + : ReactDOM.findDOMNode(this.otherValue); + return target.offsetParent ? target : undefined; + }} + container={this}> + <Tooltip + id={`error-${errorText.replace(' ', '-')}`} + className="validation-error-message"> + {errorText} + </Tooltip> + </Overlay> + ); + } - if (overlayPos) { - position = overlayPos; - } - else if (type === 'text' - || type === 'email' - || type === 'number' - || type === 'password') { - position = 'bottom'; - } + getValue() { + let res = ''; + let { isMultiSelect } = this.props; + let { otherInputDisabled } = this.state; - return ( - <Overlay - show={!isValid} - placement={position} - target={() => { - let {otherInputDisabled} = this.state; - let target = otherInputDisabled ? ReactDOM.findDOMNode(this.input) : ReactDOM.findDOMNode(this.otherValue); - return target.offsetParent ? target : undefined; - }} - container={this}> - <Tooltip - id={`error-${errorText.replace(' ', '-')}`} - className='validation-error-message'> - {errorText} - </Tooltip> - </Overlay> - ); - } + if (otherInputDisabled) { + res = isMultiSelect ? this.input.getValue() : this.input.value; + } else { + res = this.otherValue.value; + } + return res; + } - getValue() { - let res = ''; - let {isMultiSelect} = this.props; - let {otherInputDisabled} = this.state; + enumChanged() { + let enumValue = this.input.value; + let { + onEnumChange, + onOtherChange, + isMultiSelect, + onChange + } = this.props; + this.setState({ + otherInputDisabled: + !Boolean(onOtherChange) || enumValue !== other.OTHER + }); - if (otherInputDisabled) { - res = isMultiSelect ? this.input.getValue() : this.input.value; - } else { - res = this.otherValue.value; - } - return res; - } + let value = isMultiSelect ? [enumValue] : enumValue; + if (onEnumChange) { + onEnumChange(value); + } + if (onChange) { + onChange(value); + } + } - enumChanged() { - let enumValue = this.input.value; - let {onEnumChange, onOtherChange, isMultiSelect, onChange} = this.props; - this.setState({ - otherInputDisabled: !Boolean(onOtherChange) || enumValue !== other.OTHER - }); + multiSelectEnumChanged(enumValue) { + let { onEnumChange, onOtherChange } = this.props; + let selectedValues = enumValue.map(enumVal => { + return enumVal.value; + }); - let value = isMultiSelect ? [enumValue] : enumValue; - if (onEnumChange) { - onEnumChange(value); - } - if (onChange) { - onChange(value); - } - } + if (this.state.otherInputDisabled === false) { + selectedValues.shift(); + } else if (selectedValues.includes(i18n(other.OTHER))) { + selectedValues = [i18n(other.OTHER)]; + } - multiSelectEnumChanged(enumValue) { - let {onEnumChange, onOtherChange} = this.props; - let selectedValues = enumValue.map(enumVal => { - return enumVal.value; - }); + this.setState({ + otherInputDisabled: + !Boolean(onOtherChange) || + !selectedValues.includes(i18n(other.OTHER)) + }); + onEnumChange(selectedValues); + } - if (this.state.otherInputDisabled === false) { - selectedValues.shift(); - } - else if (selectedValues.includes(i18n(other.OTHER))) { - selectedValues = [i18n(other.OTHER)]; - } + changedOtherInput() { + let { onOtherChange } = this.props; + onOtherChange(this.otherValue.value); + } - this.setState({ - otherInputDisabled: !Boolean(onOtherChange) || !selectedValues.includes(i18n(other.OTHER)) - }); - onEnumChange(selectedValues); - } - - changedOtherInput() { - let {onOtherChange} = this.props; - onOtherChange(this.otherValue.value); - } - - componentDidUpdate() { - let {otherValue, selectedEnum, onInputChange, multiSelectedEnum} = this.props; - if (this.oldProps.otherValue !== otherValue - || this.oldProps.selectedEnum !== selectedEnum - || this.oldProps.multiSelectedEnum !== multiSelectedEnum) { - this.oldProps = { - otherValue, - selectedEnum, - multiSelectedEnum - }; - onInputChange(); - } - } - - static getTitleByName(values, name) { - for (let key of Object.keys(values)) { - let option = values[key].find(option => option.enum === name); - if (option) { - return option.title; - } - } - return name; - } + componentDidUpdate() { + let { + otherValue, + selectedEnum, + onInputChange, + multiSelectedEnum + } = this.props; + if ( + this.oldProps.otherValue !== otherValue || + this.oldProps.selectedEnum !== selectedEnum || + this.oldProps.multiSelectedEnum !== multiSelectedEnum + ) { + this.oldProps = { + otherValue, + selectedEnum, + multiSelectedEnum + }; + onInputChange(); + } + } + static getTitleByName(values, name) { + for (let key of Object.keys(values)) { + let option = values[key].find(option => option.enum === name); + if (option) { + return option.title; + } + } + return name; + } } export default InputOptions; diff --git a/openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx b/openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx index e440fcda69..3e0bb32ca9 100644 --- a/openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx +++ b/openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx @@ -22,114 +22,149 @@ import FormGroup from 'react-bootstrap/lib/FormGroup.js'; import FormControl from 'react-bootstrap/lib/FormControl.js'; class InputWrapper extends React.Component { - - state = { - value: this.props.value, - checked: this.props.checked, - selectedValues: [] - } - - render() { - const {label, hasError, validations = {}, isReadOnlyMode, value, onBlur, onKeyDown, type, disabled, checked, name} = this.props; - const {groupClassName, ...inputProps} = this.props; - return( - <FormGroup className={classNames('form-group', [groupClassName], {'required' : validations.required , 'has-error' : hasError})} > - {(label && (type !== 'checkbox' && type !== 'radio')) && <label className='control-label'>{label}</label>} - {(type === 'text' || type === 'number') && - <FormControl - bsClass={'form-control input-options-other'} - onChange={(e) => this.onChange(e)} - disabled={isReadOnlyMode || Boolean(disabled)} - onBlur={onBlur} - onKeyDown={onKeyDown} - value={value || ''} - ref={(input) => this.inputWrapper = input} - type={type} - data-test-id={this.props['data-test-id']}/>} - - {type === 'textarea' && - <FormControl - className='form-control input-options-other' - disabled={isReadOnlyMode || Boolean(disabled)} - value={value || ''} - onBlur={onBlur} - onKeyDown={onKeyDown} - componentClass={type} - onChange={(e) => this.onChange(e)} - data-test-id={this.props['data-test-id']}/>} - - {type === 'checkbox' && - <Checkbox - className={classNames({'required' : validations.required , 'has-error' : hasError})} - onChange={(e)=>this.onChangeCheckBox(e)} - disabled={isReadOnlyMode || Boolean(disabled)} - checked={value} - data-test-id={this.props['data-test-id']}>{label}</Checkbox>} - - {type === 'radio' && - <Radio name={name} - checked={checked} - disabled={isReadOnlyMode || Boolean(disabled)} - value={value} - ref={(input) => this.inputWrapper = input} - onChange={(isChecked)=>this.onChangeRadio(isChecked)} label={label} - data-test-id={this.props['data-test-id']} />} - {type === 'select' && - <FormControl onClick={ (e) => this.optionSelect(e) } - componentClass={type} - name={name} {...inputProps} - data-test-id={this.props['data-test-id']}/>} - - </FormGroup> - - ); - } - - getValue() { - return this.props.type !== 'select' ? this.state.value : this.state.selectedValues; - } - - getChecked() { - return this.state.checked; - } - - optionSelect(e) { - let selectedValues = []; - if (e.target.value) { - selectedValues.push(e.target.value); - } - this.setState({ - selectedValues - }); - } - - onChange(e) { - let {onChange} = this.props; - this.setState({ - value: e.target.value - }); - onChange(e.target.value); - } - - onChangeCheckBox(e) { - let {onChange} = this.props; - this.setState({ - checked: e.target.checked - }); - onChange(e.target.checked); - } - - onChangeRadio(isChecked) { - let {onChange} = this.props; - this.setState({ - checked: isChecked - }); - onChange(this.state.value); - } - - focus() { - ReactDOM.findDOMNode(this.inputWrapper).focus(); - } - + state = { + value: this.props.value, + checked: this.props.checked, + selectedValues: [] + }; + + render() { + const { + label, + hasError, + validations = {}, + isReadOnlyMode, + value, + onBlur, + onKeyDown, + type, + disabled, + checked, + name + } = this.props; + const { groupClassName, ...inputProps } = this.props; + return ( + <FormGroup + className={classNames('form-group', [groupClassName], { + required: validations.required, + 'has-error': hasError + })}> + {label && + (type !== 'checkbox' && type !== 'radio') && ( + <label className="control-label">{label}</label> + )} + {(type === 'text' || type === 'number') && ( + <FormControl + bsClass={'form-control input-options-other'} + onChange={e => this.onChange(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + onBlur={onBlur} + onKeyDown={onKeyDown} + value={value || ''} + ref={input => (this.inputWrapper = input)} + type={type} + data-test-id={this.props['data-test-id']} + /> + )} + + {type === 'textarea' && ( + <FormControl + className="form-control input-options-other" + disabled={isReadOnlyMode || Boolean(disabled)} + value={value || ''} + onBlur={onBlur} + onKeyDown={onKeyDown} + componentClass={type} + onChange={e => this.onChange(e)} + data-test-id={this.props['data-test-id']} + /> + )} + + {type === 'checkbox' && ( + <Checkbox + className={classNames({ + required: validations.required, + 'has-error': hasError + })} + onChange={e => this.onChangeCheckBox(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + checked={value} + data-test-id={this.props['data-test-id']}> + {label} + </Checkbox> + )} + + {type === 'radio' && ( + <Radio + name={name} + checked={checked} + disabled={isReadOnlyMode || Boolean(disabled)} + value={value} + ref={input => (this.inputWrapper = input)} + onChange={isChecked => this.onChangeRadio(isChecked)} + label={label} + data-test-id={this.props['data-test-id']} + /> + )} + {type === 'select' && ( + <FormControl + onClick={e => this.optionSelect(e)} + componentClass={type} + name={name} + {...inputProps} + data-test-id={this.props['data-test-id']} + /> + )} + </FormGroup> + ); + } + + getValue() { + return this.props.type !== 'select' + ? this.state.value + : this.state.selectedValues; + } + + getChecked() { + return this.state.checked; + } + + optionSelect(e) { + let selectedValues = []; + if (e.target.value) { + selectedValues.push(e.target.value); + } + this.setState({ + selectedValues + }); + } + + onChange(e) { + let { onChange } = this.props; + this.setState({ + value: e.target.value + }); + onChange(e.target.value); + } + + onChangeCheckBox(e) { + let { onChange } = this.props; + this.setState({ + checked: e.target.checked + }); + onChange(e.target.checked); + } + + onChangeRadio(isChecked) { + let { onChange } = this.props; + this.setState({ + checked: isChecked + }); + onChange(this.state.value); + } + + focus() { + ReactDOM.findDOMNode(this.inputWrapper).focus(); + } } -export default InputWrapper; +export default InputWrapper; diff --git a/openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx b/openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx index 0982c133e6..429985a902 100644 --- a/openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx +++ b/openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx @@ -16,64 +16,76 @@ import React from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; -import {default as SDCTabs} from 'sdc-ui/lib/react/Tabs.js'; +import { default as SDCTabs } from 'sdc-ui/lib/react/Tabs.js'; import Overlay from 'react-bootstrap/lib/Overlay.js'; import Tooltip from 'react-bootstrap/lib/Tooltip.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; -export default -class Tabs extends React.Component { +export default class Tabs extends React.Component { + static propTypes = { + children: PropTypes.node + }; - static propTypes = { - children: PropTypes.node - }; + cloneTab(element) { + const { invalidTabs } = this.props; + return React.cloneElement(element, { + key: element.props.tabId, + className: + invalidTabs.indexOf(element.props.tabId) > -1 + ? 'invalid-tab' + : 'valid-tab' + }); + } - cloneTab(element) { - const {invalidTabs} = this.props; - return React.cloneElement( - element, - { - key: element.props.tabId, - className: invalidTabs.indexOf(element.props.tabId) > -1 ? 'invalid-tab' : 'valid-tab' - } - ); - } + showTabsError() { + const { invalidTabs } = this.props; + const showError = + (invalidTabs.length === 1 && + invalidTabs[0] !== this.props.activeTab) || + invalidTabs.length > 1; + return showError; + } - showTabsError() { - const {invalidTabs} = this.props; - const showError = ((invalidTabs.length === 1 && invalidTabs[0] !== this.props.activeTab) || (invalidTabs.length > 1)); - return showError; - } - - render() { - // eslint-disable-next-line no-unused-vars - let {invalidTabs, ...tabProps} = this.props; - return ( - <div> - <SDCTabs {...tabProps} ref='tabsList' id='tabsList' > - {this.props.children.map(element => this.cloneTab(element))} - </SDCTabs> - <Overlay - animation={false} - show={this.showTabsError()} - placement='bottom' - target={() => { - let target = ReactDOM.findDOMNode(this.refs.tabsList).querySelector('ul > li.invalid-tab:not(.sdc-tab-active):nth-of-type(n)'); - return target && target.offsetParent ? target : undefined; - } - } - container={() => { - let target = ReactDOM.findDOMNode(this.refs.tabsList).querySelector('ul > li.invalid-tab:not(.sdc-tab-active):nth-of-type(n)'); - return target && target.offsetParent ? target.offsetParent : this; - }}> - <Tooltip - id='error-some-tabs-contain-errors' - className='validation-error-message'> - {i18n('One or more tabs are invalid')} - </Tooltip> - </Overlay> - </div> - ); - } + render() { + // eslint-disable-next-line no-unused-vars + let { invalidTabs, ...tabProps } = this.props; + return ( + <div> + <SDCTabs {...tabProps} ref="tabsList" id="tabsList"> + {this.props.children.map(element => this.cloneTab(element))} + </SDCTabs> + <Overlay + animation={false} + show={this.showTabsError()} + placement="bottom" + target={() => { + let target = ReactDOM.findDOMNode( + this.refs.tabsList + ).querySelector( + 'ul > li.invalid-tab:not(.sdc-tab-active):nth-of-type(n)' + ); + return target && target.offsetParent + ? target + : undefined; + }} + container={() => { + let target = ReactDOM.findDOMNode( + this.refs.tabsList + ).querySelector( + 'ul > li.invalid-tab:not(.sdc-tab-active):nth-of-type(n)' + ); + return target && target.offsetParent + ? target.offsetParent + : this; + }}> + <Tooltip + id="error-some-tabs-contain-errors" + className="validation-error-message"> + {i18n('One or more tabs are invalid')} + </Tooltip> + </Overlay> + </div> + ); + } } diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx index 151d3fe859..e460f68a98 100644 --- a/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx +++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx @@ -27,32 +27,63 @@ import Button from 'sdc-ui/lib/react/Button.js'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; class ValidationButtons extends React.Component { + static propTypes = { + labledButtons: PropTypes.bool.isRequired, + isReadOnlyMode: PropTypes.bool, + submitButtonText: PropTypes.string, + cancelButtonText: PropTypes.string + }; - static propTypes = { - labledButtons: PropTypes.bool.isRequired, - isReadOnlyMode: PropTypes.bool, - submitButtonText: PropTypes.string, - cancelButtonText: PropTypes.string - }; + state = { + isValid: this.props.formValid + }; - state = { - isValid: this.props.formValid - }; - - render() { - let submitBtn = this.props.labledButtons ? this.props.submitButtonText ? this.props.submitButtonText : i18n('Save') : <SVGIcon className='check' name='check'/>; - let closeBtn = this.props.labledButtons ? this.props.cancelButtonText ? this.props.cancelButtonText : i18n('Cancel') : <SVGIcon className='close' name='close'/>; - return ( - <div className='validation-buttons'> - {!this.props.isReadOnlyMode ? - <div> - <Button type='submit' data-test-id='form-submit-button' disabled={!this.state.isValid}>{submitBtn}</Button> - <Button btnType='outline' type='reset' data-test-id='form-close-button'>{closeBtn}</Button> - </div> - : <Button btnType='outline' type='reset' data-test-id='form-close-button'>{i18n('Close')}</Button> - } - </div> - ); - } + render() { + let submitBtn = this.props.labledButtons ? ( + this.props.submitButtonText ? ( + this.props.submitButtonText + ) : ( + i18n('Save') + ) + ) : ( + <SVGIcon className="check" name="check" /> + ); + let closeBtn = this.props.labledButtons ? ( + this.props.cancelButtonText ? ( + this.props.cancelButtonText + ) : ( + i18n('Cancel') + ) + ) : ( + <SVGIcon className="close" name="close" /> + ); + return ( + <div className="validation-buttons"> + {!this.props.isReadOnlyMode ? ( + <div> + <Button + type="submit" + data-test-id="form-submit-button" + disabled={!this.state.isValid}> + {submitBtn} + </Button> + <Button + btnType="outline" + type="reset" + data-test-id="form-close-button"> + {closeBtn} + </Button> + </div> + ) : ( + <Button + btnType="outline" + type="reset" + data-test-id="form-close-button"> + {i18n('Close')} + </Button> + )} + </div> + ); + } } export default ValidationButtons; diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx index 60c559a3d1..359af9d3b8 100644 --- a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx +++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx @@ -19,50 +19,78 @@ import classnames from 'classnames'; import i18n from 'nfvo-utils/i18n/i18n.js'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; import store from 'sdc-app/AppStore.js'; -import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js'; +import { actionTypes as modalActionTypes } from 'nfvo-components/modal/GlobalModalConstants.js'; class ListEditorItem extends React.Component { - static propTypes = { - onSelect: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - onDelete: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - onEdit: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), - children: PropTypes.node, - isReadOnlyMode: PropTypes.bool - }; + static propTypes = { + onSelect: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), + onDelete: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), + onEdit: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), + children: PropTypes.node, + isReadOnlyMode: PropTypes.bool + }; - render() { - let {onDelete, onSelect, onEdit, children, isReadOnlyMode} = this.props; - let isAbilityToDelete = isReadOnlyMode === undefined ? true : !isReadOnlyMode; - return ( - <div className={classnames('list-editor-item-view', {'selectable': Boolean(onSelect)})} data-test-id='list-editor-item'> - <div className='list-editor-item-view-content' onClick={onSelect}> - {children} - </div> - {(onEdit || onDelete) && <div className='list-editor-item-view-controller'> - {onEdit && <SVGIcon name='sliders' onClick={() => this.onClickedItem(onEdit)}/>} - {onDelete && isAbilityToDelete && <SVGIcon name='trashO' data-test-id='delete-list-item' onClick={() => this.onClickedItem(onDelete)}/>} - </div>} - </div> - ); - } + render() { + let { + onDelete, + onSelect, + onEdit, + children, + isReadOnlyMode + } = this.props; + let isAbilityToDelete = + isReadOnlyMode === undefined ? true : !isReadOnlyMode; + return ( + <div + className={classnames('list-editor-item-view', { + selectable: Boolean(onSelect) + })} + data-test-id="list-editor-item"> + <div + className="list-editor-item-view-content" + onClick={onSelect}> + {children} + </div> + {(onEdit || onDelete) && ( + <div className="list-editor-item-view-controller"> + {onEdit && ( + <SVGIcon + name="sliders" + onClick={() => this.onClickedItem(onEdit)} + /> + )} + {onDelete && + isAbilityToDelete && ( + <SVGIcon + name="trashO" + data-test-id="delete-list-item" + onClick={() => this.onClickedItem(onDelete)} + /> + )} + </div> + )} + </div> + ); + } - onClickedItem(callBackFunc) { - if(typeof callBackFunc === 'function') { - let {isCheckedOut} = this.props; - if (isCheckedOut === false) { - store.dispatch({ - type: modalActionTypes.GLOBAL_MODAL_WARNING, - data: { - title: i18n('Error'), - msg: i18n('This item is checkedin/submitted, Click Check Out to continue') - } - }); - } - else { - callBackFunc(); - } - } - } + onClickedItem(callBackFunc) { + if (typeof callBackFunc === 'function') { + let { isCheckedOut } = this.props; + if (isCheckedOut === false) { + store.dispatch({ + type: modalActionTypes.GLOBAL_MODAL_WARNING, + data: { + title: i18n('Error'), + msg: i18n( + 'This item is checkedin/submitted, Click Check Out to continue' + ) + } + }); + } else { + callBackFunc(); + } + } + } } export default ListEditorItem; diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemViewField.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemViewField.jsx index 839f9a504a..509ea176a5 100644 --- a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemViewField.jsx +++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemViewField.jsx @@ -15,10 +15,8 @@ */ import React from 'react'; -export const ListEditorItemViewField = ({children}) => ( - <div className='list-editor-item-view-field'> - {children} - </div> +export const ListEditorItemViewField = ({ children }) => ( + <div className="list-editor-item-view-field">{children}</div> ); export default ListEditorItemViewField; diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx index 16823b7dc5..f71372ce1a 100644 --- a/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx +++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx @@ -19,69 +19,107 @@ import Button from 'sdc-ui/lib/react/Button.js'; import classnames from 'classnames'; import ExpandableInput from 'nfvo-components/input/ExpandableInput.jsx'; -const ListEditorHeader = ({onAdd, isReadOnlyMode, title, plusButtonTitle}) => { - return ( - <div className='list-editor-view-header'> - {title && <div className='list-editor-view-title'>{title}</div>} - <div> - { onAdd && - <Button data-test-id='add-button' iconName='plusThin' btnType='link' onClick={onAdd} disabled={isReadOnlyMode === true}>{plusButtonTitle}</Button> - } - </div> - </div> - ); +const ListEditorHeader = ({ + onAdd, + isReadOnlyMode, + title, + plusButtonTitle +}) => { + return ( + <div className="list-editor-view-header"> + {title && <div className="list-editor-view-title">{title}</div>} + <div> + {onAdd && ( + <Button + data-test-id="add-button" + iconName="plusThin" + btnType="link" + onClick={onAdd} + disabled={isReadOnlyMode === true}> + {plusButtonTitle} + </Button> + )} + </div> + </div> + ); }; -const ListEditorScroller = ({children, twoColumns}) => { - return ( - <div className='list-editor-view-list-scroller'> - <div className={classnames('list-editor-view-list', {'two-columns': twoColumns})}> - {children} - </div> - </div> - ); +const ListEditorScroller = ({ children, twoColumns }) => { + return ( + <div className="list-editor-view-list-scroller"> + <div + className={classnames('list-editor-view-list', { + 'two-columns': twoColumns + })}> + {children} + </div> + </div> + ); }; -const FilterWrapper = ({onFilter, filterValue}) => { - return ( - <div className='expandble-search-wrapper'> - <ExpandableInput - onChange={onFilter} - iconType='search' - value={filterValue}/> - </div> - ); +const FilterWrapper = ({ onFilter, filterValue }) => { + return ( + <div className="expandble-search-wrapper"> + <ExpandableInput + onChange={onFilter} + iconType="search" + value={filterValue} + /> + </div> + ); }; class ListEditorView extends React.Component { + static defaultProps = { + className: '', + twoColumns: false + }; - static defaultProps = { - className: '', - twoColumns: false - }; - - static propTypes = { - title: PropTypes.string, - plusButtonTitle: PropTypes.string, - children: PropTypes.node, - filterValue: PropTypes.string, - onFilter: PropTypes.func, - className: PropTypes.string, - isReadOnlyMode: PropTypes.bool, - placeholder: PropTypes.string, - twoColumns: PropTypes.bool - }; - - render() { - let {title, plusButtonTitle, onAdd, children, onFilter, className, isReadOnlyMode, twoColumns, filterValue} = this.props; - return ( - <div className={classnames('list-editor-view', className)}> - <ListEditorHeader onAdd={onAdd} isReadOnlyMode={isReadOnlyMode} plusButtonTitle={plusButtonTitle} title={title}/> - {onFilter && (children.length || filterValue) && <FilterWrapper onFilter={onFilter} filterValue={filterValue}/>} - <ListEditorScroller children={children} twoColumns={twoColumns}/> - </div> - ); - } + static propTypes = { + title: PropTypes.string, + plusButtonTitle: PropTypes.string, + children: PropTypes.node, + filterValue: PropTypes.string, + onFilter: PropTypes.func, + className: PropTypes.string, + isReadOnlyMode: PropTypes.bool, + placeholder: PropTypes.string, + twoColumns: PropTypes.bool + }; + render() { + let { + title, + plusButtonTitle, + onAdd, + children, + onFilter, + className, + isReadOnlyMode, + twoColumns, + filterValue + } = this.props; + return ( + <div className={classnames('list-editor-view', className)}> + <ListEditorHeader + onAdd={onAdd} + isReadOnlyMode={isReadOnlyMode} + plusButtonTitle={plusButtonTitle} + title={title} + /> + {onFilter && + (children.length || filterValue) && ( + <FilterWrapper + onFilter={onFilter} + filterValue={filterValue} + /> + )} + <ListEditorScroller + children={children} + twoColumns={twoColumns} + /> + </div> + ); + } } export default ListEditorView; diff --git a/openecomp-ui/src/nfvo-components/listEditor/listEditor.stories.js b/openecomp-ui/src/nfvo-components/listEditor/listEditor.stories.js index 81125c84ba..67258c956d 100644 --- a/openecomp-ui/src/nfvo-components/listEditor/listEditor.stories.js +++ b/openecomp-ui/src/nfvo-components/listEditor/listEditor.stories.js @@ -1,27 +1,25 @@ import React from 'react'; -import {storiesOf, action} from '@kadira/storybook'; +import { storiesOf, action } from '@kadira/storybook'; import ListEditorView from './ListEditorView.jsx'; import ListEditorItemView from './ListEditorItemView.jsx'; import ListEditorItemField from './ListEditorItemViewField.jsx'; -import {text, number} from '@kadira/storybook-addon-knobs'; -import {withKnobs} from '@kadira/storybook-addon-knobs'; +import { text, number } from '@kadira/storybook-addon-knobs'; +import { withKnobs } from '@kadira/storybook-addon-knobs'; -function makeChildren({onEdit = false, onDelete = false} = {}) { - return ( - [...Array(number('Items', 2)).keys()].map(index => ( - <ListEditorItemView - key={index} - onEdit={onEdit ? onEdit : undefined} - onDelete={onDelete ? onDelete : undefined}> - <ListEditorItemField> - <div>{text('field 1', 'Lorum Ipsum')}</div> - </ListEditorItemField> - <ListEditorItemField> - <div>{text('field 2', 'Lorum Ipsum')}</div> - </ListEditorItemField> - </ListEditorItemView>) - ) - ); +function makeChildren({ onEdit = false, onDelete = false } = {}) { + return [...Array(number('Items', 2)).keys()].map(index => ( + <ListEditorItemView + key={index} + onEdit={onEdit ? onEdit : undefined} + onDelete={onDelete ? onDelete : undefined}> + <ListEditorItemField> + <div>{text('field 1', 'Lorum Ipsum')}</div> + </ListEditorItemField> + <ListEditorItemField> + <div>{text('field 2', 'Lorum Ipsum')}</div> + </ListEditorItemField> + </ListEditorItemView> + )); } const stories = storiesOf('ListEditor', module); @@ -29,32 +27,49 @@ stories.addDecorator(withKnobs); stories .add('regular', () => ( - <ListEditorView title='List Editor'> - {makeChildren()} - </ListEditorView> + <ListEditorView title="List Editor">{makeChildren()}</ListEditorView> )) .add('two columns', () => ( - <ListEditorView title='List Editor' twoColumns> + <ListEditorView title="List Editor" twoColumns> {makeChildren()} </ListEditorView> )) .add('with add', () => ( - <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns> - {makeChildren()} + <ListEditorView + title="List Editor" + onAdd={action('onAdd')} + plusButtonTitle="Add" + twoColumns> + {makeChildren()} </ListEditorView> )) .add('with delete', () => ( - <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns> - {makeChildren({onDelete: action('onDelete')})} + <ListEditorView + title="List Editor" + onAdd={action('onAdd')} + plusButtonTitle="Add" + twoColumns> + {makeChildren({ onDelete: action('onDelete') })} </ListEditorView> )) .add('with edit', () => ( - <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns> - {makeChildren({onEdit: action('onEdit')})} + <ListEditorView + title="List Editor" + onAdd={action('onAdd')} + plusButtonTitle="Add" + twoColumns> + {makeChildren({ onEdit: action('onEdit') })} </ListEditorView> )) .add('with edit and delete', () => ( - <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns> - {makeChildren({onDelete: action('onDelete'), onEdit: action('onEdit')})} + <ListEditorView + title="List Editor" + onAdd={action('onAdd')} + plusButtonTitle="Add" + twoColumns> + {makeChildren({ + onDelete: action('onDelete'), + onEdit: action('onEdit') + })} </ListEditorView> )); diff --git a/openecomp-ui/src/nfvo-components/loader/Loader.jsx b/openecomp-ui/src/nfvo-components/loader/Loader.jsx index 9ebe52dcfc..076351bf7a 100644 --- a/openecomp-ui/src/nfvo-components/loader/Loader.jsx +++ b/openecomp-ui/src/nfvo-components/loader/Loader.jsx @@ -16,40 +16,39 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {connect} from 'react-redux'; +import { connect } from 'react-redux'; -const mapStateToProps = ({loader}) => { - return { - isLoading: loader.isLoading - }; +const mapStateToProps = ({ loader }) => { + return { + isLoading: loader.isLoading + }; }; class Loader extends React.Component { - - static propTypes = { - isLoading: PropTypes.bool.isRequired - }; - - static defaultProps = { - isLoading: false - }; - - shouldComponentUpdate(nextProps) { - return (nextProps.isLoading !== this.props.isLoading); - } - - render() { - let {isLoading} = this.props; - return ( - <div className='onboarding-loader'> - { - isLoading && <div className='onboarding-loader-backdrop'> - <div className='tlv-loader large'></div> - </div> - } - </div> - ); - } + static propTypes = { + isLoading: PropTypes.bool.isRequired + }; + + static defaultProps = { + isLoading: false + }; + + shouldComponentUpdate(nextProps) { + return nextProps.isLoading !== this.props.isLoading; + } + + render() { + let { isLoading } = this.props; + return ( + <div className="onboarding-loader"> + {isLoading && ( + <div className="onboarding-loader-backdrop"> + <div className="tlv-loader large" /> + </div> + )} + </div> + ); + } } -export default connect(mapStateToProps) (Loader); +export default connect(mapStateToProps)(Loader); diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js index 2b531b08fa..e367a74352 100644 --- a/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js +++ b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js @@ -16,9 +16,9 @@ import keyMirror from 'nfvo-utils/KeyMirror.js'; export const actionTypes = keyMirror({ - SHOW: null, - HIDE: null, + SHOW: null, + HIDE: null, - SEND_REQUEST: null, - RECEIVE_RESPONSE: null + SEND_REQUEST: null, + RECEIVE_RESPONSE: null }); diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js index 3afdad0573..1d0f6790e1 100644 --- a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js +++ b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js @@ -13,43 +13,54 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {actionTypes} from './LoaderConstants.js'; +import { actionTypes } from './LoaderConstants.js'; -export default (state = {fetchingRequests : 0, currentlyFetching : [], isLoading : false}, action) => { - let fetchingRequests = state.fetchingRequests; - let newArray; - switch (action.type) { - case actionTypes.SEND_REQUEST: - fetchingRequests++; - newArray = state.currentlyFetching.slice(); - newArray.splice(0, 0, action.url); - if (DEBUG) { - console.log('Loader SEND REQUEST url: ' + action.url); - console.log('Loader SEND REQUEST number of fetching requests: ' + fetchingRequests); - } - return { - fetchingRequests: fetchingRequests, - currentlyFetching : newArray, - isLoading: true - }; - case actionTypes.RECEIVE_RESPONSE: - fetchingRequests--; +export default ( + state = { fetchingRequests: 0, currentlyFetching: [], isLoading: false }, + action +) => { + let fetchingRequests = state.fetchingRequests; + let newArray; + switch (action.type) { + case actionTypes.SEND_REQUEST: + fetchingRequests++; + newArray = state.currentlyFetching.slice(); + newArray.splice(0, 0, action.url); + if (DEBUG) { + console.log('Loader SEND REQUEST url: ' + action.url); + console.log( + 'Loader SEND REQUEST number of fetching requests: ' + + fetchingRequests + ); + } + return { + fetchingRequests: fetchingRequests, + currentlyFetching: newArray, + isLoading: true + }; + case actionTypes.RECEIVE_RESPONSE: + fetchingRequests--; - newArray = state.currentlyFetching.filter((item) => {return item !== action.url;}); - if (DEBUG) { - console.log('Loader RECEIVE_RESPONSE url: ' + action.url); - console.log('Loader RECEIVE_RESPONSE: number of fetching requests: ' + fetchingRequests); - } - return { - currentlyFetching : newArray, - fetchingRequests: fetchingRequests, - isLoading: (fetchingRequests !== 0) - }; - case actionTypes.SHOW: - return {isLoading: true}; - case actionTypes.HIDE: - return {isLoading: false}; - default: - return state; - } + newArray = state.currentlyFetching.filter(item => { + return item !== action.url; + }); + if (DEBUG) { + console.log('Loader RECEIVE_RESPONSE url: ' + action.url); + console.log( + 'Loader RECEIVE_RESPONSE: number of fetching requests: ' + + fetchingRequests + ); + } + return { + currentlyFetching: newArray, + fetchingRequests: fetchingRequests, + isLoading: fetchingRequests !== 0 + }; + case actionTypes.SHOW: + return { isLoading: true }; + case actionTypes.HIDE: + return { isLoading: false }; + default: + return state; + } }; diff --git a/openecomp-ui/src/nfvo-components/modal/GlobalModal.js b/openecomp-ui/src/nfvo-components/modal/GlobalModal.js index 75b7983ac3..3f19bd76a3 100644 --- a/openecomp-ui/src/nfvo-components/modal/GlobalModal.js +++ b/openecomp-ui/src/nfvo-components/modal/GlobalModal.js @@ -16,145 +16,195 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {connect} from 'react-redux'; +import { connect } from 'react-redux'; import Modal from 'nfvo-components/modal/Modal.jsx'; import Button from 'sdc-ui/lib/react/Button.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; -import {modalContentComponents} from 'sdc-app/common/modal/ModalContentMapper.js'; -import {actionTypes, typeEnum} from './GlobalModalConstants.js'; - +import { modalContentComponents } from 'sdc-app/common/modal/ModalContentMapper.js'; +import { actionTypes, typeEnum } from './GlobalModalConstants.js'; const typeClass = { - 'default': 'primary', - error: 'negative', - warning: 'warning', - success: 'positive' + default: 'primary', + error: 'negative', + warning: 'warning', + success: 'positive' }; const type2HeaderColor = { - 'default': 'primary', - error: 'danger', - warning: 'warning', - success: 'success' + default: 'primary', + error: 'danger', + warning: 'warning', + success: 'success' }; - -const ModalFooter = ({type, onConfirmed, onDeclined, onClose, confirmationButtonText, cancelButtonText}) => { - let myPropsForNoConfirmed = {}; - if (onConfirmed) { - myPropsForNoConfirmed.btnType = 'outline'; - } - return ( - <Modal.Footer> - <div className='sdc-modal-footer'> - {onConfirmed && <Button data-test-id='sdc-modal-confirm-button' color={typeClass[type]} onClick={() => { - onConfirmed(); - onClose(); - }}>{confirmationButtonText}</Button>} - <Button {...myPropsForNoConfirmed} data-test-id='sdc-modal-cancel-button' btnType='outline' color={typeClass[type]} onClick={onDeclined ? () => { - onDeclined(); - onClose();} : () => onClose()}> - {cancelButtonText} - </Button> - </div> - </Modal.Footer> - ); +const ModalFooter = ({ + type, + onConfirmed, + onDeclined, + onClose, + confirmationButtonText, + cancelButtonText +}) => { + let myPropsForNoConfirmed = {}; + if (onConfirmed) { + myPropsForNoConfirmed.btnType = 'outline'; + } + return ( + <Modal.Footer> + <div className="sdc-modal-footer"> + {onConfirmed && ( + <Button + data-test-id="sdc-modal-confirm-button" + color={typeClass[type]} + onClick={() => { + onConfirmed(); + onClose(); + }}> + {confirmationButtonText} + </Button> + )} + <Button + {...myPropsForNoConfirmed} + data-test-id="sdc-modal-cancel-button" + btnType="outline" + color={typeClass[type]} + onClick={ + onDeclined + ? () => { + onDeclined(); + onClose(); + } + : () => onClose() + }> + {cancelButtonText} + </Button> + </div> + </Modal.Footer> + ); }; ModalFooter.defaultProps = { - type: 'default', - confirmationButtonText: i18n('OK'), - cancelButtonText: i18n('Cancel') + type: 'default', + confirmationButtonText: i18n('OK'), + cancelButtonText: i18n('Cancel') }; ModalFooter.PropTypes = { - type: PropTypes.string, - confirmationButtonText: PropTypes.string, - cancelButtonText: PropTypes.string + type: PropTypes.string, + confirmationButtonText: PropTypes.string, + cancelButtonText: PropTypes.string }; -export const mapStateToProps = ({modal}) => { - const show = !!modal; - return { - show, - ...modal - }; +export const mapStateToProps = ({ modal }) => { + const show = !!modal; + return { + show, + ...modal + }; }; -export const mapActionToProps = (dispatch) => { - return { - onClose: () => dispatch({type: actionTypes.GLOBAL_MODAL_CLOSE}) - }; +export const mapActionToProps = dispatch => { + return { + onClose: () => dispatch({ type: actionTypes.GLOBAL_MODAL_CLOSE }) + }; }; - -export class GlobalModalView extends React.Component { - - static propTypes = { - show: PropTypes.bool, - type: PropTypes.oneOf(['default', 'error', 'warning', 'success']), - title: PropTypes.string, - modalComponentProps: PropTypes.object, - modalComponentName: PropTypes.string, - onConfirmed: PropTypes.func, - onDeclined: PropTypes.func, - confirmationButtonText: PropTypes.string, - cancelButtonText: PropTypes.string - }; - - static defaultProps = { - show: false, - type: 'default', - title: '' - }; - - render() { - let {title, type, show, modalComponentName, modalComponentProps, - modalClassName, msg, onConfirmed, onDeclined, confirmationButtonText, cancelButtonText, onClose} = this.props; - const ComponentToRender = modalContentComponents[modalComponentName]; - return ( - <Modal show={show} bsSize={modalComponentProps && modalComponentProps.size} className={`onborading-modal ${modalClassName || ''} ${type2HeaderColor[type]}`}> - <Modal.Header> - <Modal.Title>{title}</Modal.Title> - </Modal.Header> - <Modal.Body> - {ComponentToRender ? - <ComponentToRender {...modalComponentProps}/> : - msg && typeof msg === 'string' ? - <div> {msg.split('\n').map((txt, i) => <span key={i}> {txt} <br/> </span>)} </div> : - msg - } - </Modal.Body> - {(onConfirmed || onDeclined || type !== typeEnum.DEFAULT) && - <ModalFooter - type={type} - onConfirmed={onConfirmed} - onDeclined={onDeclined} - onClose={onClose} - confirmationButtonText={confirmationButtonText} - cancelButtonText={cancelButtonText}/>} - </Modal> - ); - } - - componentDidUpdate() { - if (this.props.timeout) { - setTimeout(this.props.onClose, this.props.timeout); - } - } -}; +export class GlobalModalView extends React.Component { + static propTypes = { + show: PropTypes.bool, + type: PropTypes.oneOf(['default', 'error', 'warning', 'success']), + title: PropTypes.string, + modalComponentProps: PropTypes.object, + modalComponentName: PropTypes.string, + onConfirmed: PropTypes.func, + onDeclined: PropTypes.func, + confirmationButtonText: PropTypes.string, + cancelButtonText: PropTypes.string + }; + + static defaultProps = { + show: false, + type: 'default', + title: '' + }; + + render() { + let { + title, + type, + show, + modalComponentName, + modalComponentProps, + modalClassName, + msg, + onConfirmed, + onDeclined, + confirmationButtonText, + cancelButtonText, + onClose + } = this.props; + const ComponentToRender = modalContentComponents[modalComponentName]; + return ( + <Modal + show={show} + bsSize={modalComponentProps && modalComponentProps.size} + className={`onborading-modal ${modalClassName || ''} ${ + type2HeaderColor[type] + }`}> + <Modal.Header> + <Modal.Title>{title}</Modal.Title> + </Modal.Header> + <Modal.Body> + {ComponentToRender ? ( + <ComponentToRender {...modalComponentProps} /> + ) : msg && typeof msg === 'string' ? ( + <div> + {' '} + {msg.split('\n').map((txt, i) => ( + <span key={i}> + {' '} + {txt} <br />{' '} + </span> + ))}{' '} + </div> + ) : ( + msg + )} + </Modal.Body> + {(onConfirmed || onDeclined || type !== typeEnum.DEFAULT) && ( + <ModalFooter + type={type} + onConfirmed={onConfirmed} + onDeclined={onDeclined} + onClose={onClose} + confirmationButtonText={confirmationButtonText} + cancelButtonText={cancelButtonText} + /> + )} + </Modal> + ); + } + + componentDidUpdate() { + if (this.props.timeout) { + setTimeout(this.props.onClose, this.props.timeout); + } + } +} GlobalModalView.propTypes = { - show: PropTypes.bool, - type: PropTypes.oneOf(['default', 'error', 'warning', 'success']), - title: PropTypes.string, - modalComponentProps: PropTypes.object, - modalComponentName: PropTypes.string, - onConfirmed: PropTypes.func, - onDeclined: PropTypes.func, - confirmationButtonText: PropTypes.string, - cancelButtonText: PropTypes.string + show: PropTypes.bool, + type: PropTypes.oneOf(['default', 'error', 'warning', 'success']), + title: PropTypes.string, + modalComponentProps: PropTypes.object, + modalComponentName: PropTypes.string, + onConfirmed: PropTypes.func, + onDeclined: PropTypes.func, + confirmationButtonText: PropTypes.string, + cancelButtonText: PropTypes.string }; -export default connect(mapStateToProps, mapActionToProps, null, {withRef: true})(GlobalModalView); +export default connect(mapStateToProps, mapActionToProps, null, { + withRef: true +})(GlobalModalView); diff --git a/openecomp-ui/src/nfvo-components/modal/GlobalModalConstants.js b/openecomp-ui/src/nfvo-components/modal/GlobalModalConstants.js index 3e5545371a..e9c1853c97 100644 --- a/openecomp-ui/src/nfvo-components/modal/GlobalModalConstants.js +++ b/openecomp-ui/src/nfvo-components/modal/GlobalModalConstants.js @@ -16,23 +16,21 @@ import keyMirror from 'nfvo-utils/KeyMirror.js'; export const actionTypes = keyMirror({ - GLOBAL_MODAL_SHOW: null, - GLOBAL_MODAL_CLOSE: null, - GLOBAL_MODAL_ERROR: null, - GLOBAL_MODAL_WARNING: null, - GLOBAL_MODAL_SUCCESS: null, - + GLOBAL_MODAL_SHOW: null, + GLOBAL_MODAL_CLOSE: null, + GLOBAL_MODAL_ERROR: null, + GLOBAL_MODAL_WARNING: null, + GLOBAL_MODAL_SUCCESS: null }); - export const typeEnum = { - DEFAULT: 'default', - ERROR: 'error', - WARNING: 'warning', - SUCCESS: 'success' + DEFAULT: 'default', + ERROR: 'error', + WARNING: 'warning', + SUCCESS: 'success' }; -export const modalSizes = { - LARGE: 'large', - SMALL: 'small' +export const modalSizes = { + LARGE: 'large', + SMALL: 'small' }; diff --git a/openecomp-ui/src/nfvo-components/modal/GlobalModalReducer.js b/openecomp-ui/src/nfvo-components/modal/GlobalModalReducer.js index 28674ea569..b2273fa7a7 100644 --- a/openecomp-ui/src/nfvo-components/modal/GlobalModalReducer.js +++ b/openecomp-ui/src/nfvo-components/modal/GlobalModalReducer.js @@ -14,37 +14,37 @@ * permissions and limitations under the License. */ -import {actionTypes, typeEnum} from './GlobalModalConstants.js'; +import { actionTypes, typeEnum } from './GlobalModalConstants.js'; export default (state = null, action) => { - switch (action.type) { - case actionTypes.GLOBAL_MODAL_SHOW: - return { - ...action.data - }; - case actionTypes.GLOBAL_MODAL_ERROR: - return { - type: typeEnum.ERROR, - modalClassName: 'notification-modal', - ...action.data - }; - case actionTypes.GLOBAL_MODAL_WARNING: - return { - type: typeEnum.WARNING, - modalClassName: 'notification-modal', - ...action.data - }; + switch (action.type) { + case actionTypes.GLOBAL_MODAL_SHOW: + return { + ...action.data + }; + case actionTypes.GLOBAL_MODAL_ERROR: + return { + type: typeEnum.ERROR, + modalClassName: 'notification-modal', + ...action.data + }; + case actionTypes.GLOBAL_MODAL_WARNING: + return { + type: typeEnum.WARNING, + modalClassName: 'notification-modal', + ...action.data + }; - case actionTypes.GLOBAL_MODAL_SUCCESS: - return { - type: typeEnum.SUCCESS, - modalClassName: 'notification-modal', - ...action.data - }; + case actionTypes.GLOBAL_MODAL_SUCCESS: + return { + type: typeEnum.SUCCESS, + modalClassName: 'notification-modal', + ...action.data + }; - case actionTypes.GLOBAL_MODAL_CLOSE: - return null; - default: - return state; - } + case actionTypes.GLOBAL_MODAL_CLOSE: + return null; + default: + return state; + } }; diff --git a/openecomp-ui/src/nfvo-components/modal/Modal.jsx b/openecomp-ui/src/nfvo-components/modal/Modal.jsx index b0f704dba9..cfd757501f 100644 --- a/openecomp-ui/src/nfvo-components/modal/Modal.jsx +++ b/openecomp-ui/src/nfvo-components/modal/Modal.jsx @@ -20,65 +20,63 @@ import BootstrapModal from 'react-bootstrap/lib/Modal.js'; let nextModalId = 0; export default class Modal extends React.Component { + static Header = BootstrapModal.Header; - static Header = BootstrapModal.Header; + static Title = BootstrapModal.Title; - static Title = BootstrapModal.Title; + static Footer = BootstrapModal.Footer; - static Footer = BootstrapModal.Footer; + static Body = class ModalBody extends React.Component { + render() { + let { children, ...props } = this.props; + return ( + <BootstrapModal.Body {...props}>{children}</BootstrapModal.Body> + ); + } - static Body = class ModalBody extends React.Component { + componentDidMount() { + let element = ReactDOM.findDOMNode(this); + element.addEventListener('click', event => { + if (event.target.tagName === 'A') { + event.preventDefault(); + } + }); + ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType => + element.addEventListener(eventType, event => + event.stopPropagation() + ) + ); + } + }; - render() { - let {children, ...props} = this.props; - return ( - <BootstrapModal.Body {...props}> - {children} - </BootstrapModal.Body> - ); - } + componentWillMount() { + this.modalId = `dox-ui-modal-${nextModalId++}`; + } - componentDidMount() { - let element = ReactDOM.findDOMNode(this); - element.addEventListener('click', event => { - if (event.target.tagName === 'A') { - event.preventDefault(); - } - }); - ['wheel', 'mousewheel', 'DOMMouseScroll'].forEach(eventType => - element.addEventListener(eventType, event => event.stopPropagation()) - ); - } - }; + componentDidMount() { + this.ensureRootClass(); + } - componentWillMount() { - this.modalId = `dox-ui-modal-${nextModalId++}`; - } + componentDidUpdate() { + this.ensureRootClass(); + } - componentDidMount() { - this.ensureRootClass(); - } + ensureRootClass() { + let element = document.getElementById(this.modalId); + while (element && !element.hasAttribute('data-reactroot')) { + element = element.parentElement; + } + if (element && !element.classList.contains('dox-ui')) { + element.classList.add('dox-ui'); + } + } - componentDidUpdate() { - this.ensureRootClass(); - } - - ensureRootClass() { - let element = document.getElementById(this.modalId); - while(element && !element.hasAttribute('data-reactroot')) { - element = element.parentElement; - } - if (element && !element.classList.contains('dox-ui')) { - element.classList.add('dox-ui'); - } - } - - render() { - let {children, ...props} = this.props; - return ( - <BootstrapModal {...props} id={this.modalId}> - {children} - </BootstrapModal> - ); - } + render() { + let { children, ...props } = this.props; + return ( + <BootstrapModal {...props} id={this.modalId}> + {children} + </BootstrapModal> + ); + } } diff --git a/openecomp-ui/src/nfvo-components/overlay/Overlay.jsx b/openecomp-ui/src/nfvo-components/overlay/Overlay.jsx index 054c1e2852..7ed898e593 100644 --- a/openecomp-ui/src/nfvo-components/overlay/Overlay.jsx +++ b/openecomp-ui/src/nfvo-components/overlay/Overlay.jsx @@ -18,23 +18,21 @@ import React from 'react'; import enhanceWithClickOutside from 'react-click-outside'; class Overlay extends React.Component { + handleClickOutside() { + if (this.props.onClose) { + this.props.onClose(); + } + } - handleClickOutside() { - if (this.props.onClose) { - this.props.onClose(); - } - } - - render() { - return ( - <div className='onboarding-overlay'> - <div className='arrow-up'></div> - <div className='arrow-border'/> - {this.props.children} - </div> - ); - } - -}; + render() { + return ( + <div className="onboarding-overlay"> + <div className="arrow-up" /> + <div className="arrow-border" /> + {this.props.children} + </div> + ); + } +} export default enhanceWithClickOutside(Overlay); diff --git a/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx index 2eda7e69bf..61121df335 100644 --- a/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx +++ b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx @@ -19,116 +19,159 @@ import classnames from 'classnames'; import Collapse from 'react-bootstrap/lib/Collapse.js'; class NavigationSideBar extends React.Component { - static PropTypes = { - activeItemId: PropTypes.string.isRequired, - onSelect: PropTypes.func, - onToggle: PropTypes.func, - groups: PropTypes.array, - disabled: PropTypes.bool - }; + static PropTypes = { + activeItemId: PropTypes.string.isRequired, + onSelect: PropTypes.func, + onToggle: PropTypes.func, + groups: PropTypes.array, + disabled: PropTypes.bool + }; - constructor(props) { - super(props); - this.state = { - activeItemId: null - }; - this.handleItemClicked = this.handleItemClicked.bind(this); - } + constructor(props) { + super(props); + this.state = { + activeItemId: null + }; + this.handleItemClicked = this.handleItemClicked.bind(this); + } - render() { - let {groups, activeItemId, disabled = false} = this.props; + render() { + let { groups, activeItemId, disabled = false } = this.props; - return ( - <div className={`navigation-side-content ${disabled ? 'disabled' : ''}`}> - {groups.map(group => ( - <NavigationMenu menu={group} activeItemId={activeItemId} onNavigationItemClick={this.handleItemClicked} key={'menu_' + group.id} /> - ))} - </div> - ); - } + return ( + <div + className={`navigation-side-content ${ + disabled ? 'disabled' : '' + }`}> + {groups.map(group => ( + <NavigationMenu + menu={group} + activeItemId={activeItemId} + onNavigationItemClick={this.handleItemClicked} + key={'menu_' + group.id} + /> + ))} + </div> + ); + } - handleItemClicked(event, item) { - event.stopPropagation(); - if(this.props.onToggle) { - this.props.onToggle(this.props.groups, item.id); - } - if(item.onSelect) { - item.onSelect(); - } - if(this.props.onSelect) { - this.props.onSelect(item); - } - } + handleItemClicked(event, item) { + event.stopPropagation(); + if (this.props.onToggle) { + this.props.onToggle(this.props.groups, item.id); + } + if (item.onSelect) { + item.onSelect(); + } + if (this.props.onSelect) { + this.props.onSelect(item); + } + } } class NavigationMenu extends React.Component { - static PropTypes = { - activeItemId: PropTypes.string.isRequired, - onNavigationItemClick: PropTypes.func, - menu: PropTypes.array - }; + static PropTypes = { + activeItemId: PropTypes.string.isRequired, + onNavigationItemClick: PropTypes.func, + menu: PropTypes.array + }; - render() { - const {menu, activeItemId, onNavigationItemClick} = this.props; - return ( - <div className='navigation-group' key={menu.id}> - <NavigationMenuHeader title={menu.name} /> - <NavigationMenuItems items={menu.items} activeItemId={activeItemId} onNavigationItemClick={onNavigationItemClick} /> - </div>); - } + render() { + const { menu, activeItemId, onNavigationItemClick } = this.props; + return ( + <div className="navigation-group" key={menu.id}> + <NavigationMenuHeader title={menu.name} /> + <NavigationMenuItems + items={menu.items} + activeItemId={activeItemId} + onNavigationItemClick={onNavigationItemClick} + /> + </div> + ); + } } function NavigationMenuHeader(props) { - return <div className='group-name' data-test-id='navbar-group-name'>{props.title}</div>; + return ( + <div className="group-name" data-test-id="navbar-group-name"> + {props.title} + </div> + ); } function getItemDataTestId(itemId) { - return itemId.split('|')[0]; + return itemId.split('|')[0]; } function NavigationMenuItems(props) { - const {items, activeItemId, onNavigationItemClick} = props; - return ( - <div className='navigation-group-items'> - { - items && items.map(item => (<NavigationMenuItem key={'menuItem_' + item.id} item={item} activeItemId={activeItemId} onNavigationItemClick={onNavigationItemClick} />)) - } - </div> - ); + const { items, activeItemId, onNavigationItemClick } = props; + return ( + <div className="navigation-group-items"> + {items && + items.map(item => ( + <NavigationMenuItem + key={'menuItem_' + item.id} + item={item} + activeItemId={activeItemId} + onNavigationItemClick={onNavigationItemClick} + /> + ))} + </div> + ); } function NavigationMenuItem(props) { - const {onNavigationItemClick, item, activeItemId} = props; - const isGroup = item.items && item.items.length > 0; - return ( - <div className={classnames('navigation-group-item', {'selected-item': item.id === activeItemId})} key={'item_' + item.id}> - <NavigationLink item={item} activeItemId={activeItemId} onClick={onNavigationItemClick} /> - {isGroup && <Collapse in={item.expanded} data-test-id={'navigation-group-' + getItemDataTestId(item.id)}> - <div> - {item.items.map(subItem => (<NavigationMenuItem key={'menuItem_' + subItem.id} item={subItem} onNavigationItemClick={onNavigationItemClick} activeItemId={activeItemId} />)) } - </div> - </Collapse> - } - </div> - ); + const { onNavigationItemClick, item, activeItemId } = props; + const isGroup = item.items && item.items.length > 0; + return ( + <div + className={classnames('navigation-group-item', { + 'selected-item': item.id === activeItemId + })} + key={'item_' + item.id}> + <NavigationLink + item={item} + activeItemId={activeItemId} + onClick={onNavigationItemClick} + /> + {isGroup && ( + <Collapse + in={item.expanded} + data-test-id={ + 'navigation-group-' + getItemDataTestId(item.id) + }> + <div> + {item.items.map(subItem => ( + <NavigationMenuItem + key={'menuItem_' + subItem.id} + item={subItem} + onNavigationItemClick={onNavigationItemClick} + activeItemId={activeItemId} + /> + ))} + </div> + </Collapse> + )} + </div> + ); } function NavigationLink(props) { - const {item, activeItemId, onClick} = props; - // todo should this be button - return ( - <div - key={'navAction_' + item.id} - className={classnames('navigation-group-item-name', { - 'selected': item.id === activeItemId, - 'disabled': item.disabled, - 'bold-name': item.expanded, - 'hidden': item.hidden - })} - onClick={(event) => onClick(event, item)} - data-test-id={'navbar-group-item-' + getItemDataTestId(item.id)}> - {item.name} - </div> - ); + const { item, activeItemId, onClick } = props; + // todo should this be button + return ( + <div + key={'navAction_' + item.id} + className={classnames('navigation-group-item-name', { + selected: item.id === activeItemId, + disabled: item.disabled, + 'bold-name': item.expanded, + hidden: item.hidden + })} + onClick={event => onClick(event, item)} + data-test-id={'navbar-group-item-' + getItemDataTestId(item.id)}> + {item.name} + </div> + ); } export default NavigationSideBar; diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx index 57fa86f561..c9c5789622 100644 --- a/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx +++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx @@ -17,125 +17,224 @@ import React from 'react'; import PropTypes from 'prop-types'; import i18n from 'nfvo-utils/i18n/i18n.js'; -import {actionsEnum} from './VersionControllerConstants.js'; +import { actionsEnum } from './VersionControllerConstants.js'; import ActionButtons from './components/ActionButtons.jsx'; import NotificationsView from 'sdc-app/onboarding/userNotifications/NotificationsView.jsx'; - class VersionController extends React.Component { - - static propTypes = { - version: PropTypes.object, - viewableVersions: PropTypes.array, - onVersionSwitching: PropTypes.func, - callVCAction: PropTypes.func, - onSave: PropTypes.func, - onClose: PropTypes.func, - isFormDataValid: PropTypes.bool, - onOpenCommentCommitModal: PropTypes.func, - isReadOnlyMode: PropTypes.bool - }; - - state = { - showPermissions: false, - showRevisions: false - }; - - render() { - let {version = {}, viewableVersions = [], onVersionSwitching, onMoreVersionsClick, callVCAction, onSave, isReadOnlyMode, itemPermission, - isFormDataValid, onClose, onManagePermissions, permissions = {}, userInfo, usersList, itemName, - onOpenCommentCommitModal, onOpenRevisionsModal, isManual, candidateInProcess, isArchived} = this.props; - return ( - <div className='version-controller-bar'> - <div className={`vc-container ${candidateInProcess ? 'disabled' : ''}`}> - <div className='version-status-container'> - <VersionSelector - viewableVersions={viewableVersions} - version={version} - onVersionSwitching={onVersionSwitching} - onMoreVersionsClick={() => onMoreVersionsClick({itemName, users: usersList})}/> - {isArchived && <div className='depricated-item-status'>{i18n('Archived')}</div>} - </div> - <div className='save-submit-cancel-container'> - <ActionButtons onSubmit={callVCAction ? () => this.submit(callVCAction, version) : undefined} - onRevert={callVCAction ? () => this.revert(callVCAction, version) : undefined} - onOpenRevisionsModal={onOpenRevisionsModal} - onSave={onSave ? () => onSave() : undefined} - permissions={permissions} - userInfo={userInfo} - onManagePermissions={onManagePermissions} - showPermissions={this.state.showPermissions} - onClosePermissions={()=>this.setState({showPermissions: false})} - onClickPermissions={() => this.onClickPermissions()} - onSync={callVCAction ? () => this.sync(callVCAction, version) : undefined} - onOpenCommentCommitModal={onOpenCommentCommitModal} - onCommit={callVCAction ? (comment) => this.commit(callVCAction, version, comment) : undefined} - isFormDataValid={isFormDataValid} - itemPermissions={itemPermission} - isArchived={isArchived} - isReadOnlyMode={isReadOnlyMode || candidateInProcess} - isManual={isManual} /> - <div className='vc-separator'></div> - <NotificationsView /> - {onClose && <div className='vc-nav-item-close' onClick={() => onClose()} data-test-id='vc-cancel-btn'> X</div>} - </div> - </div> - </div> - ); - } - - onClickPermissions() { - let {onOpenPermissions, usersList} = this.props; - let {showPermissions} = this.state; - let promise = showPermissions ? Promise.resolve() : onOpenPermissions({users: usersList}); - promise.then(() => this.setState({showPermissions: !showPermissions})); - } - - - submit(callVCAction, version) { - const action = actionsEnum.SUBMIT; - callVCAction(action, version); - } - - revert(callVCAction, version) { - const action = actionsEnum.REVERT; - callVCAction(action, version); - } - - sync(callVCAction, version) { - const action = actionsEnum.SYNC; - callVCAction(action, version); - } - - commit(callVCAction, version, comment) { - const action = actionsEnum.COMMIT; - callVCAction(action, version, comment); - } - - permissions() { - - } + static propTypes = { + version: PropTypes.object, + viewableVersions: PropTypes.array, + onVersionSwitching: PropTypes.func, + callVCAction: PropTypes.func, + onSave: PropTypes.func, + onClose: PropTypes.func, + isFormDataValid: PropTypes.bool, + onOpenCommentCommitModal: PropTypes.func, + isReadOnlyMode: PropTypes.bool + }; + + state = { + showPermissions: false, + showRevisions: false + }; + + render() { + let { + version = {}, + viewableVersions = [], + onVersionSwitching, + onMoreVersionsClick, + callVCAction, + onSave, + isReadOnlyMode, + itemPermission, + isFormDataValid, + onClose, + onManagePermissions, + permissions = {}, + userInfo, + usersList, + itemName, + onOpenCommentCommitModal, + onOpenRevisionsModal, + isManual, + candidateInProcess, + isArchived + } = this.props; + return ( + <div className="version-controller-bar"> + <div + className={`vc-container ${ + candidateInProcess ? 'disabled' : '' + }`}> + <div className="version-status-container"> + <VersionSelector + viewableVersions={viewableVersions} + version={version} + onVersionSwitching={onVersionSwitching} + onMoreVersionsClick={() => + onMoreVersionsClick({ + itemName, + users: usersList + }) + } + /> + {isArchived && ( + <div className="depricated-item-status"> + {i18n('Archived')} + </div> + )} + </div> + <div className="save-submit-cancel-container"> + <ActionButtons + onSubmit={ + callVCAction + ? () => this.submit(callVCAction, version) + : undefined + } + onRevert={ + callVCAction + ? () => this.revert(callVCAction, version) + : undefined + } + onOpenRevisionsModal={onOpenRevisionsModal} + onSave={onSave ? () => onSave() : undefined} + permissions={permissions} + userInfo={userInfo} + onManagePermissions={onManagePermissions} + showPermissions={this.state.showPermissions} + onClosePermissions={() => + this.setState({ showPermissions: false }) + } + onClickPermissions={() => this.onClickPermissions()} + onSync={ + callVCAction + ? () => this.sync(callVCAction, version) + : undefined + } + onOpenCommentCommitModal={onOpenCommentCommitModal} + onCommit={ + callVCAction + ? comment => + this.commit( + callVCAction, + version, + comment + ) + : undefined + } + isFormDataValid={isFormDataValid} + itemPermissions={itemPermission} + isArchived={isArchived} + isReadOnlyMode={ + isReadOnlyMode || candidateInProcess + } + isManual={isManual} + /> + <div className="vc-separator" /> + <NotificationsView /> + {onClose && ( + <div + className="vc-nav-item-close" + onClick={() => onClose()} + data-test-id="vc-cancel-btn"> + {' '} + X + </div> + )} + </div> + </div> + </div> + ); + } + + onClickPermissions() { + let { onOpenPermissions, usersList } = this.props; + let { showPermissions } = this.state; + let promise = showPermissions + ? Promise.resolve() + : onOpenPermissions({ users: usersList }); + promise.then(() => + this.setState({ showPermissions: !showPermissions }) + ); + } + + submit(callVCAction, version) { + const action = actionsEnum.SUBMIT; + callVCAction(action, version); + } + + revert(callVCAction, version) { + const action = actionsEnum.REVERT; + callVCAction(action, version); + } + + sync(callVCAction, version) { + const action = actionsEnum.SYNC; + callVCAction(action, version); + } + + commit(callVCAction, version, comment) { + const action = actionsEnum.COMMIT; + callVCAction(action, version, comment); + } + + permissions() {} } function VersionSelector(props) { - let {version = {}, onMoreVersionsClick, viewableVersions = [], onVersionSwitching} = props; - const includedVersions = viewableVersions.filter(ver => {return ver.id === version.id;}); - return (<div className='version-section-wrapper'> - <select className='version-selector' - onChange={ev => onVersionSwitching && onVersionSwitching(viewableVersions.find(version => version.id === ev.target.value))} - value={version.id} - data-test-id='vc-versions-select-box'> - {viewableVersions && viewableVersions.map(viewVersion => { - return ( - <option key={viewVersion.id} value={viewVersion.id} data-test-id='vc-version-option'>{`V ${viewVersion.name} ${viewVersion.status}`}</option> - ); - }) - } - {!includedVersions.length && - <option key={version.id} value={version.id} data-test-id='vc-selected-version-option'>{`V ${version.name} ${version.status}`}</option>} - </select> - <span onClick={onMoreVersionsClick} className='version-selector-more-versions' data-test-id='vc-versions-page-link'>{i18n('Versions Page')}</span> - </div>); + let { + version = {}, + onMoreVersionsClick, + viewableVersions = [], + onVersionSwitching + } = props; + const includedVersions = viewableVersions.filter(ver => { + return ver.id === version.id; + }); + return ( + <div className="version-section-wrapper"> + <select + className="version-selector" + onChange={ev => + onVersionSwitching && + onVersionSwitching( + viewableVersions.find( + version => version.id === ev.target.value + ) + ) + } + value={version.id} + data-test-id="vc-versions-select-box"> + {viewableVersions && + viewableVersions.map(viewVersion => { + return ( + <option + key={viewVersion.id} + value={viewVersion.id} + data-test-id="vc-version-option">{`V ${ + viewVersion.name + } ${viewVersion.status}`}</option> + ); + })} + {!includedVersions.length && ( + <option + key={version.id} + value={version.id} + data-test-id="vc-selected-version-option">{`V ${ + version.name + } ${version.status}`}</option> + )} + </select> + <span + onClick={onMoreVersionsClick} + className="version-selector-more-versions" + data-test-id="vc-versions-page-link"> + {i18n('Versions Page')} + </span> + </div> + ); } export default VersionController; diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js index ddb428a1e9..54f10c1acf 100644 --- a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js +++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js @@ -16,9 +16,9 @@ import keyMirror from 'nfvo-utils/KeyMirror.js'; export const actionsEnum = keyMirror({ - REVERT: 'Revert', - SYNC: 'Sync', - SUBMIT: 'Submit', - COMMIT: 'Commit', - CREATE_PACKAGE: 'Create_Package' + REVERT: 'Revert', + SYNC: 'Sync', + SUBMIT: 'Submit', + COMMIT: 'Commit', + CREATE_PACKAGE: 'Create_Package' }); diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/components/ActionButtons.jsx b/openecomp-ui/src/nfvo-components/panel/versionController/components/ActionButtons.jsx index b5750817ba..4dfa117803 100644 --- a/openecomp-ui/src/nfvo-components/panel/versionController/components/ActionButtons.jsx +++ b/openecomp-ui/src/nfvo-components/panel/versionController/components/ActionButtons.jsx @@ -13,7 +13,7 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import enhanceWithClickOutside from 'react-click-outside'; import i18n from 'nfvo-utils/i18n/i18n.js'; @@ -22,88 +22,171 @@ import Overlay from 'nfvo-components/overlay/Overlay.jsx'; import Permissions from './Permissions.jsx'; class ClickOutsideWrapper extends Component { - handleClickOutside() { - this.props.onClose(); - } - render() { - return <div>{this.props.children}</div>; - } + handleClickOutside() { + this.props.onClose(); + } + render() { + return <div>{this.props.children}</div>; + } } -const EnhancedClickOutsideWrapper = enhanceWithClickOutside(ClickOutsideWrapper); +const EnhancedClickOutsideWrapper = enhanceWithClickOutside( + ClickOutsideWrapper +); -const VCButton = ({name, tooltipText, disabled, onClick, dataTestId}) => { - let onClickAction = disabled ? ()=>{} : onClick; - return ( - <div className={`action-button-wrapper ${disabled ? 'disabled' : 'clickable'}`} onClick={onClickAction}> - <div className='action-buttons-svg'> - <SVGIcon label={tooltipText} labelPosition='bottom' labelClassName='action-button-label' - data-test-id={dataTestId} name={name} disabled={disabled}/> - </div> - </div> - ); +const VCButton = ({ name, tooltipText, disabled, onClick, dataTestId }) => { + let onClickAction = disabled ? () => {} : onClick; + return ( + <div + className={`action-button-wrapper ${ + disabled ? 'disabled' : 'clickable' + }`} + onClick={onClickAction}> + <div className="action-buttons-svg"> + <SVGIcon + label={tooltipText} + labelPosition="bottom" + labelClassName="action-button-label" + data-test-id={dataTestId} + name={name} + disabled={disabled} + /> + </div> + </div> + ); }; -const Separator = () => (<div className='vc-separator'></div>); +const Separator = () => <div className="vc-separator" />; -const SubmitButton = ({onClick, disabled}) => ( - <div onClick={()=>onClick()} data-test-id='vc-submit-btn' className={`vc-submit-button ${disabled ? 'disabled' : ''}`}> - <SVGIcon name='check' iconClassName='vc-v-submit' disabled={disabled} /> - {i18n('Submit')} - </div> +const SubmitButton = ({ onClick, disabled }) => ( + <div + onClick={() => onClick()} + data-test-id="vc-submit-btn" + className={`vc-submit-button ${disabled ? 'disabled' : ''}`}> + <SVGIcon name="check" iconClassName="vc-v-submit" disabled={disabled} /> + {i18n('Submit')} + </div> ); - -const ActionButtons = ({isReadOnlyMode, onSubmit, onRevert, onSave, isFormDataValid, onClickPermissions, onSync, onCommit, isArchived, - onOpenCommentCommitModal, showPermissions, onClosePermissions, permissions, onManagePermissions, userInfo, onOpenRevisionsModal, isManual, - itemPermissions: {isCertified, isCollaborator, isDirty, isOutOfSync, isUpToDate}}) => ( - <div className='action-buttons'> - <EnhancedClickOutsideWrapper onClose={onClosePermissions}> - <VCButton disabled={isManual} dataTestId='vc-permission-btn' onClick={onClickPermissions} - name='version-controller-permissions' tooltipText={i18n('Permissons')} /> - {showPermissions && - <Overlay> - <Permissions userInfo={userInfo} onManagePermissions={onManagePermissions} permissions={permissions} onClosePermissions={onClosePermissions}/> - </Overlay> - } - </EnhancedClickOutsideWrapper> - {isCollaborator && !isArchived && <div className='collaborator-action-buttons'> - <Separator /> - {onSave && <div className='vc-save-section'> - <VCButton dataTestId='vc-save-btn' onClick={() => onSave()} - name='version-controller-save' tooltipText={i18n('Save')} disabled={isReadOnlyMode || !isFormDataValid} /> - <Separator /> - </div> - } - <VCButton dataTestId='vc-sync-btn' onClick={onSync} - name='version-controller-sync' tooltipText={i18n('Sync')} disabled={!isCollaborator || isUpToDate || isCertified} /> - <VCButton dataTestId='vc-commit-btn' onClick={() => onOpenCommentCommitModal({onCommit, title: i18n('Commit')})} - name='version-controller-commit' tooltipText={i18n('Share')} disabled={isReadOnlyMode || !isDirty || isOutOfSync} /> - {onRevert && - <VCButton dataTestId='vc-revert-btn' onClick={onOpenRevisionsModal} - name='version-controller-revert' tooltipText={i18n('Revert')} disabled={isReadOnlyMode || isOutOfSync} /> - } - {onSubmit && - <div className='vc-submit-section'> - <Separator /> - <SubmitButton onClick={onSubmit} - disabled={isReadOnlyMode || isOutOfSync || !isUpToDate || isCertified} /> - </div> - } - </div>} - </div> +const ActionButtons = ({ + isReadOnlyMode, + onSubmit, + onRevert, + onSave, + isFormDataValid, + onClickPermissions, + onSync, + onCommit, + isArchived, + onOpenCommentCommitModal, + showPermissions, + onClosePermissions, + permissions, + onManagePermissions, + userInfo, + onOpenRevisionsModal, + isManual, + itemPermissions: { + isCertified, + isCollaborator, + isDirty, + isOutOfSync, + isUpToDate + } +}) => ( + <div className="action-buttons"> + <EnhancedClickOutsideWrapper onClose={onClosePermissions}> + <VCButton + disabled={isManual} + dataTestId="vc-permission-btn" + onClick={onClickPermissions} + name="version-controller-permissions" + tooltipText={i18n('Permissons')} + /> + {showPermissions && ( + <Overlay> + <Permissions + userInfo={userInfo} + onManagePermissions={onManagePermissions} + permissions={permissions} + onClosePermissions={onClosePermissions} + /> + </Overlay> + )} + </EnhancedClickOutsideWrapper> + {isCollaborator && + !isArchived && ( + <div className="collaborator-action-buttons"> + <Separator /> + {onSave && ( + <div className="vc-save-section"> + <VCButton + dataTestId="vc-save-btn" + onClick={() => onSave()} + name="version-controller-save" + tooltipText={i18n('Save')} + disabled={isReadOnlyMode || !isFormDataValid} + /> + <Separator /> + </div> + )} + <VCButton + dataTestId="vc-sync-btn" + onClick={onSync} + name="version-controller-sync" + tooltipText={i18n('Sync')} + disabled={!isCollaborator || isUpToDate || isCertified} + /> + <VCButton + dataTestId="vc-commit-btn" + onClick={() => + onOpenCommentCommitModal({ + onCommit, + title: i18n('Commit') + }) + } + name="version-controller-commit" + tooltipText={i18n('Share')} + disabled={isReadOnlyMode || !isDirty || isOutOfSync} + /> + {onRevert && ( + <VCButton + dataTestId="vc-revert-btn" + onClick={onOpenRevisionsModal} + name="version-controller-revert" + tooltipText={i18n('Revert')} + disabled={isReadOnlyMode || isOutOfSync} + /> + )} + {onSubmit && ( + <div className="vc-submit-section"> + <Separator /> + <SubmitButton + onClick={onSubmit} + disabled={ + isReadOnlyMode || + isOutOfSync || + !isUpToDate || + isCertified + } + /> + </div> + )} + </div> + )} + </div> ); ActionButtons.propTypes = { - version: PropTypes.object, - onSubmit: PropTypes.func, - onRevert: PropTypes.func, - onSave: PropTypes.func, - isLatestVersion: PropTypes.bool, - isCheckedIn: PropTypes.bool, - isCheckedOut: PropTypes.bool, - isFormDataValid: PropTypes.bool, - isReadOnlyMode: PropTypes.bool + version: PropTypes.object, + onSubmit: PropTypes.func, + onRevert: PropTypes.func, + onSave: PropTypes.func, + isLatestVersion: PropTypes.bool, + isCheckedIn: PropTypes.bool, + isCheckedOut: PropTypes.bool, + isFormDataValid: PropTypes.bool, + isReadOnlyMode: PropTypes.bool }; export default ActionButtons; diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/components/CommitCommentModal.jsx b/openecomp-ui/src/nfvo-components/panel/versionController/components/CommitCommentModal.jsx index 600eaeefaa..ae0913f3a9 100644 --- a/openecomp-ui/src/nfvo-components/panel/versionController/components/CommitCommentModal.jsx +++ b/openecomp-ui/src/nfvo-components/panel/versionController/components/CommitCommentModal.jsx @@ -15,59 +15,66 @@ */ import React from 'react'; -import {connect} from 'react-redux'; +import { connect } from 'react-redux'; import i18n from 'nfvo-utils/i18n/i18n.js'; import Form from 'nfvo-components/input/validation/Form.jsx'; import Input from 'nfvo-components/input/validation/Input.jsx'; -import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js'; +import { actionTypes as modalActionTypes } from 'nfvo-components/modal/GlobalModalConstants.js'; import keyMirror from 'nfvo-utils/KeyMirror.js'; export const CommitModalType = keyMirror({ - COMMIT: null, - COMMIT_SUBMIT: null - + COMMIT: null, + COMMIT_SUBMIT: null }); -export const mapActionToProps = (dispatch) => { - return { - onClose: () => dispatch({ - type: modalActionTypes.GLOBAL_MODAL_CLOSE - }) - }; +export const mapActionToProps = dispatch => { + return { + onClose: () => + dispatch({ + type: modalActionTypes.GLOBAL_MODAL_CLOSE + }) + }; }; class CommitCommentModal extends React.Component { + state = { + comment: '' + }; - state = { - comment: '' - }; - - render() { - const {onCommit, onClose, type} = this.props; - const [commitButtonText, descriptionText] = type === CommitModalType.COMMIT ? - [i18n('Commit'), i18n('You are about to commit your version')] : - [i18n('Commit & Submit'), i18n('You must commit your changes before the submit')]; + render() { + const { onCommit, onClose, type } = this.props; + const [commitButtonText, descriptionText] = + type === CommitModalType.COMMIT + ? [i18n('Commit'), i18n('You are about to commit your version')] + : [ + i18n('Commit & Submit'), + i18n('You must commit your changes before the submit') + ]; - return ( - <Form - ref='validationForm' - hasButtons={true} - onSubmit={ () => {onCommit(this.state.comment); onClose();} } - onReset={onClose} - submitButtonText={commitButtonText} - labledButtons={true} - isValid={true} - className='comment-commit-form'> - <div className='commit-modal-text'>{descriptionText}</div> - <Input - data-test-id='commit-comment-text' - onChange={comment => this.setState({comment: comment})} - label={i18n('Enter Commit Comment:')} - value={this.state.comment} - type='textarea'/> - </Form> - ); - } + return ( + <Form + ref="validationForm" + hasButtons={true} + onSubmit={() => { + onCommit(this.state.comment); + onClose(); + }} + onReset={onClose} + submitButtonText={commitButtonText} + labledButtons={true} + isValid={true} + className="comment-commit-form"> + <div className="commit-modal-text">{descriptionText}</div> + <Input + data-test-id="commit-comment-text" + onChange={comment => this.setState({ comment: comment })} + label={i18n('Enter Commit Comment:')} + value={this.state.comment} + type="textarea" + /> + </Form> + ); + } } export default connect(null, mapActionToProps)(CommitCommentModal); diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/components/Permissions.jsx b/openecomp-ui/src/nfvo-components/panel/versionController/components/Permissions.jsx index 952bd4fb58..6e0ae8187b 100644 --- a/openecomp-ui/src/nfvo-components/panel/versionController/components/Permissions.jsx +++ b/openecomp-ui/src/nfvo-components/panel/versionController/components/Permissions.jsx @@ -18,48 +18,76 @@ import React from 'react'; import i18n from 'nfvo-utils/i18n/i18n.js'; import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; -const Contributor = ({name, role, id, userInfo}) => { +const Contributor = ({ name, role, id, userInfo }) => { + const selected = id === userInfo.userId ? 'selected' : ''; - const selected = id === userInfo.userId ? 'selected' : ''; - - return( - <div className='contributor'> - <div className='contributor-content'> - <div className={`contributor-icon-circle ${selected}`}> - <div className={`contributer-icon ${selected}`}> - <SVGIcon name='user'/> - </div> - </div> - <div className='contributer-info'> - <div className='contributer-name'>{name}</div> - <div className='contributer-role'><p>{role}</p></div> - </div> - </div> - </div> - ); + return ( + <div className="contributor"> + <div className="contributor-content"> + <div className={`contributor-icon-circle ${selected}`}> + <div className={`contributer-icon ${selected}`}> + <SVGIcon name="user" /> + </div> + </div> + <div className="contributer-info"> + <div className="contributer-name">{name}</div> + <div className="contributer-role"> + <p>{role}</p> + </div> + </div> + </div> + </div> + ); }; -const Permissions = ({permissions: {owner, contributors}, onManagePermissions, userInfo, onClosePermissions}) => { - - return ( - <div className='permissions-overlay'> - <div className='permissions-overlay-header'> - <h4 className='permissions-overlay-header-title'>{i18n('PERMISSIONS')}</h4> - </div> - <div className='permissions-overlay-content'> - <Contributor userInfo={userInfo} id={owner.userId} key={owner.fullName} name={owner.fullName} role={owner.role}/> - {contributors.map(item => item.userId !== owner.userId && <Contributor userInfo={userInfo} id={item.userId} key={item.fullName} name={item.fullName} role={item.role}/>)} - </div> - <div className='permissions-overlay-footer'> - { - owner.userId === userInfo.userId && - <div onClick={() => { onClosePermissions(); onManagePermissions(); }} className='manage-permissions-btn'> - {i18n('Manage Permissions')} - </div> - } - </div> - </div> - ); +const Permissions = ({ + permissions: { owner, contributors }, + onManagePermissions, + userInfo, + onClosePermissions +}) => { + return ( + <div className="permissions-overlay"> + <div className="permissions-overlay-header"> + <h4 className="permissions-overlay-header-title"> + {i18n('PERMISSIONS')} + </h4> + </div> + <div className="permissions-overlay-content"> + <Contributor + userInfo={userInfo} + id={owner.userId} + key={owner.fullName} + name={owner.fullName} + role={owner.role} + /> + {contributors.map( + item => + item.userId !== owner.userId && ( + <Contributor + userInfo={userInfo} + id={item.userId} + key={item.fullName} + name={item.fullName} + role={item.role} + /> + ) + )} + </div> + <div className="permissions-overlay-footer"> + {owner.userId === userInfo.userId && ( + <div + onClick={() => { + onClosePermissions(); + onManagePermissions(); + }} + className="manage-permissions-btn"> + {i18n('Manage Permissions')} + </div> + )} + </div> + </div> + ); }; export default Permissions; diff --git a/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx index ca5cb3d765..cb10bb565e 100644 --- a/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx +++ b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx @@ -17,22 +17,25 @@ import React from 'react'; import PropTypes from 'prop-types'; class ProgressBar extends React.Component { - static propTypes = { - label: PropTypes.string, - now: PropTypes.string.isRequired - } - render() { - let {label, now} = this.props; + static propTypes = { + label: PropTypes.string, + now: PropTypes.string.isRequired + }; + render() { + let { label, now } = this.props; - return( - <div className='progress-bar-view'> - <div className='progress-bar-outside'> - <div style={{width: now + '%'}} className='progress-bar-inside'></div> - </div> - <div className='progress-bar-view-label'>{label}</div> - </div> - ); - } + return ( + <div className="progress-bar-view"> + <div className="progress-bar-outside"> + <div + style={{ width: now + '%' }} + className="progress-bar-inside" + /> + </div> + <div className="progress-bar-view-label">{label}</div> + </div> + ); + } } export default ProgressBar; diff --git a/openecomp-ui/src/nfvo-components/table/SelectActionTable.jsx b/openecomp-ui/src/nfvo-components/table/SelectActionTable.jsx index 3f8dbba53a..ee8a9dca45 100644 --- a/openecomp-ui/src/nfvo-components/table/SelectActionTable.jsx +++ b/openecomp-ui/src/nfvo-components/table/SelectActionTable.jsx @@ -5,26 +5,57 @@ import Button from 'sdc-ui/lib/react/Button.js'; import uuid from 'uuid-js'; export default class SelectActionTable extends React.Component { - - render() { - let {columns, onAdd, isReadOnlyMode, children, onAddItem, numOfIcons} = this.props; - return ( - <div className={`select-action-table-view ${isReadOnlyMode ? 'disabled' : ''}`}> - <div className='select-action-table-controllers'> - {onAdd && onAddItem && - <Button btnType='link' disabled={isReadOnlyMode === true} color='primary' iconName='plus' data-test-id='select-action-table-add' onClick={onAdd}>{onAddItem}</Button>} - <SVGIcon name='trashO' className='dummy-icon' /> - </div> - <div className='select-action-table'> - <div className='select-action-table-headers'> - {columns.map(column => <div key={uuid.create()} className='select-action-table-header'>{i18n(column)}</div>)} - {Array(numOfIcons).fill().map((e, i) => <SVGIcon name='trash-o' key={i} className='dummy-icon' />)} - </div> - <div className='select-action-table-body'> - {children} - </div> - </div> - </div> - ); - } + render() { + let { + columns, + onAdd, + isReadOnlyMode, + children, + onAddItem, + numOfIcons + } = this.props; + return ( + <div + className={`select-action-table-view ${ + isReadOnlyMode ? 'disabled' : '' + }`}> + <div className="select-action-table-controllers"> + {onAdd && + onAddItem && ( + <Button + btnType="link" + disabled={isReadOnlyMode === true} + color="primary" + iconName="plus" + data-test-id="select-action-table-add" + onClick={onAdd}> + {onAddItem} + </Button> + )} + <SVGIcon name="trashO" className="dummy-icon" /> + </div> + <div className="select-action-table"> + <div className="select-action-table-headers"> + {columns.map(column => ( + <div + key={uuid.create()} + className="select-action-table-header"> + {i18n(column)} + </div> + ))} + {Array(numOfIcons) + .fill() + .map((e, i) => ( + <SVGIcon + name="trash-o" + key={i} + className="dummy-icon" + /> + ))} + </div> + <div className="select-action-table-body">{children}</div> + </div> + </div> + ); + } } diff --git a/openecomp-ui/src/nfvo-components/table/SelectActionTableCell.jsx b/openecomp-ui/src/nfvo-components/table/SelectActionTableCell.jsx index 2664c8e944..20e4f2413c 100644 --- a/openecomp-ui/src/nfvo-components/table/SelectActionTableCell.jsx +++ b/openecomp-ui/src/nfvo-components/table/SelectActionTableCell.jsx @@ -1,20 +1,28 @@ import React from 'react'; import SelectInput from 'nfvo-components/input/SelectInput.jsx'; -const SelectActionTableCell = ({options, selected, disabled, onChange, clearable = true, placeholder}) => { - return ( - <div className='select-action-table-cell'> - <SelectInput - placeholder={placeholder} - type='select' - value={selected} - data-test-id='select-action-table-dropdown' - disabled={disabled} - onChange={option => onChange(option ? option.value : null)} - clearable={clearable} - options={options} /> - </div> - ); +const SelectActionTableCell = ({ + options, + selected, + disabled, + onChange, + clearable = true, + placeholder +}) => { + return ( + <div className="select-action-table-cell"> + <SelectInput + placeholder={placeholder} + type="select" + value={selected} + data-test-id="select-action-table-dropdown" + disabled={disabled} + onChange={option => onChange(option ? option.value : null)} + clearable={clearable} + options={options} + /> + </div> + ); }; export default SelectActionTableCell; diff --git a/openecomp-ui/src/nfvo-components/table/SelectActionTableRow.jsx b/openecomp-ui/src/nfvo-components/table/SelectActionTableRow.jsx index 260d39d31c..1c2c1529f2 100644 --- a/openecomp-ui/src/nfvo-components/table/SelectActionTableRow.jsx +++ b/openecomp-ui/src/nfvo-components/table/SelectActionTableRow.jsx @@ -3,38 +3,65 @@ import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger.js'; import Tooltip from 'react-bootstrap/lib/Tooltip.js'; -function tooltip (msg) { - return ( - <Tooltip className='select-action-table-error-tooltip' id='error-tooltip'>{msg}</Tooltip> - ); -}; +function tooltip(msg) { + return ( + <Tooltip + className="select-action-table-error-tooltip" + id="error-tooltip"> + {msg} + </Tooltip> + ); +} -const IconWithOverlay = ({overlayMsg}) => ( - <OverlayTrigger placement='bottom' overlay={tooltip(overlayMsg)}> - <SVGIcon name='errorCircle' color='negative'/> - </OverlayTrigger> +const IconWithOverlay = ({ overlayMsg }) => ( + <OverlayTrigger placement="bottom" overlay={tooltip(overlayMsg)}> + <SVGIcon name="errorCircle" color="negative" /> + </OverlayTrigger> ); -function renderErrorOrCheck({hasError, overlayMsg}) { - if (hasError === undefined) { - return <SVGIcon name='angleRight' className='dummy-icon' />; - } +function renderErrorOrCheck({ hasError, overlayMsg }) { + if (hasError === undefined) { + return <SVGIcon name="angleRight" className="dummy-icon" />; + } - if (hasError) { - return overlayMsg ? <IconWithOverlay overlayMsg={overlayMsg}/> : <SVGIcon color='negative' name='errorCircle'/>; - } + if (hasError) { + return overlayMsg ? ( + <IconWithOverlay overlayMsg={overlayMsg} /> + ) : ( + <SVGIcon color="negative" name="errorCircle" /> + ); + } - return <SVGIcon name='checkCircle' color='positive'/>; + return <SVGIcon name="checkCircle" color="positive" />; } -const SelectActionTableRow = ({children, actionIcon, onAction, showAction, hasError, hasErrorIndication, overlayMsg}) => ( - <div className='select-action-table-row-wrapper'> - <div className={`select-action-table-row ${hasError ? 'has-error' : ''}`}> - {children} - </div> - {onAction && <SVGIcon color='secondary' name={actionIcon} data-test-id={`select-action-table-${actionIcon}`} onClick={onAction} iconClassName={(showAction) ? '' : 'hideDelete'}/>} - {hasErrorIndication && renderErrorOrCheck({hasError, overlayMsg})} - </div> +const SelectActionTableRow = ({ + children, + actionIcon, + onAction, + showAction, + hasError, + hasErrorIndication, + overlayMsg +}) => ( + <div className="select-action-table-row-wrapper"> + <div + className={`select-action-table-row ${ + hasError ? 'has-error' : '' + }`}> + {children} + </div> + {onAction && ( + <SVGIcon + color="secondary" + name={actionIcon} + data-test-id={`select-action-table-${actionIcon}`} + onClick={onAction} + iconClassName={showAction ? '' : 'hideDelete'} + /> + )} + {hasErrorIndication && renderErrorOrCheck({ hasError, overlayMsg })} + </div> ); export default SelectActionTableRow; diff --git a/openecomp-ui/src/nfvo-components/tree/Tree.jsx b/openecomp-ui/src/nfvo-components/tree/Tree.jsx index 682f3b6d50..39434fcdf1 100644 --- a/openecomp-ui/src/nfvo-components/tree/Tree.jsx +++ b/openecomp-ui/src/nfvo-components/tree/Tree.jsx @@ -1,16 +1,28 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import {select} from 'd3-selection'; -import {tree, stratify} from 'd3-hierarchy'; - +import { select } from 'd3-selection'; +import { tree, stratify } from 'd3-hierarchy'; function diagonal(d) { - - const offset = 50; - return 'M' + d.y + ',' + d.x - + 'C' + (d.parent.y + offset) + ',' + d.x - + ' ' + (d.parent.y + offset) + ',' + d.parent.x - + ' ' + d.parent.y + ',' + d.parent.x; + const offset = 50; + return ( + 'M' + + d.y + + ',' + + d.x + + 'C' + + (d.parent.y + offset) + + ',' + + d.x + + ' ' + + (d.parent.y + offset) + + ',' + + d.parent.x + + ' ' + + d.parent.y + + ',' + + d.parent.x + ); } const nodeRadius = 8; @@ -18,164 +30,226 @@ const verticalSpaceBetweenNodes = 70; const NARROW_HORIZONTAL_SPACES = 47; const WIDE_HORIZONTAL_SPACES = 65; -const stratifyFn = stratify().id(d => d.id).parentId(d => d.parent); +const stratifyFn = stratify() + .id(d => d.id) + .parentId(d => d.parent); class Tree extends Component { - - // state = { - // startingCoordinates: null, - // isDown: false - // } - - static propTypes = { - name: PropTypes.string, - width: PropTypes.number, - allowScaleWidth: PropTypes.bool, - nodes: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - parent: PropTypes.string - })), - selectedNodeId: PropTypes.string, - onNodeClick: PropTypes.func, - onRenderedBeyondWidth: PropTypes.func - }; - - static defaultProps = { - width: 500, - allowScaleWidth : true, - name: 'default-name' - }; - - render() { - let {width, name, scrollable = false} = this.props; - return ( - <div - className={`tree-view ${name}-container ${scrollable ? 'scrollable' : ''}`}> - <svg width={width} className={name}></svg> - </div> - ); - } - - componentDidMount() { - this.renderTree(); - } - - // handleMouseMove(e) { - // if (!this.state.isDown) { - // return; - // } - // const container = select(`.tree-view.${this.props.name}-container`); - // let coordinates = this.getCoordinates(e); - // container.property('scrollLeft' , container.property('scrollLeft') + coordinates.x - this.state.startingCoordinates.x); - // container.property('scrollTop' , container.property('scrollTop') + coordinates.y - this.state.startingCoordinates.y); - // } - - // handleMouseDown(e) { - // let startingCoordinates = this.getCoordinates(e); - // this.setState({ - // startingCoordinates, - // isDown: true - // }); - // } - - // handleMouseUp() { - // this.setState({ - // startingCorrdinates: null, - // isDown: false - // }); - // } - - // getCoordinates(e) { - // var bounds = e.target.getBoundingClientRect(); - // var x = e.clientX - bounds.left; - // var y = e.clientY - bounds.top; - // return {x, y}; - // } - - componentDidUpdate(prevProps) { - if (this.props.nodes.length !== prevProps.nodes.length || - this.props.selectedNodeId !== prevProps.selectedNodeId) { - console.log('update'); - this.renderTree(); - } - } - - renderTree() { - let {width, nodes, name, allowScaleWidth, selectedNodeId, onRenderedBeyondWidth, toWiden} = this.props; - if (nodes.length > 0) { - - let horizontalSpaceBetweenLeaves = toWiden ? WIDE_HORIZONTAL_SPACES : NARROW_HORIZONTAL_SPACES; - const treeFn = tree().nodeSize([horizontalSpaceBetweenLeaves, verticalSpaceBetweenNodes]);//.size([width - 50, height - 50]) - let root = stratifyFn(nodes).sort((a, b) => a.data.name.localeCompare(b.data.name)); - let svgHeight = verticalSpaceBetweenNodes * root.height + nodeRadius * 6; - - treeFn(root); - - let nodesXValue = root.descendants().map(node => node.x); - let maxX = Math.max(...nodesXValue); - let minX = Math.min(...nodesXValue); - - let svgTempWidth = (maxX - minX) / 30 * (horizontalSpaceBetweenLeaves); - let svgWidth = svgTempWidth < width ? (width - 5) : svgTempWidth; - const svgEL = select(`svg.${name}`); - const container = select(`.tree-view.${name}-container`); - svgEL.html(''); - svgEL.attr('height', svgHeight); - let canvasWidth = width; - if (svgTempWidth > width) { - if (allowScaleWidth) { - canvasWidth = svgTempWidth; - } - // we seems to have a margin of 25px that we can still see with text - if (((svgTempWidth - 25) > width) && onRenderedBeyondWidth !== undefined) { - onRenderedBeyondWidth(); - } - }; - svgEL.attr('width', canvasWidth); - let rootGroup = svgEL.append('g').attr('transform', `translate(${svgWidth / 2 + nodeRadius},${nodeRadius * 4}) rotate(90)`); - - // handle link - rootGroup.selectAll('.link') - .data(root.descendants().slice(1)) - .enter().append('path') - .attr('class', 'link') - .attr('d', diagonal); - - let node = rootGroup.selectAll('.node') - .data(root.descendants()) - .enter().append('g') - .attr('class', node => `node ${node.children ? ' has-children' : ' leaf'} ${node.id === selectedNodeId ? 'selectedNode' : ''} ${this.props.onNodeClick ? 'clickable' : ''}`) - .attr('transform', node => 'translate(' + node.y + ',' + node.x + ')') - .on('click', node => this.onNodeClick(node)); - - node.append('circle').attr('r', nodeRadius).attr('class', 'outer-circle'); - node.append('circle').attr('r', nodeRadius - 3).attr('class', 'inner-circle'); - - node.append('text') - .attr('y', nodeRadius / 4 + 1) - .attr('x', - nodeRadius * 1.8) - .text(node => node.data.name) - .attr('transform', 'rotate(-90)'); - - let selectedNode = selectedNodeId ? root.descendants().find(node => node.id === selectedNodeId) : null; - if (selectedNode) { - - container.property('scrollLeft', (svgWidth / 4) + (svgWidth / 4 - 100) - (selectedNode.x / 30 * horizontalSpaceBetweenLeaves)); - container.property('scrollTop', (selectedNode.y / 100 * verticalSpaceBetweenNodes)); - - } else { - container.property('scrollLeft', (svgWidth / 4) + (svgWidth / 4 - 100)); - } - } - } - - onNodeClick(node) { - if (this.props.onNodeClick) { - this.props.onNodeClick(node.data); - } - } - + // state = { + // startingCoordinates: null, + // isDown: false + // } + + static propTypes = { + name: PropTypes.string, + width: PropTypes.number, + allowScaleWidth: PropTypes.bool, + nodes: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + parent: PropTypes.string + }) + ), + selectedNodeId: PropTypes.string, + onNodeClick: PropTypes.func, + onRenderedBeyondWidth: PropTypes.func + }; + + static defaultProps = { + width: 500, + allowScaleWidth: true, + name: 'default-name' + }; + + render() { + let { width, name, scrollable = false } = this.props; + return ( + <div + className={`tree-view ${name}-container ${ + scrollable ? 'scrollable' : '' + }`}> + <svg width={width} className={name} /> + </div> + ); + } + + componentDidMount() { + this.renderTree(); + } + + // handleMouseMove(e) { + // if (!this.state.isDown) { + // return; + // } + // const container = select(`.tree-view.${this.props.name}-container`); + // let coordinates = this.getCoordinates(e); + // container.property('scrollLeft' , container.property('scrollLeft') + coordinates.x - this.state.startingCoordinates.x); + // container.property('scrollTop' , container.property('scrollTop') + coordinates.y - this.state.startingCoordinates.y); + // } + + // handleMouseDown(e) { + // let startingCoordinates = this.getCoordinates(e); + // this.setState({ + // startingCoordinates, + // isDown: true + // }); + // } + + // handleMouseUp() { + // this.setState({ + // startingCorrdinates: null, + // isDown: false + // }); + // } + + // getCoordinates(e) { + // var bounds = e.target.getBoundingClientRect(); + // var x = e.clientX - bounds.left; + // var y = e.clientY - bounds.top; + // return {x, y}; + // } + + componentDidUpdate(prevProps) { + if ( + this.props.nodes.length !== prevProps.nodes.length || + this.props.selectedNodeId !== prevProps.selectedNodeId + ) { + console.log('update'); + this.renderTree(); + } + } + + renderTree() { + let { + width, + nodes, + name, + allowScaleWidth, + selectedNodeId, + onRenderedBeyondWidth, + toWiden + } = this.props; + if (nodes.length > 0) { + let horizontalSpaceBetweenLeaves = toWiden + ? WIDE_HORIZONTAL_SPACES + : NARROW_HORIZONTAL_SPACES; + const treeFn = tree().nodeSize([ + horizontalSpaceBetweenLeaves, + verticalSpaceBetweenNodes + ]); //.size([width - 50, height - 50]) + let root = stratifyFn(nodes).sort((a, b) => + a.data.name.localeCompare(b.data.name) + ); + let svgHeight = + verticalSpaceBetweenNodes * root.height + nodeRadius * 6; + + treeFn(root); + + let nodesXValue = root.descendants().map(node => node.x); + let maxX = Math.max(...nodesXValue); + let minX = Math.min(...nodesXValue); + + let svgTempWidth = + (maxX - minX) / 30 * horizontalSpaceBetweenLeaves; + let svgWidth = svgTempWidth < width ? width - 5 : svgTempWidth; + const svgEL = select(`svg.${name}`); + const container = select(`.tree-view.${name}-container`); + svgEL.html(''); + svgEL.attr('height', svgHeight); + let canvasWidth = width; + if (svgTempWidth > width) { + if (allowScaleWidth) { + canvasWidth = svgTempWidth; + } + // we seems to have a margin of 25px that we can still see with text + if ( + svgTempWidth - 25 > width && + onRenderedBeyondWidth !== undefined + ) { + onRenderedBeyondWidth(); + } + } + svgEL.attr('width', canvasWidth); + let rootGroup = svgEL + .append('g') + .attr( + 'transform', + `translate(${svgWidth / 2 + nodeRadius},${nodeRadius * + 4}) rotate(90)` + ); + + // handle link + rootGroup + .selectAll('.link') + .data(root.descendants().slice(1)) + .enter() + .append('path') + .attr('class', 'link') + .attr('d', diagonal); + + let node = rootGroup + .selectAll('.node') + .data(root.descendants()) + .enter() + .append('g') + .attr( + 'class', + node => + `node ${node.children ? ' has-children' : ' leaf'} ${ + node.id === selectedNodeId ? 'selectedNode' : '' + } ${this.props.onNodeClick ? 'clickable' : ''}` + ) + .attr( + 'transform', + node => 'translate(' + node.y + ',' + node.x + ')' + ) + .on('click', node => this.onNodeClick(node)); + + node + .append('circle') + .attr('r', nodeRadius) + .attr('class', 'outer-circle'); + node + .append('circle') + .attr('r', nodeRadius - 3) + .attr('class', 'inner-circle'); + + node + .append('text') + .attr('y', nodeRadius / 4 + 1) + .attr('x', -nodeRadius * 1.8) + .text(node => node.data.name) + .attr('transform', 'rotate(-90)'); + + let selectedNode = selectedNodeId + ? root.descendants().find(node => node.id === selectedNodeId) + : null; + if (selectedNode) { + container.property( + 'scrollLeft', + svgWidth / 4 + + (svgWidth / 4 - 100) - + selectedNode.x / 30 * horizontalSpaceBetweenLeaves + ); + container.property( + 'scrollTop', + selectedNode.y / 100 * verticalSpaceBetweenNodes + ); + } else { + container.property( + 'scrollLeft', + svgWidth / 4 + (svgWidth / 4 - 100) + ); + } + } + } + + onNodeClick(node) { + if (this.props.onNodeClick) { + this.props.onNodeClick(node.data); + } + } } export default Tree; diff --git a/openecomp-ui/src/nfvo-components/tree/Tree.stories.js b/openecomp-ui/src/nfvo-components/tree/Tree.stories.js index b29920b3ec..aca1a13402 100644 --- a/openecomp-ui/src/nfvo-components/tree/Tree.stories.js +++ b/openecomp-ui/src/nfvo-components/tree/Tree.stories.js @@ -1,119 +1,141 @@ import React from 'react'; -import {storiesOf} from '@kadira/storybook'; -import {withKnobs} from '@kadira/storybook-addon-knobs'; +import { storiesOf } from '@kadira/storybook'; +import { withKnobs } from '@kadira/storybook-addon-knobs'; import Tree from './Tree.jsx'; -import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; const stories = storiesOf('Version Tree', module); stories.addDecorator(withKnobs); const response = { - listCount: 6, - results: [ - { - 'id': '123', - 'name': '1.0', - 'description': 'string', - 'baseId': '', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '1234', - 'name': '1.1', - 'description': 'string', - 'baseId': '123', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '12345', - 'name': '2.0', - 'description': 'string', - 'baseId': '123', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '123456', - 'name': '3.0', - 'description': 'string', - 'baseId': '12345', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '1234567', - 'name': '1.2', - 'description': 'string', - 'baseId': '1234', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '12345678', - 'name': '2.1', - 'description': 'string', - 'baseId': '12345', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '123456789', - 'name': '4.0', - 'description': 'string', - 'baseId': '123456', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '12345678910', - 'name': '3.1', - 'description': 'string', - 'baseId': '123456', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - } - ] + listCount: 6, + results: [ + { + id: '123', + name: '1.0', + description: 'string', + baseId: '', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '1234', + name: '1.1', + description: 'string', + baseId: '123', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '12345', + name: '2.0', + description: 'string', + baseId: '123', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '123456', + name: '3.0', + description: 'string', + baseId: '12345', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '1234567', + name: '1.2', + description: 'string', + baseId: '1234', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '12345678', + name: '2.1', + description: 'string', + baseId: '12345', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '123456789', + name: '4.0', + description: 'string', + baseId: '123456', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '12345678910', + name: '3.1', + description: 'string', + baseId: '123456', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + } + ] }; -const divStyle = { width: '200px', borderStyle: 'solid', borderColor: 'black', border: '1px solid black'}; -const tree = response.results.map(item => ({id: item.id, name: item.name, parent: item.baseId})); -const nodeClickHandler = function (node) { - window.alert(node.name); +const divStyle = { + width: '200px', + borderStyle: 'solid', + borderColor: 'black', + border: '1px solid black' }; -stories.add('Classic Version Tree', () => ( - <div> - <Tree nodes={tree} onNodeClick={nodeClickHandler} selectedNodeId={'1234'}/> - </div> -)).add('Single Version Tree', () => ( - <div> - <Tree nodes={[tree[0]]} onNodeClick={nodeClickHandler}/> - </div> -)).add('Single Path Version Tree', () => ( - <div> - <Tree nodes={[tree[0], tree[1]]} onNodeClick={nodeClickHandler}/> - </div> -)).add('Empty Tree', () => ( - <div> - <Tree nodes={[]}/> - </div> -)).add('Add Tree in Version Page Frame', () => ( - <div style={divStyle}> - Tree wider than frame<br/><br/><br/> - <Tree - name={'versions-tree'} - width={200} - nodes={tree} - onRenderedBeyondWidth={() => {console.log('rendered beyond width')}} - allowScaleWidth={false} - onNodeClick={nodeClickHandler}/> - </div> -)); +const tree = response.results.map(item => ({ + id: item.id, + name: item.name, + parent: item.baseId +})); +const nodeClickHandler = function(node) { + window.alert(node.name); +}; +stories + .add('Classic Version Tree', () => ( + <div> + <Tree + nodes={tree} + onNodeClick={nodeClickHandler} + selectedNodeId={'1234'} + /> + </div> + )) + .add('Single Version Tree', () => ( + <div> + <Tree nodes={[tree[0]]} onNodeClick={nodeClickHandler} /> + </div> + )) + .add('Single Path Version Tree', () => ( + <div> + <Tree nodes={[tree[0], tree[1]]} onNodeClick={nodeClickHandler} /> + </div> + )) + .add('Empty Tree', () => ( + <div> + <Tree nodes={[]} /> + </div> + )) + .add('Add Tree in Version Page Frame', () => ( + <div style={divStyle}> + Tree wider than frame<br /> + <br /> + <br /> + <Tree + name={'versions-tree'} + width={200} + nodes={tree} + onRenderedBeyondWidth={() => { + console.log('rendered beyond width'); + }} + allowScaleWidth={false} + onNodeClick={nodeClickHandler} + /> + </div> + )); |