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