19af5393a84bb1a0e3a33b9231b2bb22f90395ef
[osm/UI.git] / skyquake / plugins / composer / src / src / components / EditDescriptorModelProperties.js
1 /*
2 *
3 * Copyright 2016 RIFT.IO Inc
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18 /**
19 * Created by onvelocity on 1/18/16.
20 *
21 * This class generates the form fields used to edit the CONFD JSON model.
22 */
23 'use strict';
24
25 import _ from 'lodash'
26 import utils from '../libraries/utils'
27 import React from 'react'
28 import ClassNames from 'classnames'
29 import changeCase from 'change-case'
30 import toggle from '../libraries/ToggleElementHandler'
31 import Button from './Button'
32 import Property from '../libraries/model/DescriptorModelMetaProperty'
33 import ComposerAppActions from '../actions/ComposerAppActions'
34 import CatalogItemsActions from '../actions/CatalogItemsActions'
35 import DESCRIPTOR_MODEL_FIELDS from '../libraries/model/DescriptorModelFields'
36 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
37 import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
38 import SelectionManager from '../libraries/SelectionManager'
39 import DeletionManager from '../libraries/DeletionManager'
40 import DescriptorModelIconFactory from '../libraries/model/IconFactory'
41 import getEventPath from '../libraries/getEventPath'
42 import CatalogDataStore from '../stores/CatalogDataStore'
43
44 import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg'
45 import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg'
46
47 import '../styles/EditDescriptorModelProperties.scss'
48
49
50
51 function getDescriptorMetaBasicForType(type) {
52 const basicPropertiesFilter = d => _.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
53 return DescriptorModelMetaFactory.getModelMetaForType(type, basicPropertiesFilter) || {properties: []};
54 }
55
56 function getDescriptorMetaAdvancedForType(type) {
57 const advPropertiesFilter = d => !_.includes(DESCRIPTOR_MODEL_FIELDS[type], d.name);
58 return DescriptorModelMetaFactory.getModelMetaForType(type, advPropertiesFilter) || {properties: []};
59 }
60
61 function getTitle(model = {}) {
62 if (typeof model['short-name'] === 'string' && model['short-name']) {
63 return model['short-name'];
64 }
65 if (typeof model.name === 'string' && model.name) {
66 return model.name;
67 }
68 if (model.uiState && typeof model.uiState.displayName === 'string' && model.uiState.displayName) {
69 return model.uiState.displayName
70 }
71 if (typeof model.id === 'string') {
72 return model.id;
73 }
74 }
75 function startEditing() {
76 DeletionManager.removeEventListeners();
77 }
78
79 function endEditing() {
80 DeletionManager.addEventListeners();
81 }
82
83 function onClickSelectItem(property, path, value, event) {
84 event.preventDefault();
85 const root = this.getRoot();
86 if (SelectionManager.select(value)) {
87 CatalogItemsActions.catalogItemMetaDataChanged(root.model);
88 }
89 }
90
91 function onFocusPropertyFormInputElement(property, path, value, event) {
92
93 event.preventDefault();
94 startEditing();
95
96 function removeIsFocusedClass(event) {
97 event.target.removeEventListener('blur', removeIsFocusedClass);
98 Array.from(document.querySelectorAll('.-is-focused')).forEach(d => d.classList.remove('-is-focused'));
99 }
100
101 removeIsFocusedClass(event);
102
103 const propertyWrapper = getEventPath(event).reduce((parent, element) => {
104 if (parent) {
105 return parent;
106 }
107 if (!element.classList) {
108 return false;
109 }
110 if (element.classList.contains('property')) {
111 return element;
112 }
113 }, false);
114
115 if (propertyWrapper) {
116 propertyWrapper.classList.add('-is-focused');
117 event.target.addEventListener('blur', removeIsFocusedClass);
118 }
119
120 }
121
122 function buildAddPropertyAction(container, property, path) {
123 function onClickAddProperty(property, path, event) {
124 event.preventDefault();
125 //SelectionManager.resume();
126 const create = Property.getContainerCreateMethod(property, this);
127 if (create) {
128 const model = null;
129 create(model, path, property);
130 } else {
131 const name = path.join('.');
132 const value = Property.createModelInstance(property);
133 utils.assignPathValue(this.model, name, value);
134 }
135 CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
136 }
137 return (
138 <Button className="inline-hint" onClick={onClickAddProperty.bind(container, property, path)} label="Add" src={imgAdd} />
139 );
140 }
141
142 function buildRemovePropertyAction(container, property, path) {
143 function onClickRemoveProperty(property, path, event) {
144 event.preventDefault();
145 const name = path.join('.');
146 const removeMethod = Property.getContainerMethod(property, this, 'remove');
147 if (removeMethod) {
148 removeMethod(utils.resolvePath(this.model, name));
149 } else {
150 utils.removePathValue(this.model, name);
151 }
152 CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
153 }
154 return (
155 <Button className="remove-property-action inline-hint" title="Remove" onClick={onClickRemoveProperty.bind(container, property, path)} label="Remove" src={imgRemove}/>
156 );
157 }
158
159 function onFormFieldValueChanged(event) {
160 if (DescriptorModelFactory.isContainer(this)) {
161 event.preventDefault();
162 const name = event.target.name;
163 const value = event.target.value;
164 utils.assignPathValue(this.model, name, value);
165 CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
166 }
167 }
168
169 function buildField(container, property, path, value, fieldKey) {
170 let cds = CatalogDataStore;
171 let catalogs = cds.getTransientCatalogs();
172
173 const name = path.join('.');
174 const isEditable = true;
175 const isGuid = Property.isGuid(property);
176 const isBoolean = Property.isBoolean(property);
177 const onChange = onFormFieldValueChanged.bind(container);
178 const isEnumeration = Property.isEnumeration(property);
179 const isLeafRef = Property.isLeafRef(property);
180 const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
181 const placeholder = changeCase.title(property.name);
182 const className = ClassNames(property.name + '-input', {'-is-guid': isGuid});
183 const fieldValue = value ? (value.constructor.name != "Object") ? value : '' : undefined;
184 if (isEnumeration) {
185 const enumeration = Property.getEnumeration(property, value);
186 const options = enumeration.map((d, i) => {
187 // note yangforge generates values for enums but the system does not use them
188 // so we categorically ignore them
189 // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
190 //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
191 return <option key={fieldKey.toString() + ':' + i} value={d.name}>{d.name}</option>;
192 });
193 const isValueSet = enumeration.filter(d => d.isSelected).length > 0;
194 if (!isValueSet || property.cardinality === '0..1') {
195 const noValueDisplayText = changeCase.title(property.name);
196 options.unshift(<option key={'(value-not-in-enum)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
197 }
198 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>;
199 }
200
201 if (isLeafRef) {
202 let fullFieldKey = _.isArray(fieldKey) ? fieldKey.join(':') : fieldKey;
203 let containerRef = container;
204 while (containerRef.parent) {
205 fullFieldKey = containerRef.parent.key + ':' + fullFieldKey;
206 containerRef = containerRef.parent;
207 }
208 const leafRefPathValues = Property.getLeafRef(property, path, value, fullFieldKey, catalogs, container);
209
210 const options = leafRefPathValues && leafRefPathValues.map((d, i) => {
211 return <option key={fieldKey.toString() + ':' + i} value={d.value}>{d.value}</option>;
212 });
213 const isValueSet = leafRefPathValues.filter(d => d.isSelected).length > 0;
214 if (!isValueSet || property.cardinality === '0..1') {
215 const noValueDisplayText = changeCase.title(property.name);
216 options.unshift(<option key={'(value-not-in-leafref)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
217 }
218 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>;
219 }
220
221 if (isBoolean) {
222 let fullFieldKey = _.isArray(fieldKey) ? fieldKey.join(':') : fieldKey;
223 let containerRef = container;
224 while (containerRef.parent) {
225 fullFieldKey = containerRef.parent.key + ':' + fullFieldKey;
226 containerRef = containerRef.parent;
227 }
228
229 const options = [
230 <option key={fieldKey.toString() + '-true'} value="TRUE">TRUE</option>,
231 <option key={fieldKey.toString() + '-false'} value="FALSE">FALSE</option>
232 ]
233
234 // if (!isValueSet) {
235 const noValueDisplayText = changeCase.title(property.name);
236 options.unshift(<option key={'(value-not-in-leafref)' + fieldKey.toString()} value="" placeholder={placeholder}></option>);
237 // }
238 let val = value;
239 if(typeof(val) == 'number') {
240 val = value ? "TRUE" : "FALSE"
241 }
242 const isValueSet = (val != '' && val)
243 return <select key={fieldKey.toString()} id={fieldKey.toString()} className={ClassNames({'-value-not-set': !isValueSet})} name={name} value={val && val.toUpperCase()} title={name} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} readOnly={!isEditable}>{options}</select>;
244 }
245
246 if (property['preserve-line-breaks']) {
247 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} />;
248 }
249
250 return <input key={fieldKey.toString()}
251 id={fieldKey.toString()}
252 type="text"
253 name={name}
254 value={fieldValue}
255 className={className}
256 placeholder={placeholder}
257 onChange={onChange}
258 onFocus={onFocus}
259 onBlur={endEditing}
260 onMouseDown={startEditing}
261 onMouseOver={startEditing}
262 onMouseOut={endEditing}
263 onMouseLeave={endEditing}
264 readOnly={!isEditable}
265 />;
266
267 }
268
269 function buildElement(container, property, valuePath, value) {
270 return property.properties.map((property, index) => {
271 let childValue;
272 const childPath = valuePath.slice();
273 if (typeof value === 'object') {
274 childValue = value[property.name];
275 }
276 if(property.type != 'choice'){
277 childPath.push(property.name);
278 }
279 return build(container, property, childPath, childValue);
280
281 });
282 }
283
284 function buildChoice(container, property, path, value, key, props={}) {
285
286 function onFormFieldValueChanged(event) {
287 if (DescriptorModelFactory.isContainer(this)) {
288
289 event.preventDefault();
290
291 let name = event.target.name;
292 const value = event.target.value;
293
294
295 /*
296 Transient State is stored for convenience in the uiState field.
297 The choice yang type uses case elements to describe the "options".
298 A choice can only ever have one option selected which allows
299 the system to determine which type is selected by the name of
300 the element contained within the field.
301 */
302 /*
303 const stateExample = {
304 uiState: {
305 choice: {
306 'conf-config': {
307 selected: 'rest',
308 'case': {
309 rest: {},
310 netconf: {},
311 script: {}
312 }
313 }
314 }
315 }
316 };
317 */
318 const statePath = ['uiState.choice'].concat(name);
319 const stateObject = utils.resolvePath(this.model, statePath.join('.')) || {};
320 const selected = stateObject.selected ? stateObject.selected.split('.')[1] : undefined;
321 // write state back to the model so the new state objects are captured
322 utils.assignPathValue(this.model, statePath.join('.'), stateObject);
323
324 // write the current choice value into the state
325 let choiceObject = utils.resolvePath(this.model, [name, selected].join('.'));
326 let isTopCase = false;
327 if (!choiceObject) {
328 isTopCase = true;
329 choiceObject = utils.resolvePath(this.model, [selected].join('.'));
330 }
331 utils.assignPathValue(stateObject, [selected].join('.'), _.cloneDeep(choiceObject));
332
333 if(selected) {
334 if(this.model.uiState.choice.hasOwnProperty(name)) {
335 delete this.model[selected];
336 utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
337 } else {
338 // remove the current choice value from the model
339 utils.removePathValue(this.model, [name, selected].join('.'), isTopCase);
340 }
341 }
342
343 // get any state for the new selected choice
344 const newChoiceObject = utils.resolvePath(stateObject, [value].join('.')) || {};
345
346 // assign new choice value to the model
347 if (isTopCase) {
348 utils.assignPathValue(this.model, [name, value].join('.'), newChoiceObject);
349 } else {
350 utils.assignPathValue(this.model, [value].join('.'), newChoiceObject)
351 }
352
353
354 // update the selected name
355 utils.assignPathValue(this.model, statePath.concat('selected').join('.'), value);
356
357 CatalogItemsActions.catalogItemDescriptorChanged(this.getRoot());
358 }
359 }
360
361 const caseByNameMap = {};
362
363 const onChange = onFormFieldValueChanged.bind(container);
364
365 const cases = property.properties.map(d => {
366 if (d.type === 'case') {
367 caseByNameMap[d.name] = d.properties[0];
368 return {
369 optionName: d.name,
370 optionTitle: d.description,
371 //represents case name and case element name
372 optionValue: [d.name, d.properties[0].name].join('.')
373 };
374 }
375 caseByNameMap[d.name] = d;
376 return {optionName: d.name};
377 });
378
379 const options = [{optionName: '', optionValue: false}].concat(cases).map((d, i) => {
380 return (
381 <option key={i} value={d.optionValue} title={d.optionTitle}>
382 {d.optionName}
383 {i ? null : changeCase.title(property.name)}
384 </option>
385 );
386 });
387
388 const selectName = path.join('.');
389 let selectedOptionPath = ['uiState.choice', selectName, 'selected'].join('.');
390 //Currently selected choice/case statement on UI model
391 let selectedOptionValue = utils.resolvePath(container.model, selectedOptionPath);
392 //If first time loaded, and none is selected, check if there is a value corresponding to a case statement in the container model
393 if(!selectedOptionValue) {
394 //get field properties for choice on container model
395 let fieldProperties = utils.resolvePath(container.model, selectName);
396 if(fieldProperties) {
397 //Check each case statement in model and see if it is present in container model.
398 cases.map(function(c){
399 if(fieldProperties.hasOwnProperty(c.optionValue.split('.')[1])) {
400 utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), c.optionValue);
401 }
402 });
403 selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
404 } else {
405 property.properties.map(function(p) {
406 let pname = p.properties[0].name;
407 if(container.model.hasOwnProperty(pname)) {
408 utils.assignPathValue(container.model, ['uiState.choice', selectName, 'selected'].join('.'), [p.name, pname].join('.'));
409 }
410 })
411 selectedOptionValue = utils.resolvePath(container.model, ['uiState.choice', selectName, 'selected'].join('.'));
412 }
413 }
414 //If selectedOptionValue is present, take first item in string which represents the case name.
415 const valueProperty = caseByNameMap[selectedOptionValue ? selectedOptionValue.split('.')[0] : undefined] || {properties: []};
416 const isLeaf = Property.isLeaf(valueProperty);
417 const hasProperties = _.isArray(valueProperty.properties) && valueProperty.properties.length;
418 const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(valueProperty);
419 //Some magic that prevents errors for arising
420 const valueResponse = valueProperty.properties.length ? valueProperty.properties.map((d, i) => {
421 const childPath = path.concat(valueProperty.name, d.name);
422 const childValue = utils.resolvePath(container.model, childPath.join('.'));
423 return (
424 <div key={childPath.concat('info', i).join(':')}>
425 {build(container, d, childPath, childValue, props)}
426 </div>
427 );
428 }) : (!isMissingDescriptorMeta) ? build(container, valueProperty, path.concat(valueProperty.name), utils.resolvePath(container.model, path.concat(valueProperty.name).join('.')) || container.model[valueProperty.name]) : null
429 // end magic
430 const onFocus = onFocusPropertyFormInputElement.bind(container, property, path, value);
431
432 return (
433 <div key={key} className="choice">
434 <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}>
435 {options}
436 </select>
437 {valueResponse}
438 </div>
439 );
440
441 }
442
443 function buildSimpleListItem(container, property, path, value, key, index) {
444 // todo need to abstract this better
445 const title = getTitle(value);
446 var req = require.context("../", true, /\.svg/);
447 return (
448 <div>
449 <a href="#select-list-item" key={Date.now()} className={property.name + '-list-item simple-list-item '} onClick={onClickSelectItem.bind(container, property, path, value)}>
450 <img src={req('./' + DescriptorModelIconFactory.getUrlForType(property.name))} width="20px" />
451 <span>{title}</span>
452 </a>
453 {buildRemovePropertyAction(container, property, path)}
454 </div>
455 );
456 }
457
458 function buildRemoveListItem(container, property, valuePath, fieldKey, index) {
459 const className = ClassNames(property.name + '-remove actions');
460 return (
461 <div key={fieldKey.concat(index).join(':')} className={className}>
462 <h3>
463 <span className={property.type + '-name name'}>{changeCase.title(property.name)}</span>
464 <span className="info">{index + 1}</span>
465 {buildRemovePropertyAction(container, property, valuePath)}
466 </h3>
467 </div>
468 );
469 }
470
471 function buildLeafListItem(container, property, valuePath, value, key, index) {
472 // look at the type to determine how to parse the value
473 return (
474 <div>
475 {buildRemoveListItem(container, property, valuePath, key, index)}
476 {buildField(container, property, valuePath, value, key)}
477 </div>
478
479 );
480 }
481
482 function build(container, property, path, value, props = {}) {
483
484 const fields = [];
485 const isLeaf = Property.isLeaf(property);
486 const isArray = Property.isArray(property);
487 const isObject = Property.isObject(property);
488 const isLeafList = Property.isLeafList(property);
489 const fieldKey = [container.id].concat(path);
490 const isRequired = Property.isRequired(property);
491 const title = changeCase.titleCase(property.name);
492 const columnCount = property.properties.length || 1;
493 const isColumnar = isArray && (Math.round(props.width / columnCount) > 155);
494 const classNames = {'-is-required': isRequired, '-is-columnar': isColumnar};
495
496 if (!property.properties && isObject) {
497 const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
498 property.properties = uiState.properties;
499 }
500
501 const hasProperties = _.isArray(property.properties) && property.properties.length;
502 const isMissingDescriptorMeta = !hasProperties && !Property.isLeaf(property);
503
504 // ensure value is not undefined for non-leaf property types
505 if (isObject) {
506 if (typeof value !== 'object') {
507 value = isArray ? [] : {};
508 }
509 }
510 const valueAsArray = _.isArray(value) ? value : isLeafList && typeof value === 'undefined' ? [] : [value];
511
512 const isMetaField = property.name === 'meta';
513 const isCVNFD = property.name === 'constituent-vnfd';
514 const isSimpleListView = Property.isSimpleList(property);
515
516 valueAsArray.forEach((value, index) => {
517
518 let field;
519 const key = fieldKey.slice();
520 const valuePath = path.slice();
521
522 if (isArray) {
523 valuePath.push(index);
524 key.push(index);
525 }
526
527 if (isMetaField) {
528 if (typeof value === 'object') {
529 value = JSON.stringify(value, undefined, 12);
530 } else if (typeof value !== 'string') {
531 value = '{}';
532 }
533 }
534
535 if (isMissingDescriptorMeta) {
536 field = <span key={key.concat('warning').join(':')} className="warning">No Descriptor Meta for {property.name}</span>;
537 } else if (property.type === 'choice') {
538 field = buildChoice(container, property, valuePath, value, key.join(':'), props);
539 } else if (isSimpleListView) {
540 field = buildSimpleListItem(container, property, valuePath, value, key, index, props);
541 } else if (isLeafList) {
542 field = buildLeafListItem(container, property, valuePath, value, key, index, props);
543 } else if (hasProperties) {
544 field = buildElement(container, property, valuePath, value, key.join(':'), props)
545 } else {
546 field = buildField(container, property, valuePath, value, key.join(':'), props);
547 }
548
549 function onClickLeaf(property, path, value, event) {
550 if (event.isDefaultPrevented()) {
551 return;
552 }
553 event.preventDefault();
554 event.stopPropagation();
555 this.getRoot().uiState.focusedPropertyPath = path.join('.');
556 console.log('property selected', path.join('.'));
557 ComposerAppActions.propertySelected([path.join('.')]);
558 }
559
560 const clickHandler = isLeaf ? onClickLeaf : () => {};
561 const isContainerList = isArray && !(isSimpleListView || isLeafList);
562
563 fields.push(
564 <div key={fieldKey.concat(['property-content', index]).join(':')}
565 className={ClassNames('property-content', {'simple-list': isSimpleListView})}
566 onClick={clickHandler.bind(container, property, valuePath, value)}>
567 {isContainerList ? buildRemoveListItem(container, property, valuePath, fieldKey, index) : null}
568 {field}
569 </div>
570 );
571
572 });
573
574 classNames['-is-leaf'] = isLeaf;
575 classNames['-is-array'] = isArray;
576 classNames['cols-' + columnCount] = isColumnar;
577
578 if (property.type === 'choice') {
579 value = utils.resolvePath(container.model, ['uiState.choice'].concat(path, 'selected').join('.'));
580 if(!value) {
581 property.properties.map(function(p) {
582 let pname = p.properties[0].name;
583 if(container.model.hasOwnProperty(pname)) {
584 value = container.model[pname];
585 }
586 })
587 }
588 }
589
590 let displayValue = typeof value === 'object' ? '' : value;
591 const displayValueInfo = isArray ? valueAsArray.filter(d => typeof d !== 'undefined').length + ' items' : '';
592
593 const onFocus = isLeaf ? event => event.target.classList.add('-is-focused') : false;
594
595 return (
596 <div key={fieldKey.join(':')} className={ClassNames(property.type + '-property property', classNames)} onFocus={onFocus}>
597 <h3 className="property-label">
598 <label htmlFor={fieldKey.join(':')}>
599 <span className={property.type + '-name name'}>{title}</span>
600 <small>
601 <span className={property.type + '-info info'}>{displayValueInfo}</span>
602 <span className={property.type + '-value value'}>{displayValue}</span>
603 </small>
604 {isArray ? buildAddPropertyAction(container, property, path.concat(valueAsArray.length)) : null}
605 </label>
606 </h3>
607 <span className={property.type + '-description description'}>{property.description}</span>
608 <val className="property-value">
609 {isCVNFD ? <span className={property.type + '-tip tip'}>Drag a VNFD from the Catalog to add more.</span> : null}
610 {fields}
611 </val>
612 </div>
613 );
614
615 }
616 export default function EditDescriptorModelProperties(props, type) {
617
618 const container = props.container;
619
620 if (!(DescriptorModelFactory.isContainer(container))) {
621 return
622 }
623
624
625
626 const containerType = (_.isEmpty(type) ? false : type)|| container.uiState['qualified-type'] || container.uiState.type;
627 const basicProperties = getDescriptorMetaBasicForType(containerType).properties;
628
629 function buildBasicGroup() {
630 if (basicProperties.length === 0) {
631 return null;
632 }
633 return (
634 <div className="basic-properties-group">
635 <h2>Basic</h2>
636 <div>
637 {basicProperties.map(property => {
638 const path = [property.name];
639 const value = container.model[property.name];
640 return build(container, property, path, value);
641 })}
642 </div>
643 </div>
644 );
645 }
646
647 function buildAdvancedGroup() {
648 const properties = getDescriptorMetaAdvancedForType(containerType).properties;
649 if (properties.length === 0) {
650 return null;
651 }
652 const hasBasicFields = basicProperties.length > 0;
653 const closeGroup = basicProperties.length > 0;
654 return (
655 <div className="advanced-properties-group">
656 <h1 data-toggle={closeGroup ? 'true' : 'false'} className={ClassNames({'-is-toggled': closeGroup})} onClick={toggle} style={{display: hasBasicFields ? 'block' : 'none'}}>
657 <a className="toggle-show-more" href="#show-more-properties">more&hellip;</a>
658 <a className="toggle-show-less" href="#show-more-properties">less&hellip;</a>
659 </h1>
660 <div className="toggleable">
661 {properties.map(property => {
662 const path = [property.name];
663 const value = container.model[property.name];
664 return build(container, property, path, value, _.assign({toggle: true, width: props.width}, props));
665 })}
666 </div>
667 <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>
668 </div>
669 );
670 }
671
672 function buildMoreLess(d, i) {
673 return (
674 <span key={'bread-crumb-part-' + i}>
675 <a href="#select-item" onClick={onClickSelectItem.bind(d, null, null, d)}>{d.title}</a>
676 <i> / </i>
677 </span>
678 );
679 }
680
681 const path = [];
682 if (container.parent) {
683 path.push(container.parent);
684 }
685 path.push(container);
686
687 return (
688 <div className="EditDescriptorModelProperties -is-tree-view">
689 <h1>{path.map(buildMoreLess)}</h1>
690 {buildBasicGroup()}
691 {buildAdvancedGroup()}
692 </div>
693 );
694
695 }
696 export {build}
697 // export buildElement;
698 // export buildChoice;