RIFT-15154: Config parameter map
[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 'normalize.css'
52 import '../styles/AppRoot.scss'
53 import 'style/layout.scss'
54
55
56 const resizeManager = new ResizableManager(window);
57
58 const clearLocalStorage = utils.getSearchParams(window.location).hasOwnProperty('clearLocalStorage');
59
60 const preventDefault = e => e.preventDefault();
61 const clearDragState = () => ComposerAppActions.setDragState(null);
62
63
64 const ComposerApp = React.createClass({
65 mixins: [PureRenderMixin],
66 getInitialState() {
67 return ComposerAppStore.getState();
68 },
69 getDefaultProps() {
70 return {};
71 },
72 componentWillMount() {
73 if (clearLocalStorage) {
74 window.localStorage.clear();
75 }
76 if(this.item) {
77 FileManagerActions.openFileManagerSockets();
78 }
79 this.state.isLoading = CatalogDataStore.getState().isLoading;
80 ComposerAppStore.listen(this.onChange);
81 CatalogDataStore.listen(this.onCatalogDataChanged);
82 window.addEventListener('resize', this.resize);
83 window.onbeforeunload = this.onBeforeUnload;
84 // prevent browser from downloading any drop outside of our specific drop zones
85 window.addEventListener('dragover', preventDefault);
86 window.addEventListener('drop', preventDefault);
87 // ensure drags initiated in the app clear the state on drop
88 window.addEventListener('drop', clearDragState);
89 DeletionManager.addEventListeners();
90 },
91 componentWillUnmount() {
92 window.removeEventListener('resize', this.resize);
93 window.removeEventListener('dragover', preventDefault);
94 window.removeEventListener('drop', preventDefault);
95 window.removeEventListener('drop', clearDragState);
96 FileManagerActions.closeFileManagerSockets();
97 // resizeManager automatically registered its event handlers
98 resizeManager.removeAllEventListeners();
99 ComposerAppStore.unlisten(this.onChange);
100 CatalogDataStore.unlisten(this.onCatalogDataChanged);
101 DeletionManager.removeEventListeners();
102 TooltipManager.removeEventListeners();
103 },
104 componentDidMount() {
105 resizeManager.addAllEventListeners();
106 const snapshot = window.localStorage.getItem('composer');
107 if (snapshot) {
108 alt.bootstrap(snapshot);
109 }
110 document.body.addEventListener('keydown', (event) => {
111 // prevent details editor form from blowing up the app
112 const ENTER_KEY = 13;
113 if (event.which === ENTER_KEY) {
114 event.preventDefault();
115 return false;
116 }
117 });
118 const appRootElement = ReactDOM.findDOMNode(this.refs.appRoot);
119 TooltipManager.addEventListeners(appRootElement);
120 SelectionManager.onClearSelection = () => {
121 if (this.state.item) {
122 CatalogItemsActions.catalogItemMetaDataChanged.defer(this.state.item);
123 }
124 };
125 },
126 componentDidUpdate() {
127 if (this.state.fullScreenMode) {
128 document.body.classList.add('-is-full-screen');
129 } else {
130 document.body.classList.remove('-is-full-screen');
131 }
132 SelectionManager.refreshOutline();
133 },
134 resize(e) {
135 PanelResizeAction.resize(e);
136 },
137 getModel() {
138 let html;
139 let self = this;
140 DescriptorModelMetaFactory.init().then(function(){
141
142 self.setState({
143 hasModel: true
144 })
145 });
146 },
147 render() {
148 let html = null;
149 let self = this;
150 if(this.state.hasModel) {
151
152 function onClickUpdateSelection(event) {
153 if (event.defaultPrevented) {
154 return
155 }
156 const element = SelectionManager.getClosestElementWithUID(event.target);
157 if (element) {
158 SelectionManager.select(element);
159 SelectionManager.refreshOutline();
160 event.preventDefault();
161 } else {
162 SelectionManager.clearSelectionAndRemoveOutline();
163 }
164 }
165
166 let cpNumber = 0;
167 let AppHeader = (<div className="AppHeader">
168 <RiftHeader />
169 </div>);
170 // AppHeader = null;
171 const classNames = ClassNames('ComposerApp');
172 const isNew = self.state.item && self.state.item.uiState.isNew;
173 const hasItem = self.state.item && self.state.item.uiState;
174 const isModified = self.state.item && self.state.item.uiState.modified;
175 const isEditingNSD = self.state.item && self.state.item.uiState && /nsd/.test(self.state.item.uiState.type);
176 const isEditingVNFD = self.state.item && self.state.item.uiState && /vnfd/.test(self.state.item.uiState.type);
177 const containers = [self.state.item].reduce(DescriptorModelFactory.buildCatalogItemFactory(CatalogDataStore.getState().catalogs), []);
178
179 containers.filter(d => DescriptorModelFactory.isConnectionPoint(d)).forEach(d => {
180 d.cpNumber = ++cpNumber;
181 containers.filter(d => DescriptorModelFactory.isVnfdConnectionPointRef(d)).filter(ref => ref.key === d.key).forEach(ref => ref.cpNumber = d.cpNumber);
182 });
183 const canvasTitle = containers.length ? containers[0].model.name : '';
184 const hasNoCatalogs = CatalogDataStore.getState().catalogs.length === 0;
185 const isLoading = self.state.isLoading;
186
187 //Bridge element for Crouton fix. Should eventually put Composer on same flux context
188 const Bridge = this.state.ComponentBridgeElement;
189
190 html = (
191 <div ref="appRoot" id="RIFT_wareLaunchpadComposerAppRoot" className="AppRoot" onClick={onClickUpdateSelection}>
192 <Bridge />
193 <i className="corner-accent top left" />
194 <i className="corner-accent top right" />
195 <i className="corner-accent bottom left" />
196 <i className="corner-accent bottom right" />
197 {AppHeader}
198 <div className="AppBody">
199 <div className={classNames}>
200 <CatalogPanel layout={self.state.layout}
201 isLoading={isLoading}
202 hasNoCatalogs={hasNoCatalogs}
203 filterByType={self.state.filterCatalogByTypeValue} />
204 <CanvasPanel layout={self.state.layout}
205 hasNoCatalogs={hasNoCatalogs}
206 showMore={self.state.showMore}
207 containers={containers}
208 title={canvasTitle}
209 zoom={self.state.zoom}
210 panelTabShown={self.state.panelTabShown}
211 files={self.state.files}
212 filesState={self.state.filesState}
213 item={self.state.item}
214 type={self.state.filterCatalogByTypeValue}
215 displayedPanel={self.state.displayedPanel}
216 />
217 {
218 (self.state.panelTabShown == 'descriptor') ?
219 <DetailsPanel layout={self.state.layout}
220 hasNoCatalogs={hasNoCatalogs}
221 showMore={self.state.showMore}
222 containers={containers}
223 showJSONViewer={self.state.showJSONViewer} />
224 : null
225 }
226
227 <ComposerAppToolbar layout={self.state.layout}
228 showMore={self.state.showMore}
229 isEditingNSD={isEditingNSD}
230 isEditingVNFD={isEditingVNFD}
231 isModified={isModified}
232 isNew={isNew}
233 disabled={!hasItem}
234 onClick={event => event.stopPropagation()}
235 panelTabShown={self.state.panelTabShown}/>
236 </div>
237 </div>
238 <ModalOverlay />
239 </div>
240 );
241 } else {
242 this.getModel();
243 }
244 return html;
245 },
246 onChange(state) {
247 this.setState(state);
248 },
249 onCatalogDataChanged(catalogDataState) {
250 const catalogs = catalogDataState.catalogs;
251 const unsavedChanges = catalogs.reduce((result, catalog) => {
252 if (result) {
253 return result;
254 }
255 return catalog.descriptors.reduce((result, descriptor) => {
256 if (result) {
257 return result;
258 }
259 return descriptor.uiState.modified;
260 }, false);
261 }, false);
262 this.setState({
263 unsavedChanges: unsavedChanges,
264 isLoading: catalogDataState.isLoading
265 });
266 },
267 onBeforeUnload() {
268 // https://trello.com/c/c8v321Xx/160-prompt-user-to-save-changes
269 //const snapshot = alt.takeSnapshot();
270 //window.localStorage.setItem('composer', snapshot);
271 if (this.state.unsavedChanges) {
272 return 'You have unsaved changes. If you do not onboard (or update) your changes they will be lost.';
273 }
274 }
275
276 });
277
278 export default ComposerApp;