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