update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / composer / src / src / components / NavigateDescriptorModel.jsx
diff --git a/skyquake/plugins/composer/src/src/components/NavigateDescriptorModel.jsx b/skyquake/plugins/composer/src/src/components/NavigateDescriptorModel.jsx
new file mode 100644 (file)
index 0000000..244c4e4
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ *
+ *   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.
+ */
+
+import _uniqueId from 'lodash/uniqueId';
+import _set from 'lodash/set';
+import _get from 'lodash/get';
+import _has from 'lodash/has';
+import _keys from 'lodash/keys';
+import _isObject from 'lodash/isObject';
+import _isArray from 'lodash/isArray';
+import _isNumber from 'lodash/isNumber';
+import utils from '../libraries/utils'
+import React from 'react'
+import changeCase from 'change-case'
+import toggle from '../libraries/ToggleElementHandler'
+import Property from '../libraries/model/DescriptorModelMetaProperty'
+import SelectionManager from '../libraries/SelectionManager'
+import ComposerAppActions from '../actions/ComposerAppActions'
+import CatalogItemsActions from '../actions/CatalogItemsActions'
+import DescriptorEditorActions from '../actions/DescriptorEditorActions'
+import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
+import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
+import PropertyNavigate from './model/PropertyNavigate'
+
+
+import '../styles/EditDescriptorModelProperties.scss'
+
+function selectModel(container, model) {
+       const root = container.getRoot();
+       if (SelectionManager.select(model)) {
+               CatalogItemsActions.catalogItemMetaDataChanged(root.model);
+       }
+}
+
+function isDataProperty(property) {
+       return property.type === 'leaf' || property.type === 'leaf_list' || property.type === 'choice';
+}
+
+function checkIfValueEmpty(value) {
+       if (value === null || typeof value === 'undefined') {
+               return true;
+       } else if (_isArray(value) && !value.length) {
+               return true;
+       } else if (_isObject(value)) {
+               const keys = _keys(value);
+               if (keys.length < 2) {
+                       return !keys.length || (keys[0] === 'uiState')
+               }
+       }
+       return false;
+}
+
+function makeOption(path, value) {
+       let labelPath = path.map(node => _isNumber(node) ? node + 1: node);
+       return {
+               value: path,
+               label: labelPath.join(' . ') + (value ? ' : ' + value : '')
+       }
+}
+
+export default function NavigateDescriptorModel(props) {
+       const { container, idMaker, style } = props;
+       const uiState = container.uiState;
+
+       function buildField(property, path, value) {
+               return [makeOption(path, value)];
+       }
+
+       function buildLeafList(property, path, value) {
+               const searchValue = Array.isArray(value) ? value.join(' ') : value;
+               return [makeOption(path, searchValue)];
+       }
+
+       function buildChoice(property, path, value) {
+               const uiStatePath = path.concat(['uiState']);
+               const choiceStatePath = ['choice', property.name];
+               const fullChoiceStatePath = uiStatePath.concat(choiceStatePath);
+
+               function determineSelectedChoice(model) {
+                       let choiceState = utils.resolvePath(container.model, fullChoiceStatePath.join('.'));
+                       if (choiceState) {
+                               return property.properties.find(c => c.name === choiceState.selected);
+                       }
+                       const selectedCase = property.properties.find(c =>
+                               c.properties && c.properties.find(p => _has(model, path.concat([p.name])))
+                       );
+                       return selectedCase;
+               }
+
+               const selectedCase = determineSelectedChoice(container.model);
+               return [makeOption(path)].concat(selectedCase ?
+                       buildComponentsForProperties(
+                               selectedCase.properties, path, path.length ? _get(container.model, path) : container.model) :
+                       []);
+       }
+
+       function buildList(property, path, value, uniqueId) {
+               if (value && !Array.isArray(value)) {
+                       value = [value];
+               }
+
+               function getListItemSummary(index, value) {
+                       const keys = property.key.map((key) => value[key]);
+                       const summary = keys.join(' ');
+                       return summary.length > 1 ? summary : '' + (index + 1);
+               }
+               const children = value ? value.reduce((a, itemValue, i) => {
+                       const itemPath = path.concat([i]);
+                       return a.concat(buildComponentsForProperties(property.properties, itemPath, itemValue));
+               }, [makeOption(path)])
+                       : [makeOption(path)];
+               return children;
+       }
+
+       function buildSimpleList(property, path, value, uniqueId) {
+               return [makeOption(path)];
+       }
+
+       function buildContainer(property, path, value, uniqueId, readOnly) {
+               return buildComponentsForProperties(property.properties, path, value);
+       }
+
+       /**
+        * buiid and return an array of components representing an editor for each property.
+        * 
+        * @param {any} container the master document being edited
+        * @param {[property]} properties 
+        * @param {string} pathToProperties path within the container to the properties
+        * @param {Object} data source for each property
+        * which may be useful/necessary to a components rendering.
+        * @returns an array of react components
+        */
+       function buildComponentsForProperties(properties, pathToProperties, data) {
+               return properties.reduce((a, property) => {
+                       let value;
+                       let propertyPath = pathToProperties.slice();
+                       if (property.type != 'choice') {
+                               propertyPath.push(property.name);
+                       }
+                       if (data && typeof data === 'object') {
+                               value = _get(data, property.name);
+                       }
+                       let result = [];
+                       try {
+                               result = buildPropertyComponent(property, propertyPath, value);
+                       } catch (e) {
+                               console.error(e);
+                       }
+                       return a.concat(result);
+               }, []);
+       }
+
+       function buildPropertyComponent(property, path, value) {
+
+               const fields = [];
+               const isObject = Property.isObject(property);
+               const title = changeCase.titleCase(property.name);
+
+               // create a unique Id for use as react component keys and html element ids
+               // use uid (from ui info) instead of id property (which is not stable)
+               let uniqueId = container.uid;
+               let containerRef = container;
+               while (containerRef.parent) {
+                       uniqueId = containerRef.parent.uid + ':' + uniqueId;
+                       containerRef = containerRef.parent;
+               }
+               uniqueId += ':' + path.join(':')
+
+               if (!property.properties && isObject) {
+                       console.debug('no properties', property);
+                       const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
+                       property.properties = uiState.properties;
+               }
+
+               if (property.type === 'leaf') {
+                       return buildField(property, path, value, uniqueId);
+               } else if (property.type === 'leaf_list') {
+                       return buildLeafList(property, path, value, uniqueId);
+               } else if (property.type === 'list') {
+                       return Property.isSimpleList(property) ?
+                               buildSimpleList(property, path, value, uniqueId) :
+                               buildList(property, path, value, uniqueId);
+               } else if (property.type === 'container') {
+                       return buildContainer(property, path, value, uniqueId);
+               } else if (property.type === 'choice') {
+                       return buildChoice(property, path, value, uniqueId);
+               } else {
+                       return ([]);
+               }
+       }
+
+
+       if (!(DescriptorModelFactory.isContainer(container))) {
+               return null;
+       }
+
+       const containerType = container.uiState['qualified-type'] || container.uiState.type;
+       let properties = DescriptorModelMetaFactory.getModelMetaForType(containerType).properties;
+       // bubble all data properties to top of list
+       let twoLists = properties.reduce((o, property) => {
+               const value = _get(container.model, [property.name]);
+               if (isDataProperty(property)) {
+                       o.listOne.push(property);
+               } else {
+                       o.listTwo.push(property);
+               }
+               return o;
+       }, {
+                       listOne: [],
+                       listTwo: []
+               });
+       properties = twoLists.listOne.concat(twoLists.listTwo);
+       const options = buildComponentsForProperties(properties, [], container.model);
+       return options.length ?
+               (<PropertyNavigate container={container} idMaker={idMaker} options={options} 
+                       style={style} placeholder="Select to navigate" />)
+               : <div />;
+};
\ No newline at end of file