RIFT-15154: Config parameter map component, intial pass. 23/923/1
authorLaurence Maultsby <laurence.maultsby@riftio.com>
Wed, 11 Jan 2017 18:29:13 +0000 (13:29 -0500)
committerLaurence Maultsby <laurence.maultsby@riftio.com>
Wed, 11 Jan 2017 18:29:13 +0000 (13:29 -0500)
Signed-off-by: Laurence Maultsby <laurence.maultsby@riftio.com>
skyquake/framework/core/modules/routes/inactivity.js
skyquake/plugins/composer/src/src/components/ConfigPrimitiveParameters/ConfigPrimitiveParameters.js
skyquake/plugins/composer/src/src/components/EditConfigParameterMap.js [new file with mode: 0644]
skyquake/plugins/composer/src/src/styles/CanvasPanelTray.scss

index 7c3c440..a90258d 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
index 3621061..848f0b4 100644 (file)
@@ -49,7 +49,7 @@ import imgAdd from '../../../../node_modules/open-iconic/svg/plus.svg'
 import imgConnection from '../../../../node_modules/open-iconic/svg/random.svg'
 import imgClassifier from '../../../../node_modules/open-iconic/svg/spreadsheet.svg'
 import imgReorder from '../../../../node_modules/open-iconic/svg/menu.svg'
-import EditDescriptorModelProperties from '../EditDescriptorModelProperties'
+import EditConfigParameterMap from '../EditConfigParameterMap'
 function configParameterMapMap(ap, i) {
 
     const context = this;
@@ -214,7 +214,7 @@ const ConfigPrimitiveParameters = React.createClass({
                     {
                         containers.map(function(c, i) {
                             if(c.className == 'ConfigParameterMap') {
-                                return <EditDescriptorModelProperties key={i} container={c} width={self.props.width} />
+                                return <EditConfigParameterMap key={i} container={c} width={self.props.width} />
                             }
                         })
                     }
diff --git a/skyquake/plugins/composer/src/src/components/EditConfigParameterMap.js b/skyquake/plugins/composer/src/src/components/EditConfigParameterMap.js
new file mode 100644 (file)
index 0000000..1add58c
--- /dev/null
@@ -0,0 +1,685 @@
+/*
+ *
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   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.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+/**
+ * Created by onvelocity on 1/18/16.
+ *
+ * This class generates the form fields used to edit the CONFD JSON model.
+ */
+'use strict';
+
+import _ from 'lodash'
+import utils from '../libraries/utils'
+import React from 'react'
+import ClassNames from 'classnames'
+import changeCase from 'change-case'
+import toggle from '../libraries/ToggleElementHandler'
+import Button from './Button'
+import Property from '../libraries/model/DescriptorModelMetaProperty'
+import ComposerAppActions from '../actions/ComposerAppActions'
+import CatalogItemsActions from '../actions/CatalogItemsActions'
+import DESCRIPTOR_MODEL_FIELDS from '../libraries/model/DescriptorModelFields'
+import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
+import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
+import SelectionManager from '../libraries/SelectionManager'
+import DeletionManager from '../libraries/DeletionManager'
+import DescriptorModelIconFactory from '../libraries/model/IconFactory'
+import getEventPath from '../libraries/getEventPath'
+import CatalogDataStore from '../stores/CatalogDataStore'
+
+import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg'
+import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
+
+import '../styles/EditDescriptorModelProperties.scss'
+
+
+
+function getDescriptorMetaBasicForType(type) {
+       const basicPropertiesFilter = d => _.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+       return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
+}
+
+function getDescriptorMetaAdvancedForType(type) {
+       const advPropertiesFilter = d => !_.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
+       return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []};
+}
+
+function getTitle(model = {}) {
+       if (typeof model['short-name'] === 'string' && model['short-name']) {
+               return model['short-name'];
+       }
+       if (typeof model.name === 'string' && model.name) {
+               return model.name;
+       }
+       if (model.uiState && typeof model.uiState.displayName === 'string' && model.uiState.displayName) {
+               return model.uiState.displayName
+       }
+       if (typeof model.id === 'string') {
+               return model.id;
+       }
+}
+function startEditing() {
+               DeletionManager.removeEventListeners();
+       }
+
+       function endEditing() {
+               DeletionManager.addEventListeners();
+       }
+
+       function onClickSelectItem(property, path, value, event) {
+               event.preventDefault();
+               const root = this.getRoot();
+               if (SelectionManager.select(value)) {
+                       CatalogItemsActions.catalogItemMetaDataChanged(root.model);
+               }
+       }
+
+       function onFocusPropertyFormInputElement(property, path, value, event) {
+
+               event.preventDefault();
+               startEditing();
+
+               function removeIsFocusedClass(event) {
+                       event.target.removeEventListener('blur', removeIsFocusedClass);
+                       Array.from(document.querySelectorAll('.-is-focused')).forEach(d => d.classList.remove('-is-focused'));
+               }
+
+               removeIsFocusedClass(event);
+
+               const propertyWrapper = getEventPath(event).reduce((parent, element) => {
+                       if (parent) {
+                               return parent;
+                       }
+                       if (!element.classList) {
+                               return false;
+                       }
+                       if (element.classList.contains('property')) {
+                               return element;
+                       }
+               }, false);
+
+               if (propertyWrapper) {
+                       propertyWrapper.classList.add('-is-focused');
+                       event.target.addEventListener('blur', removeIsFocusedClass);
+               }
+
+       }
+
+       function buildAddPropertyAction(container, property, path) {
+               function onClickAddProperty(property, path, event) {
+                       event.preventDefault();
+                       //SelectionManager.resume();
+                       const create = Property.getContainerCreateMethod(property, this);
+                       if (create) {
+                               const model = null;
+                               create(model, path, property);
+                       } else {
+                               const name = path.join('.');
+                               const value = Property.createModelInstance(property);
+                               utils.assignPathValue(this.model, name, value);
+                       }
+                       CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+               }
+               return (
+                               <Button className="inline-hint" onClick={onClickAddProperty.bind(container, property, path)} label="Add" src={imgAdd} />
+               );
+       }
+
+       function buildRemovePropertyAction(container, property, path) {
+               function onClickRemoveProperty(property, path, event) {
+                       event.preventDefault();
+                       const name = path.join('.');
+                       const removeMethod = Property.getContainerMethod(property, this, 'remove');
+                       if (removeMethod) {
+                               removeMethod(utils.resolvePath(this.model, name));
+                       } else {
+                               utils.removePathValue(this.model, name);
+                       }
+                       CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+               }
+               return (
+                       <Button className="remove-property-action inline-hint" title="Remove" onClick={onClickRemoveProperty.bind(container, property, path)} label="Remove" src={imgRemove}/>
+               );
+       }
+
+       function onFormFieldValueChanged(event) {
+               if (DescriptorModelFactory.isContainer(this)) {
+                       event.preventDefault();
+                       const name = event.target.name;
+                       const value = event.target.value;
+                       utils.assignPathValue(this.model, name, value);
+                       CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+               }
+       }
+
+       function buildField(container, property, path, value, fieldKey) {
+               let cds = CatalogDataStore;
+               let catalogs = cds.getTransientCatalogs();
+
+               const name = path.join('.');
+               const isEditable = true;
+               const isGuid = Property.isGuid(property);
+               const onChange = onFormFieldValueChanged.bind(container);
+               const isEnumeration = Property.isEnumeration(property);
+               const isLeafRef = Property.isLeafRef(property);
+               const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
+               const placeholder = changeCase.title(property.name);
+               const className = ClassNames(property.name + '-input', {'-is-guid': isGuid});
+               const fieldValue = value ? (value.constructor.name != "Object") ? value : '' : undefined;
+               if (isEnumeration) {
+                       const enumeration = Property.getEnumeration(property, value);
+                       const options = enumeration.map((d, i) => {
+                               // note yangforge generates values for enums but the system does not use them
+                               // so we categorically ignore them
+                               // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
+                               //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
+                               return <option key={fieldKey.toString() + ':' + i} value={d.name}>{d.name}</option>;
+                       });
+                       const isValueSet = enumeration.filter(d => d.isSelected).length > 0;
+                       if (!isValueSet || property.cardinality === '0..1') {
+                               const noValueDisplayText = changeCase.title(property.name);
+                               options.unshift(<option key={'(value-not-in-enum)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+                       }
+                       return <select key={fieldKey.toString()} id={fieldKey.toString()} className={ClassNames({'-value-not-set': !isValueSet})} name={name} value={value} title={name} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} readOnly={!isEditable}>{options}</select>;
+               }
+
+               if (isLeafRef) {
+                       let fullFieldKey = _.isArray(fieldKey) ? fieldKey.join(':') : fieldKey;
+                       let containerRef = container;
+                       while (containerRef.parent) {
+                               fullFieldKey = containerRef.parent.key + ':' + fullFieldKey;
+                               containerRef = containerRef.parent;
+                       }
+                       const leafRefPathValues = Property.getLeafRef(property, path, value, fullFieldKey, catalogs, container);
+
+                       const options = leafRefPathValues && leafRefPathValues.map((d, i) => {
+                               return <option key={fieldKey.toString() + ':' + i} value={d.value}>{d.value}</option>;
+                       });
+                       const isValueSet = leafRefPathValues.filter(d => d.isSelected).length > 0;
+                       if (!isValueSet || property.cardinality === '0..1') {
+                               const noValueDisplayText = changeCase.title(property.name);
+                               options.unshift(<option key={'(value-not-in-leafref)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
+                       }
+                       return <select key={fieldKey.toString()} id={fieldKey.toString()} className={ClassNames({'-value-not-set': !isValueSet})} name={name} value={value} title={name} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} readOnly={!isEditable}>{options}</select>;
+               }
+
+               if (property['preserve-line-breaks']) {
+                       return <textarea key={fieldKey.toString()} cols="5" id={fieldKey.toString()} name={name} value={value} placeholder={placeholder} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} onMouseOut={endEditing} onMouseLeave={endEditing} readOnly={!isEditable} />;
+               }
+
+               return <input key={fieldKey.toString()}
+                                         id={fieldKey.toString()}
+                                         type="text"
+                                         name={name}
+                                         value={fieldValue}
+                                         className={className}
+                                         placeholder={placeholder}
+                                         onChange={onChange}
+                                         onFocus={onFocus}
+                                         onBlur={endEditing}
+                                         onMouseDown={startEditing}
+                                         onMouseOver={startEditing}
+                                         onMouseOut={endEditing}
+                                         onMouseLeave={endEditing}
+                                         readOnly={!isEditable}
+               />;
+
+       }
+
+       function buildElement(container, property, valuePath, value) {
+               return property.properties.map((property, index) => {
+                       let childValue;
+                       const childPath = valuePath.slice();
+                       if (typeof value === 'object') {
+                               childValue = value[property.name];
+                       }
+                       if(property.type != 'choice'){
+                                               childPath.push(property.name);
+                       }
+                       return build(container, property, childPath, childValue);
+
+               });
+       }
+
+    function buildChoice(container, property, path, value, key, props={}) {
+
+               function onFormFieldValueChanged(event) {
+                       if (DescriptorModelFactory.isContainer(this)) {
+
+                               event.preventDefault();
+
+                               let name = event.target.name;
+                               const value = event.target.value;
+
+
+                               /*
+                                       Transient State is stored for convenience in the uiState field.
+                                       The choice yang type uses case elements to describe the "options".
+                                       A choice can only ever have one option selected which allows
+                                       the system to determine which type is selected by the name of
+                                       the element contained within the field.
+                                */
+                               /*
+                                       const stateExample = {
+                                               uiState: {
+                                                       choice: {
+                                                               'conf-config': {
+                                                                       selected: 'rest',
+                                                                       'case': {
+                                                                               rest: {},
+                                                                               netconf: {},
+                                                                               script: {}
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       };
+                               */
+                               const statePath = ['uiState.choice'].concat(name);
+                               const stateObject = utils.resolvePath(this.model, statePath.join('.')) || {};
+                               const selected = stateObject.selected ? stateObject.selected.split('.')[1] : undefined;
+                               // write state back to the model so the new state objects are captured
+                               utils.assignPathValue(this.model, statePath.join('.'), stateObject);
+
+                               // write the current choice value into the state
+                               let choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
+                               let isTopCase = false;
+                               if (!choiceObject) {
+                                       isTopCase = true;
+                                       choiceObject = utils.resolvePath(this.model, [selected].join('.'));
+                               }
+                               utils.assignPathValue(stateObject, [selected].join('.'), _.cloneDeep(choiceObject));
+
+                               if(selected) {
+                                       if(this.model.uiState.choice.hasOwnProperty(name)) {
+                                               delete this.model[selected];
+                                               utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+                                       } else {
+                                               // remove the current choice value from the model
+                                               utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
+                                       }
+                               }
+
+                               // get any state for the new selected choice
+                               const newChoiceObject = utils.resolvePath(stateObject, [value].join('.')) || {};
+
+                               // assign new choice value to the model
+                               if (isTopCase) {
+                                       utils.assignPathValue(this.model, [name, value].join('.'), newChoiceObject);
+                               } else {
+                                       utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
+                               }
+
+
+                               // update the selected name
+                               utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
+
+                               CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
+                       }
+               }
+
+               const caseByNameMap = {};
+
+               const onChange = onFormFieldValueChanged.bind(container);
+
+               const cases = property.properties.map(d => {
+                       if (d.type === 'case') {
+                               caseByNameMap[d.name] = d.properties[0];
+                               return {
+                                       optionName: d.name,
+                                       optionTitle: d.description,
+                                       //represents case name and case element name
+                                       optionValue: [d.name, d.properties[0].name].join('.')
+                               };
+                       }
+                       caseByNameMap[d.name] = d;
+                       return {optionName: d.name};
+               });
+
+               const options = [{optionName: '', optionValue: false}].concat(cases).map((d, i) => {
+                       return (
+                               <option key={i} value={d.optionValue} title={d.optionTitle}>
+                                       {d.optionName}
+                                       {i ? null : changeCase.title(property.name)}
+                               </option>
+                       );
+               });
+
+               const selectName = path.join('.');
+               let selectedOptionPath = ['uiState.choice', selectName, 'selected'].join('.');
+               //Currently selected choice/case statement on UI model
+               let selectedOptionValue = utils.resolvePath(container.model, selectedOptionPath);
+               //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
+               if(!selectedOptionValue) {
+                       //get field properties for choice on container model
+                       let fieldProperties = utils.resolvePath(container.model, selectName);
+                       if(fieldProperties) {
+                               //Check each case statement in model and see if it is present in container model.
+                               cases.map(function(c){
+                                       if(fieldProperties.hasOwnProperty(c.optionValue.split('.')[1])) {
+                                               utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), c.optionValue);
+                                       }
+                               });
+                               selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
+                       } else {
+                               property.properties.map(function(p) {
+                                       let pname = p.properties[0].name;
+                                       if(container.model.hasOwnProperty(pname)) {
+                                               utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), [p.name, pname].join('.'));
+                                       }
+                               })
+                               selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
+                       }
+               }
+               //If selectedOptionValue is present, take first item in string which represents the case name.
+               const valueProperty = caseByNameMap[selectedOptionValue ? selectedOptionValue.split('.')[0] : undefined] || {properties: []};
+               const isLeaf = Property.isLeaf(valueProperty);
+               const hasProperties = _.isArray(valueProperty.properties) && valueProperty.properties.length;
+               const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
+               //Some magic that prevents errors for arising
+               const valueResponse = valueProperty.properties.length ? valueProperty.properties.map((d, i) => {
+                       const childPath = path.concat(valueProperty.name, d.name);
+                       const childValue = utils.resolvePath(container.model, childPath.join('.'));
+                       return (
+                               <div key={childPath.concat('info', i).join(':')}>
+                                       {build(container, d, childPath, childValue, props)}
+                               </div>
+                       );
+               }) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]) : null
+               // end magic
+               const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
+
+               return (
+                       <div key={key} className="choice">
+                               <select key={Date.now()} className={ClassNames({'-value-not-set': !selectedOptionValue})} name={selectName} value={selectedOptionValue} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} onMouseOut={endEditing} onMouseLeave={endEditing}>
+                                       {options}
+                               </select>
+                               {valueResponse}
+                       </div>
+               );
+
+       }
+
+       function buildSimpleListItem(container, property, path, value, key, index) {
+               // todo need to abstract this better
+               const title = getTitle(value);
+               var req = require.context("../", true, /\.svg/);
+               return (
+                       <div>
+                               <a href="#select-list-item" key={Date.now()} className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem.bind(container, property, path, value)}>
+                                       <img src={req('./' + DescriptorModelIconFactory.getUrlForType(property.name))} width="20px" />
+                                       <span>{title}</span>
+                               </a>
+                               {buildRemovePropertyAction(container, property, path)}
+                       </div>
+               );
+       }
+
+       function buildRemoveListItem(container, property, valuePath, fieldKey, index) {
+               const className = ClassNames(property.name + '-remove actions');
+               return (
+                       <div key={fieldKey.concat(index).join(':')} className={className}>
+                               <h3>
+                                       <span className={property.type + '-name name'}>{changeCase.title(property.name)}</span>
+                                       <span className="info">{index + 1}</span>
+                                       {buildRemovePropertyAction(container, property, valuePath)}
+                               </h3>
+                       </div>
+               );
+       }
+
+       function buildLeafListItem(container, property, valuePath, value, key, index) {
+               // look at the type to determine how to parse the value
+               return (
+                       <div>
+                               {buildRemoveListItem(container, property, valuePath, key, index)}
+                               {buildField(container, property, valuePath, value, key)}
+                       </div>
+
+               );
+       }
+
+       function build(container, property, path, value, props = {}) {
+
+               const fields = [];
+               const isLeaf = Property.isLeaf(property);
+               const isArray = Property.isArray(property);
+               const isObject = Property.isObject(property);
+               const isLeafList = Property.isLeafList(property);
+               const fieldKey = [container.id].concat(path);
+               const isRequired = Property.isRequired(property);
+               const title = changeCase.titleCase(property.name);
+               const columnCount = property.properties.length || 1;
+               const isColumnar = isArray && (Math.round(props.width / columnCount) > 155);
+               const classNames = {'-is-required': isRequired, '-is-columnar': isColumnar};
+
+               if (!property.properties && isObject) {
+                       const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
+                       property.properties = uiState.properties;
+               }
+
+               const hasProperties = _.isArray(property.properties) && property.properties.length;
+               const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(property);
+
+               // ensure value is not undefined for non-leaf property types
+               if (isObject) {
+                       if (typeof value !== 'object') {
+                               value = isArray ? [] : {};
+                       }
+               }
+               const valueAsArray = _.isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
+
+               const isMetaField = property.name === 'meta';
+               const isCVNFD = property.name === 'constituent-vnfd';
+               const isSimpleListView = Property.isSimpleList(property);
+
+               valueAsArray.forEach((value, index) => {
+
+                       let field;
+                       const key = fieldKey.slice();
+                       const valuePath = path.slice();
+
+                       if (isArray) {
+                               valuePath.push(index);
+                               key.push(index);
+                       }
+
+                       if (isMetaField) {
+                               if (typeof value === 'object') {
+                                       value = JSON.stringify(value, undefined, 12);
+                               } else if (typeof value !== 'string') {
+                                       value = '{}';
+                               }
+                       }
+
+                       if (isMissingDescriptorMeta) {
+                               field = <span key={key.concat('warning').join(':')} className="warning">No Descriptor Meta for {property.name}</span>;
+                       } else if (property.type === 'choice') {
+                field = buildChoice(container, property, valuePath, value, key.join(':'), props);
+                       } else if (isSimpleListView) {
+                field = buildSimpleListItem(container, property, valuePath, value, key, index, props);
+                       } else if (isLeafList) {
+                field = buildLeafListItem(container, property, valuePath, value, key, index, props);
+                       } else if (hasProperties) {
+                field = buildElement(container, property, valuePath, value, key.join(':'), props)
+                       } else {
+                field = buildField(container, property, valuePath, value, key.join(':'), props);
+                       }
+
+                       function onClickLeaf(property, path, value, event) {
+                               if (event.isDefaultPrevented()) {
+                                       return;
+                               }
+                               event.preventDefault();
+                               event.stopPropagation();
+                               this.getRoot().uiState.focusedPropertyPath = path.join('.');
+                               console.log('property selected', path.join('.'));
+                               ComposerAppActions.propertySelected([path.join('.')]);
+                       }
+
+                       const clickHandler = isLeaf ? onClickLeaf : () => {};
+                       const isContainerList = isArray && !(isSimpleListView || isLeafList);
+
+                       fields.push(
+                               <div key={fieldKey.concat(['property-content', index]).join(':')}
+                                        className={ClassNames('property-content', {'simple-list': isSimpleListView})}
+                                        onClick={clickHandler.bind(container, property, valuePath, value)}>
+                                       {isContainerList ? buildRemoveListItem(container, property, valuePath, fieldKey, index) : null}
+                                       {field}
+                               </div>
+                       );
+
+               });
+
+               classNames['-is-leaf'] = isLeaf;
+               classNames['-is-array'] = isArray;
+               classNames['cols-' + columnCount] = isColumnar;
+
+               if (property.type === 'choice') {
+                       value = utils.resolvePath(container.model, ['uiState.choice'].concat(path, 'selected').join('.'));
+                       if(!value) {
+                               property.properties.map(function(p) {
+                                       let pname = p.properties[0].name;
+                                       if(container.model.hasOwnProperty(pname)) {
+                                               value = container.model[pname];
+                                       }
+                               })
+                       }
+               }
+
+               let displayValue = typeof value === 'object' ? '' : value;
+               const displayValueInfo = isArray ? valueAsArray.filter(d => typeof d !== 'undefined').length + ' items' : '';
+
+               const onFocus = isLeaf ? event => event.target.classList.add('-is-focused') : false;
+
+               return (
+                       <div key={fieldKey.join(':')} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
+                               <h3 className="property-label">
+                                       <label htmlFor={fieldKey.join(':')}>
+                                               <span className={property.type + '-name name'}>{title}</span>
+                                               <small>
+                                                       <span className={property.type + '-info info'}>{displayValueInfo}</span>
+                                                       <span className={property.type + '-value value'}>{displayValue}</span>
+                                               </small>
+                                               {isArray ? buildAddPropertyAction(container, property, path.concat(valueAsArray.length)) : null}
+                                       </label>
+                               </h3>
+                               <span className={property.type + '-description description'}>{property.description}</span>
+                               <val className="property-value">
+                                       {isCVNFD ? <span className={property.type + '-tip tip'}>Drag a VNFD from the Catalog to add more.</span> : null}
+                                       {fields}
+                               </val>
+                       </div>
+               );
+
+       }
+export default function EditDescriptorModelProperties(props, type) {
+
+    const container = props.container;
+
+    if (!(DescriptorModelFactory.isContainer(container))) {
+        return
+    }
+
+
+
+    const containerType = (_.isEmpty(type) ? false : type)|| container.uiState['qualified-type'] || container.uiState.type;
+       const basicProperties = getDescriptorMetaBasicForType(containerType).properties;
+
+
+       function buildAdvancedGroup() {
+               const properties = getDescriptorMetaAdvancedForType(containerType).properties;
+               if (properties.length === 0) {
+                       return null;
+               }
+               const hasBasicFields = basicProperties.length > 0;
+               const closeGroup = basicProperties.length > 0;
+               return (
+                       <div className="advanced-properties-group">
+
+                               <div className="toggleable">
+                                       {properties.map((property,i) => {
+                                               const path = [property.name];
+                                               const value = container.model[property.name];
+                                               if(path == 'id') {
+                                                       return null
+                                               }
+                                               if(path == 'config-parameter-request') {
+                                                       return (
+                                                               <div className="container-property property" key={path + '-' + i}>
+                                                                       <h3 className="property-label">
+                                                                               <span className="container-name name">{`VNF Index: ${value['member-vnf-index-ref']}`}</span>
+                                                                       </h3>
+                                                                       <val className="property-value">
+                                                                               <div className="property-content">
+                                                                                       <val className="property-value" style={{width: '100%'}}>
+                                                                                               <div className="property-content">
+                                                                                                       <div className="leaf-property property -is-leaf">
+                                                                                                               <h3 className="property-label">
+                                                                                                                       <label for={path + '-' + i}>
+                                                                                                                               <span className="leaf-name name">
+                                                                                                                                       Parameter Request
+                                                                                                                               </span>
+                                                                                                                       </label>
+                                                                                                               </h3>
+                                                                                                               <input value={value['config-parameter-request-ref']} readonly placeholder="Parameter Request" name={path + '-' + i}>
+
+                                                                                                               </input>
+                                                                                                       </div>
+                                                                                               </div>
+                                                                                       </val>
+                                                                               </div>
+                                                                       </val>
+
+                                                               </div>
+                                                       )
+                                               } else {
+                                                       return build(container, property, path, value, _.assign({toggle: true, width: props.width}, props));
+                                               }
+
+                                       })}
+                               </div>
+                               <div className="toggle-bottom-spacer" style={{visibility: 'hidden', 'height': '50%', position: 'absolute'}}>We need this so when the user closes the panel it won't shift away and scare the bj out of them!</div>
+                       </div>
+               );
+       }
+
+       function buildMoreLess(d, i) {
+               return (
+                       <span key={'bread-crumb-part-' + i}>
+                               <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
+                               <i> / </i>
+                       </span>
+               );
+       }
+
+       const path = [];
+       if (container.parent) {
+               path.push(container.parent);
+       }
+       path.push(container);
+
+       return (
+               <div className="EditDescriptorModelProperties -is-tree-view">
+                       {buildAdvancedGroup()}
+               </div>
+       );
+
+}
+export {build}
+// export buildElement;
+// export buildChoice;
index 55844b6..899eb8f 100644 (file)
@@ -30,7 +30,7 @@ $tray-head-height: 25px;
        height: 25px;
        min-width: 300px;
        /* background-color: white;*/
-       background: #cbd1d1;
+       background: $panel-bg-color;
        &.-with-transitions {
                transition: height 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000);
        }
@@ -65,14 +65,17 @@ $tray-head-height: 25px;
        &-buttons {
                display: -ms-flexbox;
                display: flex;
-               margin-top: 1px;
+               margin-top: 2px;
+               button {
+                       padding: 6px 34px;
+               }
        }
        .tray-body {
                top:31px;
        }
        .ConfigParameterMap {
 
-               background: #cbd1d1;
+               background: $panel-bg-color;
 
                .EditDescriptorModelProperties {
                    margin-left: 8px;
@@ -83,6 +86,8 @@ $tray-head-height: 25px;
                        display:flex;
                        -ms-flex-wrap: wrap;
                            flex-wrap: wrap;
+                   background: $panel-bg-color-contrast;
+                   margin:8px 0;
                        & > .leaf-property {
                            -ms-flex: 1 0 100%;
                        flex: 1 0 100%;