update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / composer / src / src / components / DetailsPanel.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19 'use strict';
20
21 import _cloneDeep from 'lodash/cloneDeep'
22 import _isArray from 'lodash/isArray'
23 import _isObject from 'lodash/isObject'
24 import _keys from 'lodash/keys'
25 import React from 'react';
26 import PureRenderMixin from 'react-addons-pure-render-mixin'
27 import messages from './messages'
28 import serializers from '../libraries/model/DescriptorModelSerializer'
29 import JSONViewer from 'widgets/JSONViewer/JSONViewer';
30 import PopupWindow from './PopupWindow'
31 import DetailsPanelToolbar from './DetailsPanelToolbar'
32 import NavigateDescriptorModel from './NavigateDescriptorModel'
33 import NavigateDescriptorErrors from './NavigateDescriptorErrors'
34 import CatalogItemDetailsEditor from './CatalogItemDetailsEditor'
35 import SelectionManager from '../libraries/SelectionManager'
36
37 import '../styles/DetailsPanel.scss'
38
39 function checkForErrors(errors) {
40 return _keys(errors).reduce((inError, k) => {
41 function traverseObject(obj, key) {
42 const node = obj[key];
43 if (_isArray(node)) {
44 return node.reduce((inError, v, i) => {
45 if (!inError && v) {
46 return _keys(v).reduce((inError, k) => {
47 return inError || traverseObject(v, k);
48 }, false);
49 }
50 return inError;
51 }, false);
52 } else if (_isObject(node)) {
53 return _keys(node).reduce((inError, k) => {
54 return inError || traverseObject(node, k);
55 }, false);
56 } else {
57 return !! node;
58 }
59 }
60 return inError || traverseObject(errors, k);
61 }, false);
62 }
63
64 const DetailsPanel = React.createClass({
65 mixins: [PureRenderMixin, SelectionManager.reactPauseResumeMixin],
66 getInitialState() {
67 return {};
68 },
69 getDefaultProps() {
70 return {
71 containers: [],
72 showJSONViewer: false
73 };
74 },
75 componentWillMount() {
76 setTimeout(() => {
77 const height = this.panel && this.panel.offsetHeight;
78 this.setState({ height });
79 }, 100);
80 },
81 componentDidMount() {
82 },
83 componentDidUpdate() {
84 SelectionManager.refreshOutline();
85 },
86 componentWillUnmount() {
87 },
88 contextTypes: {
89 router: React.PropTypes.object,
90 userProfile: React.PropTypes.object
91 },
92 componentWillUpdate(nextProps) {
93 if ((nextProps.layout != this.props.layout)
94 && (nextProps.layout.height != this.props.layout.height)) {
95 this.componentWillMount();
96 }
97 },
98
99 render() {
100 let json = '{}';
101 let bodyContent = this.props.hasNoCatalogs ? null : messages.detailsWelcome();
102 const selected = this.props.containers.filter(d => SelectionManager.isSelected(d));
103 const selectedContainer = selected[0];
104 let workingHeight = this.state.height || 1;
105
106 function makeId(container, path) {
107 let idParts = [_isArray(path) ? path.join(':') : path];
108 idParts.push(container.uid);
109 while (container.parent) {
110 container = container.parent;
111 idParts.push(container.uid);
112 }
113 return idParts.reverse().join(':');
114 }
115
116 if (selectedContainer) {
117 bodyContent = [];
118 bodyContent.push(
119 <DetailsPanelToolbar
120 key='toolbar'
121 container={selectedContainer}
122 showHelp={this.props.showHelp.forAll}
123 width={this.props.layout.right} />
124 );
125 workingHeight -= 32 + 35;
126 bodyContent.push(
127 <NavigateDescriptorModel key='navigate' container={selectedContainer} idMaker={makeId}
128 style={{ margin: '8px 8px 15px' }} />
129 )
130 workingHeight -= 8 + 15 + 37;
131 if (checkForErrors(selectedContainer.uiState.error)) {
132 bodyContent.push(
133 <NavigateDescriptorErrors key='errors' container={selectedContainer} idMaker={makeId}
134 style={{ margin: '8px 8px 15px' }} />
135 )
136 workingHeight -= 8 + 15 + 37;
137 }
138 bodyContent.push(
139 <div key='editor' className="DetailsPanelBody">
140 <CatalogItemDetailsEditor
141 container={selectedContainer}
142 idMaker={makeId}
143 showHelp={this.props.showHelp}
144 collapsePanelsByDefault={this.props.collapsePanelsByDefault}
145 openPanelsWithData={this.props.openPanelsWithData}
146 width={this.props.layout.right}
147 height={workingHeight} />
148 </div>
149 );
150 const edit = _cloneDeep(selectedContainer.model);
151 json = serializers.serialize(edit) || edit;
152 }
153 const jsonViewerTitle = selectedContainer ? selectedContainer.model.name : 'nothing selected';
154 return (
155 <div ref={el => this.panel = el} className="DetailsPanel" data-resizable="left" data-resizable-handle-offset="0 5" style={{ width: this.props.layout.right }} onClick={event => event.preventDefault()}>
156 {bodyContent}
157 <PopupWindow show={this.props.showJSONViewer} title={jsonViewerTitle}><JSONViewer json={json} /></PopupWindow>
158 </div>
159 );
160 }
161 });
162
163 export default DetailsPanel;