update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / composer / src / src / components / ComposerApp.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 'use strict';
19
20 window['RIFT_wareLaunchpadComposerVersion'] = `semver 0.0.79`;
21
22 import 'es5-shim'
23 import 'babel-polyfill'
24 import alt from '../alt'
25 import UID from '../libraries/UniqueId'
26 import utils from '../libraries/utils'
27 import React from 'react'
28 import ReactDOM from 'react-dom'
29 import Crouton from 'react-crouton'
30 import ClassNames from 'classnames'
31 import PureRenderMixin from 'react-addons-pure-render-mixin'
32 import DeletionManager from '../libraries/DeletionManager'
33 import SelectionManager from '../libraries/SelectionManager'
34 import ResizableManager from '../libraries/ResizableManager'
35 import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
36 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
37 import RiftHeader from './RiftHeader'
38 import CanvasPanel from './CanvasPanel'
39 import CatalogPanel from './CatalogPanel'
40 import DetailsPanel from './DetailsPanel'
41 import ModalOverlay from './ModalOverlay'
42 import ComposerAppToolbar from './ComposerAppToolbar'
43 import PanelResizeAction from '../actions/PanelResizeAction'
44 import ComposerAppActions from '../actions/ComposerAppActions'
45 import ComposerAppStore from '../stores/ComposerAppStore'
46 import CatalogDataStore from '../stores/CatalogDataStore'
47 import TooltipManager from '../libraries/TooltipManager'
48 import CatalogItemsActions from '../actions/CatalogItemsActions'
49 import CommonUtils from 'utils/utils.js'
50 import FileManagerActions from './filemanager/FileManagerActions';
51 import { SkyquakeRBAC, isRBACValid } from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
52 import ROLES from 'utils/roleConstants.js';
53 import _isEmpty from 'lodash/isEmpty';
54 import _isEqual from 'lodash/isEqual';
55
56 import 'normalize.css'
57 import '../styles/AppRoot.scss'
58 import 'style/layout.scss'
59
60
61 const resizeManager = new ResizableManager(window);
62
63 const clearLocalStorage = utils.getSearchParams(window.location).hasOwnProperty('clearLocalStorage');
64
65 const preventDefault = e => e.preventDefault();
66 const clearDragState = () => ComposerAppActions.setDragState(null);
67
68 const PROJECT_ROLES = ROLES.PROJECT;
69 const PLATFORM = ROLES.PLATFORM;
70
71 const CATALOG_POLLING_INTERVAL = 2000;
72
73
74 const ComposerApp = React.createClass({
75 getInitialState() {
76 return ComposerAppStore.getState();
77 },
78 getDefaultProps() {
79 return {};
80 },
81 contextTypes: {
82 router: React.PropTypes.object,
83 userProfile: React.PropTypes.object
84 },
85 componentWillUpdate(nextProps, nextState, nextContext) {
86 if (!_isEmpty(nextContext.userProfile)) {
87 CatalogDataStore.setUserProfile(nextContext.userProfile);
88 }
89 },
90 shouldComponentUpdate: function (nextProps, nextState, nextContext) {
91 if (!this.userProfile && !_isEmpty(nextContext.userProfile)) {
92 this.userProfile = nextContext.userProfile;
93 CatalogDataStore.setUserProfile(nextContext.userProfile);
94 return true;
95 }
96 return !_isEqual(this.props, nextProps) ||
97 !_isEqual(this.state, nextState);
98 },
99 componentWillMount() {
100 if (clearLocalStorage) {
101 window.localStorage.clear();
102 }
103 if (this.item) {
104 FileManagerActions.openFileManagerSockets();
105 }
106 this.state.isLoading = CatalogDataStore.getState().isLoading;
107 ComposerAppStore.listen(this.onChange);
108 CatalogDataStore.listen(this.onCatalogDataChanged);
109 window.addEventListener('resize', this.resize);
110 // prevent browser from downloading any drop outside of our specific drop zones
111 window.addEventListener('dragover', preventDefault);
112 window.addEventListener('drop', preventDefault);
113 // ensure drags initiated in the app clear the state on drop
114 window.addEventListener('drop', clearDragState);
115 DeletionManager.addEventListeners();
116 },
117 componentWillUnmount() {
118 window.removeEventListener('resize', this.resize);
119 window.removeEventListener('dragover', preventDefault);
120 window.removeEventListener('drop', preventDefault);
121 window.removeEventListener('drop', clearDragState);
122 FileManagerActions.closeFileManagerSockets();
123 // resizeManager automatically registered its event handlers
124 resizeManager.removeAllEventListeners();
125 ComposerAppStore.unlisten(this.onChange);
126 CatalogDataStore.unlisten(this.onCatalogDataChanged);
127 DeletionManager.removeEventListeners();
128 TooltipManager.removeEventListeners();
129 if (this.catalogMonitorId) {
130 clearTimeout(this.catalogMonitorId);
131 }
132 },
133 componentDidMount() {
134 resizeManager.addAllEventListeners();
135 const snapshot = window.localStorage.getItem('composer');
136 if (snapshot) {
137 alt.bootstrap(snapshot);
138 }
139 document.body.addEventListener('keydown', (event) => {
140 // prevent details editor form from blowing up the app
141 const ENTER_KEY = 13;
142 if (event.which === ENTER_KEY) {
143 event.preventDefault();
144 return false;
145 }
146 });
147 const loadCatalogs = () => {
148 CatalogDataStore.loadCatalogs();
149 if (CATALOG_POLLING_INTERVAL) {
150 this.catalogMonitorId = setTimeout(loadCatalogs, CATALOG_POLLING_INTERVAL);
151 }
152 };
153 loadCatalogs();
154 DescriptorModelMetaFactory.init().then(() => this.setState({ hasModel: true }));
155 },
156 componentDidUpdate() {
157 if (this.state.fullScreenMode) {
158 document.body.classList.add('-is-full-screen');
159 } else {
160 document.body.classList.remove('-is-full-screen');
161 }
162 SelectionManager.refreshOutline();
163 },
164 resize(e) {
165 PanelResizeAction.resize(e);
166 },
167 render() {
168 let html = null;
169 let self = this;
170 const User = this.userProfile || {};
171 const rbacDisabled = !isRBACValid(User, [PROJECT_ROLES.PROJECT_ADMIN, PROJECT_ROLES.CATALOG_ADMIN]);
172 if (this.state.hasModel) {
173
174 function onClickUpdateSelection(event) {
175 if (event.defaultPrevented) {
176 return
177 }
178 const element = SelectionManager.getClosestElementWithUID(event.target);
179 if (element) {
180 SelectionManager.select(element);
181 SelectionManager.refreshOutline();
182 event.preventDefault();
183 } else {
184 if (event.target.offsetParent && !event.target.offsetParent.classList.contains("tray-body")) {
185 SelectionManager.clearSelectionAndRemoveOutline();
186 }
187 }
188 }
189
190 let cpNumber = 0;
191 let AppHeader = (<div className="AppHeader">
192 <RiftHeader />
193 </div>);
194 // AppHeader = null;
195 const classNames = ClassNames('ComposerApp');
196 const isNew = self.state.item && self.state.item.uiState.isNew;
197 const hasItem = self.state.item && self.state.item.uiState;
198 const isModified = self.state.item && self.state.item.uiState.modified;
199 const isEditingNSD = self.state.item && self.state.item.uiState && /nsd/.test(self.state.item.uiState.type);
200 const isEditingVNFD = self.state.item && self.state.item.uiState && /vnfd/.test(self.state.item.uiState.type);
201 const containers = [self.state.item].reduce(DescriptorModelFactory.buildCatalogItemFactory(CatalogDataStore.getState().catalogs), []);
202
203 containers.filter(d => DescriptorModelFactory.isConnectionPoint(d)).forEach(d => {
204 d.cpNumber = ++cpNumber;
205 containers.filter(d => DescriptorModelFactory.isVnfdConnectionPointRef(d)).filter(ref => ref.key === d.key).forEach(ref => ref.cpNumber = d.cpNumber);
206 });
207 const canvasTitle = containers.length ? containers[0].model.name : '';
208 const hasNoCatalogs = CatalogDataStore.getState().catalogs.length === 0;
209 const isLoading = self.state.isLoading;
210
211 //Bridge element for Crouton fix. Should eventually put Composer on same flux context
212 const Bridge = this.state.ComponentBridgeElement;
213
214 html = (
215 <div ref={element => TooltipManager.addEventListeners(element)} id="RIFT_wareLaunchpadComposerAppRoot" className="AppRoot" onClick={onClickUpdateSelection}>
216 <Bridge />
217 <i className="corner-accent top left" />
218 <i className="corner-accent top right" />
219 <i className="corner-accent bottom left" />
220 <i className="corner-accent bottom right" />
221 {AppHeader}
222 <div className="AppBody">
223 <div className={classNames}>
224 <CatalogPanel layout={self.state.layout}
225 isLoading={isLoading}
226 hasNoCatalogs={hasNoCatalogs}
227 filterByType={self.state.filterCatalogByTypeValue}
228 rbacDisabled={rbacDisabled} />
229 <CanvasPanel layout={self.state.layout}
230 hasNoCatalogs={hasNoCatalogs}
231 showMore={self.state.showMore}
232 containers={containers}
233 title={canvasTitle}
234 zoom={self.state.zoom}
235 panelTabShown={self.state.panelTabShown}
236 files={self.state.files}
237 filesState={self.state.filesState}
238 newPathName={self.state.newPathName}
239 item={self.state.item}
240 type={self.state.filterCatalogByTypeValue}
241 rbacDisabled={rbacDisabled}
242 User={User}
243 />
244 {
245 (self.state.panelTabShown == 'descriptor') ?
246 <DetailsPanel layout={self.state.layout}
247 hasNoCatalogs={hasNoCatalogs}
248 showMore={self.state.showMore}
249 containers={containers}
250 showHelp={self.state.showHelp}
251 collapsePanelsByDefault={self.state.collapsePanelsByDefault}
252 openPanelsWithData={self.state.openPanelsWithData}
253 showJSONViewer={self.state.showJSONViewer} />
254 : null
255 }
256
257 <ComposerAppToolbar layout={self.state.layout}
258 showMore={self.state.showMore}
259 isEditingNSD={isEditingNSD}
260 isEditingVNFD={isEditingVNFD}
261 isModified={isModified}
262 isNew={isNew}
263 disabled={!hasItem || rbacDisabled}
264 onClick={event => event.stopPropagation()}
265 panelTabShown={self.state.panelTabShown} />
266 </div>
267 </div>
268 <ModalOverlay />
269 </div>
270 );
271 }
272 return html;
273 },
274 onChange(state) {
275 this.setState(state);
276 },
277 onCatalogDataChanged(catalogDataState) {
278 this.setState({
279 isLoading: catalogDataState.isLoading
280 });
281 }
282 });
283
284
285 export default ComposerApp;