RIFT-15487: composer assets download file fix
[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
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.containers;
178 const canvasTitle = containers.length ? containers[0].model.name : '';
179 const hasNoCatalogs = CatalogDataStore.getState().catalogs.length === 0;
180 const isLoading = self.state.isLoading;
181
182 //Bridge element for Crouton fix. Should eventually put Composer on same flux context
183 const Bridge = this.state.ComponentBridgeElement;
184
185 html = (
186 <div ref="appRoot" id="RIFT_wareLaunchpadComposerAppRoot" className="AppRoot" onClick={onClickUpdateSelection}>
187 <Bridge />
188 <i className="corner-accent top left" />
189 <i className="corner-accent top right" />
190 <i className="corner-accent bottom left" />
191 <i className="corner-accent bottom right" />
192 {AppHeader}
193 <div className="AppBody">
194 <div className={classNames}>
195 <CatalogPanel layout={self.state.layout}
196 isLoading={isLoading}
197 hasNoCatalogs={hasNoCatalogs}
198 filterByType={self.state.filterCatalogByTypeValue} />
199 <CanvasPanel layout={self.state.layout}
200 hasNoCatalogs={hasNoCatalogs}
201 showMore={self.state.showMore}
202 containers={containers}
203 title={canvasTitle}
204 zoom={self.state.zoom}
205 panelTabShown={self.state.panelTabShown}
206 files={self.state.files}
207 filesState={self.state.filesState}
208 item={self.state.item}
209 type={self.state.filterCatalogByTypeValue}
210 />
211 {
212 (self.state.panelTabShown == 'descriptor') ?
213 <DetailsPanel layout={self.state.layout}
214 hasNoCatalogs={hasNoCatalogs}
215 showMore={self.state.showMore}
216 containers={containers}
217 showJSONViewer={self.state.showJSONViewer} />
218 : null
219 }
220
221 <ComposerAppToolbar layout={self.state.layout}
222 showMore={self.state.showMore}
223 isEditingNSD={isEditingNSD}
224 isEditingVNFD={isEditingVNFD}
225 isModified={isModified}
226 isNew={isNew}
227 disabled={!hasItem}
228 onClick={event => event.stopPropagation()}
229 panelTabShown={self.state.panelTabShown}/>
230 </div>
231 </div>
232 <ModalOverlay />
233 </div>
234 );
235 } else {
236 this.getModel();
237 }
238 return html;
239 },
240 onChange(state) {
241 this.setState(state);
242 },
243 onCatalogDataChanged(catalogDataState) {
244 const catalogs = catalogDataState.catalogs;
245 const unsavedChanges = catalogs.reduce((result, catalog) => {
246 if (result) {
247 return result;
248 }
249 return catalog.descriptors.reduce((result, descriptor) => {
250 if (result) {
251 return result;
252 }
253 return descriptor.uiState.modified;
254 }, false);
255 }, false);
256 this.setState({
257 unsavedChanges: unsavedChanges,
258 isLoading: catalogDataState.isLoading
259 });
260 },
261 onBeforeUnload() {
262 // https://trello.com/c/c8v321Xx/160-prompt-user-to-save-changes
263 //const snapshot = alt.takeSnapshot();
264 //window.localStorage.setItem('composer', snapshot);
265 if (this.state.unsavedChanges) {
266 return 'You have unsaved changes. If you do not onboard (or update) your changes they will be lost.';
267 }
268 }
269
270 });
271
272 export default ComposerApp;