update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / composer / src / src / components / NavigateDescriptorModel.jsx
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
24 import _uniqueId from 'lodash/uniqueId';
25 import _set from 'lodash/set';
26 import _get from 'lodash/get';
27 import _has from 'lodash/has';
28 import _keys from 'lodash/keys';
29 import _isObject from 'lodash/isObject';
30 import _isArray from 'lodash/isArray';
31 import _isNumber from 'lodash/isNumber';
32 import utils from '../libraries/utils'
33 import React from 'react'
34 import changeCase from 'change-case'
35 import toggle from '../libraries/ToggleElementHandler'
36 import Property from '../libraries/model/DescriptorModelMetaProperty'
37 import SelectionManager from '../libraries/SelectionManager'
38 import ComposerAppActions from '../actions/ComposerAppActions'
39 import CatalogItemsActions from '../actions/CatalogItemsActions'
40 import DescriptorEditorActions from '../actions/DescriptorEditorActions'
41 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
42 import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
43 import PropertyNavigate from './model/PropertyNavigate'
44
45
46 import '../styles/EditDescriptorModelProperties.scss'
47
48 function selectModel(container, model) {
49         const root = container.getRoot();
50         if (SelectionManager.select(model)) {
51                 CatalogItemsActions.catalogItemMetaDataChanged(root.model);
52         }
53 }
54
55 function isDataProperty(property) {
56         return property.type === 'leaf' || property.type === 'leaf_list' || property.type === 'choice';
57 }
58
59 function checkIfValueEmpty(value) {
60         if (value === null || typeof value === 'undefined') {
61                 return true;
62         } else if (_isArray(value) && !value.length) {
63                 return true;
64         } else if (_isObject(value)) {
65                 const keys = _keys(value);
66                 if (keys.length < 2) {
67                         return !keys.length || (keys[0] === 'uiState')
68                 }
69         }
70         return false;
71 }
72
73 function makeOption(path, value) {
74         let labelPath = path.map(node => _isNumber(node) ? node + 1: node);
75         return {
76                 value: path,
77                 label: labelPath.join(' . ') + (value ? ' : ' + value : '')
78         }
79 }
80
81 export default function NavigateDescriptorModel(props) {
82         const { container, idMaker, style } = props;
83         const uiState = container.uiState;
84
85         function buildField(property, path, value) {
86                 return [makeOption(path, value)];
87         }
88
89         function buildLeafList(property, path, value) {
90                 const searchValue = Array.isArray(value) ? value.join(' ') : value;
91                 return [makeOption(path, searchValue)];
92         }
93
94         function buildChoice(property, path, value) {
95                 const uiStatePath = path.concat(['uiState']);
96                 const choiceStatePath = ['choice', property.name];
97                 const fullChoiceStatePath = uiStatePath.concat(choiceStatePath);
98
99                 function determineSelectedChoice(model) {
100                         let choiceState = utils.resolvePath(container.model, fullChoiceStatePath.join('.'));
101                         if (choiceState) {
102                                 return property.properties.find(c => c.name === choiceState.selected);
103                         }
104                         const selectedCase = property.properties.find(c =>
105                                 c.properties && c.properties.find(p => _has(model, path.concat([p.name])))
106                         );
107                         return selectedCase;
108                 }
109
110                 const selectedCase = determineSelectedChoice(container.model);
111                 return [makeOption(path)].concat(selectedCase ?
112                         buildComponentsForProperties(
113                                 selectedCase.properties, path, path.length ? _get(container.model, path) : container.model) :
114                         []);
115         }
116
117         function buildList(property, path, value, uniqueId) {
118                 if (value && !Array.isArray(value)) {
119                         value = [value];
120                 }
121
122                 function getListItemSummary(index, value) {
123                         const keys = property.key.map((key) => value[key]);
124                         const summary = keys.join(' ');
125                         return summary.length > 1 ? summary : '' + (index + 1);
126                 }
127                 const children = value ? value.reduce((a, itemValue, i) => {
128                         const itemPath = path.concat([i]);
129                         return a.concat(buildComponentsForProperties(property.properties, itemPath, itemValue));
130                 }, [makeOption(path)])
131                         : [makeOption(path)];
132                 return children;
133         }
134
135         function buildSimpleList(property, path, value, uniqueId) {
136                 return [makeOption(path)];
137         }
138
139         function buildContainer(property, path, value, uniqueId, readOnly) {
140                 return buildComponentsForProperties(property.properties, path, value);
141         }
142
143         /**
144          * buiid and return an array of components representing an editor for each property.
145          * 
146          * @param {any} container the master document being edited
147          * @param {[property]} properties 
148          * @param {string} pathToProperties path within the container to the properties
149          * @param {Object} data source for each property
150          * which may be useful/necessary to a components rendering.
151          * @returns an array of react components
152          */
153         function buildComponentsForProperties(properties, pathToProperties, data) {
154                 return properties.reduce((a, property) => {
155                         let value;
156                         let propertyPath = pathToProperties.slice();
157                         if (property.type != 'choice') {
158                                 propertyPath.push(property.name);
159                         }
160                         if (data && typeof data === 'object') {
161                                 value = _get(data, property.name);
162                         }
163                         let result = [];
164                         try {
165                                 result = buildPropertyComponent(property, propertyPath, value);
166                         } catch (e) {
167                                 console.error(e);
168                         }
169                         return a.concat(result);
170                 }, []);
171         }
172
173         function buildPropertyComponent(property, path, value) {
174
175                 const fields = [];
176                 const isObject = Property.isObject(property);
177                 const title = changeCase.titleCase(property.name);
178
179                 // create a unique Id for use as react component keys and html element ids
180                 // use uid (from ui info) instead of id property (which is not stable)
181                 let uniqueId = container.uid;
182                 let containerRef = container;
183                 while (containerRef.parent) {
184                         uniqueId = containerRef.parent.uid + ':' + uniqueId;
185                         containerRef = containerRef.parent;
186                 }
187                 uniqueId += ':' + path.join(':')
188
189                 if (!property.properties && isObject) {
190                         console.debug('no properties', property);
191                         const uiState = DescriptorModelMetaFactory.getModelMetaForType(property.name) || {};
192                         property.properties = uiState.properties;
193                 }
194
195                 if (property.type === 'leaf') {
196                         return buildField(property, path, value, uniqueId);
197                 } else if (property.type === 'leaf_list') {
198                         return buildLeafList(property, path, value, uniqueId);
199                 } else if (property.type === 'list') {
200                         return Property.isSimpleList(property) ?
201                                 buildSimpleList(property, path, value, uniqueId) :
202                                 buildList(property, path, value, uniqueId);
203                 } else if (property.type === 'container') {
204                         return buildContainer(property, path, value, uniqueId);
205                 } else if (property.type === 'choice') {
206                         return buildChoice(property, path, value, uniqueId);
207                 } else {
208                         return ([]);
209                 }
210         }
211
212
213         if (!(DescriptorModelFactory.isContainer(container))) {
214                 return null;
215         }
216
217         const containerType = container.uiState['qualified-type'] || container.uiState.type;
218         let properties = DescriptorModelMetaFactory.getModelMetaForType(containerType).properties;
219         // bubble all data properties to top of list
220         let twoLists = properties.reduce((o, property) => {
221                 const value = _get(container.model, [property.name]);
222                 if (isDataProperty(property)) {
223                         o.listOne.push(property);
224                 } else {
225                         o.listTwo.push(property);
226                 }
227                 return o;
228         }, {
229                         listOne: [],
230                         listTwo: []
231                 });
232         properties = twoLists.listOne.concat(twoLists.listTwo);
233         const options = buildComponentsForProperties(properties, [], container.model);
234         return options.length ?
235                 (<PropertyNavigate container={container} idMaker={idMaker} options={options} 
236                         style={style} placeholder="Select to navigate" />)
237                 : <div />;
238 };