Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / plugins / composer / src / src / stores / ComposerAppStore.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19 'use strict';
20
21 import _ from 'lodash'
22 import d3 from 'd3'
23 import alt from '../alt'
24 import UID from '../libraries/UniqueId'
25 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
26 import PanelResizeAction from '../actions/PanelResizeAction'
27 import CatalogItemsActions from '../actions/CatalogItemsActions'
28 import CanvasEditorActions from '../actions/CanvasEditorActions'
29 import ComposerAppActions from '../actions/ComposerAppActions'
30 import CatalogFilterActions from '../actions/CatalogFilterActions'
31 import CanvasPanelTrayActions from '../actions/CanvasPanelTrayActions'
32 import SelectionManager from '../libraries/SelectionManager'
33 import CatalogDataStore from '../stores/CatalogDataStore'
34 import isFullScreen from '../libraries/isFullScreen'
35
36 const getDefault = (name, defaultValue) => {
37 const val = window.localStorage.getItem('defaults-' + name);
38 if (val) {
39 if (_.isNumber(val)) {
40 if (val < 0) {
41 return setDefault(name, 0);
42 }
43 }
44 return Number(val);
45 }
46 setDefault(name, defaultValue);
47 return defaultValue;
48 };
49
50 const setDefault = (name, defaultValue) => {
51 window.localStorage.setItem('defaults-' + name, defaultValue);
52 return defaultValue;
53 };
54
55 /* the top and bottom positions are managed by css; requires div to be display: absolute*/
56 const defaults = {
57 left: getDefault('catalog-panel-start-width', 300),
58 right: getDefault('details-panel-start-width', 365),
59 bottom: 25 + getDefault('defaults-forwarding-graphs-panel-start-height', 0),
60 showMore: false,
61 zoom: getDefault('zoom', 100),
62 filterCatalogBy: 'nsd',
63 defaultPanelTrayOpenZoom: (() => {
64 let zoom = parseFloat(getDefault('panel-tray-zoom', 75));
65 if (isNaN(zoom)) {
66 zoom = 75;
67 }
68 zoom = Math.min(100, zoom);
69 zoom = Math.max(25, zoom);
70 setDefault('panel-tray-zoom', zoom);
71 return zoom;
72 })()
73 };
74
75 const autoZoomCanvasScale = d3.scale.linear().domain([0, 300]).range([100, 50]).clamp(true);
76
77 const uiTransientState = {};
78
79 class ComposerAppStore {
80
81 constructor() {
82 // the catalog item currently being edited in the composer
83 this.item = null;
84 // the left and right sides of the canvas area
85 this.layout = {
86 left: defaults.left,
87 right: defaults.right,
88 bottom: defaults.bottom
89 };
90 uiTransientState.restoreLayout = this.layout;
91 this.zoom = defaults.zoom;
92 this.showMore = defaults.showMore;
93 this.filterCatalogByTypeValue = defaults.filterCatalogBy;
94 // transient ui state
95 this.drag = null;
96 this.message = '';
97 this.messageType = '';
98 this.showJSONViewer = false;
99 this.showClassifiers = {};
100 this.editPathsMode = false;
101 this.fullScreenMode = false;
102 this.bindListeners({
103 onResize: PanelResizeAction.RESIZE,
104 editCatalogItem: CatalogItemsActions.EDIT_CATALOG_ITEM,
105 catalogItemMetaDataChanged: CatalogItemsActions.CATALOG_ITEM_META_DATA_CHANGED,
106 catalogItemDescriptorChanged: CatalogItemsActions.CATALOG_ITEM_DESCRIPTOR_CHANGED,
107 toggleShowMoreInfo: CanvasEditorActions.TOGGLE_SHOW_MORE_INFO,
108 showMoreInfo: CanvasEditorActions.SHOW_MORE_INFO,
109 showLessInfo: CanvasEditorActions.SHOW_LESS_INFO,
110 applyDefaultLayout: CanvasEditorActions.APPLY_DEFAULT_LAYOUT,
111 addVirtualLinkDescriptor: CanvasEditorActions.ADD_VIRTUAL_LINK_DESCRIPTOR,
112 addForwardingGraphDescriptor: CanvasEditorActions.ADD_FORWARDING_GRAPH_DESCRIPTOR,
113 addVirtualDeploymentDescriptor: CanvasEditorActions.ADD_VIRTUAL_DEPLOYMENT_DESCRIPTOR,
114 selectModel: ComposerAppActions.SELECT_MODEL,
115 outlineModel: ComposerAppActions.OUTLINE_MODEL,
116 showError: ComposerAppActions.SHOW_ERROR,
117 clearError: ComposerAppActions.CLEAR_ERROR,
118 setDragState: ComposerAppActions.SET_DRAG_STATE,
119 filterCatalogByType: CatalogFilterActions.FILTER_BY_TYPE,
120 setCanvasZoom: CanvasEditorActions.SET_CANVAS_ZOOM,
121 showJsonViewer: ComposerAppActions.SHOW_JSON_VIEWER,
122 closeJsonViewer: ComposerAppActions.CLOSE_JSON_VIEWER,
123 toggleCanvasPanelTray: CanvasPanelTrayActions.TOGGLE_OPEN_CLOSE,
124 openCanvasPanelTray: CanvasPanelTrayActions.OPEN,
125 closeCanvasPanelTray: CanvasPanelTrayActions.CLOSE,
126 enterFullScreenMode: ComposerAppActions.ENTER_FULL_SCREEN_MODE,
127 exitFullScreenMode: ComposerAppActions.EXIT_FULL_SCREEN_MODE
128 });
129 }
130
131 onResize(e) {
132 if (e.type === 'resize-manager.resize.catalog-panel') {
133 const layout = Object.assign({}, this.layout);
134 layout.left = Math.max(0, layout.left - e.moved.x);
135 if (layout.left !== this.layout.left) {
136 this.setState({layout: layout});
137 }
138 } else if (e.type === 'resize-manager.resize.details-panel') {
139 const layout = Object.assign({}, this.layout);
140 layout.right = Math.max(0, layout.right + e.moved.x);
141 if (layout.right !== this.layout.right) {
142 this.setState({layout: layout});
143 }
144 } else if (/^resize-manager\.resize\.canvas-panel-tray/.test(e.type)) {
145 const layout = Object.assign({}, this.layout);
146 layout.bottom = Math.max(25, layout.bottom + e.moved.y);
147 if (layout.bottom !== this.layout.bottom) {
148 const zoom = autoZoomCanvasScale(layout.bottom) ;
149 if (this.zoom !== zoom) {
150 this.setState({layout: layout, zoom: zoom});
151 } else {
152 this.setState({layout: layout});
153 }
154 }
155 } else if (e.type !== 'resize') {
156 console.log('no resize handler for ', e.type, '. Do you need to add a handler in ComposerAppStore::onResize()?')
157 }
158 SelectionManager.refreshOutline();
159 }
160
161 updateItem(item) {
162 if(!document.body.classList.contains('resizing')) {
163 this.setState({item: _.cloneDeep(item)});
164 }
165 SelectionManager.refreshOutline();
166 }
167
168 editCatalogItem(item) {
169 if (item && item.uiState) {
170 item.uiState.isOpenForEdit = true;
171 if (item.uiState.type !== 'nsd') {
172 this.closeCanvasPanelTray();
173 }
174 }
175 SelectionManager.select(item);
176 this.updateItem(item);
177 }
178
179 catalogItemMetaDataChanged(item) {
180 this.updateItem(item);
181 }
182
183 catalogItemDescriptorChanged(itemDescriptor) {
184 this.catalogItemMetaDataChanged(itemDescriptor.model);
185 }
186
187 showMoreInfo() {
188 this.setState({showMore: true});
189 }
190
191 showLessInfo() {
192 this.setState({showMore: false});
193 }
194
195 showError(data) {
196 this.setState({message: data.errorMessage, messageType: 'error'});
197 }
198
199 clearError() {
200 this.setState({message: '', messageType: ''});
201 }
202
203 toggleShowMoreInfo() {
204 this.setState({showMore: !this.showMore});
205 }
206
207 applyDefaultLayout() {
208 if (this.item && this.item.uiState && this.item.uiState.containerPositionMap) {
209 if (!_.isEmpty(this.item.uiState.containerPositionMap)) {
210 this.item.uiState.containerPositionMap = {};
211 CatalogItemsActions.catalogItemMetaDataChanged.defer(this.item);
212 }
213 }
214 }
215
216 addVirtualLinkDescriptor(dropCoordinates = null) {
217 let vld;
218 if (this.item) {
219 if (this.item.uiState.type === 'nsd') {
220 const nsdc = DescriptorModelFactory.newNetworkService(this.item);
221 vld = nsdc.createVld();
222 } else if (this.item.uiState.type === 'vnfd') {
223 const vnfd = DescriptorModelFactory.newVirtualNetworkFunction(this.item);
224 vld = vnfd.createVld();
225 }
226 if (vld) {
227 vld.uiState.dropCoordinates = dropCoordinates;
228 SelectionManager.clearSelectionAndRemoveOutline();
229 SelectionManager.addSelection(vld);
230 this.updateItem(vld.getRoot().model);
231 CatalogItemsActions.catalogItemDescriptorChanged.defer(vld.getRoot());
232 }
233 }
234 }
235
236 addForwardingGraphDescriptor(dropCoordinates = null) {
237 if (this.item && this.item.uiState.type === 'nsd') {
238 const nsdc = DescriptorModelFactory.newNetworkService(this.item);
239 const fg = nsdc.createVnffgd();
240 fg.uiState.dropCoordinates = dropCoordinates;
241 SelectionManager.clearSelectionAndRemoveOutline();
242 SelectionManager.addSelection(fg);
243 this.updateItem(nsdc.model);
244 CatalogItemsActions.catalogItemDescriptorChanged.defer(nsdc);
245 }
246 }
247
248 addVirtualDeploymentDescriptor(dropCoordinates = null) {
249 if (this.item.uiState.type === 'vnfd') {
250 const vnfd = DescriptorModelFactory.newVirtualNetworkFunction(this.item);
251 const vdu = vnfd.createVdu();
252 vdu.uiState.dropCoordinates = dropCoordinates;
253 SelectionManager.clearSelectionAndRemoveOutline();
254 SelectionManager.addSelection(vdu);
255 this.updateItem(vdu.getRoot().model);
256 CatalogItemsActions.catalogItemDescriptorChanged.defer(vdu.getRoot());
257 }
258 }
259
260 selectModel(container) {
261 if (SelectionManager.select(container)) {
262 const model = DescriptorModelFactory.isContainer(container) ? container.getRoot().model : container;
263 this.catalogItemMetaDataChanged(model);
264 }
265 }
266
267 outlineModel(obj) {
268 const uid = UID.from(obj);
269 requestAnimationFrame(() => {
270 SelectionManager.outline(Array.from(document.querySelectorAll(`[data-uid="${uid}"]`)));
271 });
272 }
273
274 clearSelection() {
275 SelectionManager.clearSelectionAndRemoveOutline();
276 this.catalogItemMetaDataChanged(this.item);
277 }
278
279 setDragState(dragState) {
280 this.setState({drag: dragState});
281 }
282
283 filterCatalogByType(typeValue) {
284 this.setState({filterCatalogByTypeValue: typeValue})
285 }
286
287 setCanvasZoom(zoom) {
288 this.setState({zoom: zoom});
289 }
290
291 showJsonViewer() {
292 this.setState({showJSONViewer: true});
293 }
294
295 closeJsonViewer() {
296 this.setState({showJSONViewer: false});
297 }
298
299 toggleCanvasPanelTray() {
300 const layout = this.layout;
301 if (layout.bottom > 25) {
302 this.closeCanvasPanelTray();
303 } else {
304 this.openCanvasPanelTray();
305 }
306 }
307
308 openCanvasPanelTray() {
309 const layout = {
310 left: this.layout.left,
311 right: this.layout.right,
312 bottom: 300
313 };
314 const zoom = defaults.defaultPanelTrayOpenZoom;
315 if (this.zoom !== zoom) {
316 this.setState({layout: layout, zoom: zoom, restoreZoom: this.zoom});
317 } else {
318 this.setState({layout: layout});
319 }
320 }
321
322 closeCanvasPanelTray() {
323 const layout = {
324 left: this.layout.left,
325 right: this.layout.right,
326 bottom: 25
327 };
328 const zoom = this.restoreZoom || autoZoomCanvasScale(layout.bottom);
329 if (this.zoom !== zoom) {
330 this.setState({layout: layout, zoom: zoom, restoreZoom: null});
331 } else {
332 this.setState({layout: layout, restoreZoom: null});
333 }
334 }
335
336 enterFullScreenMode() {
337
338 /**
339 * https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
340 * This is an experimental api but works our target browsers and ignored by others
341 */
342 const eventNames = ['fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
343
344 const appRoot = document.body;//.getElementById('RIFT_wareLaunchpadComposerAppRoot');
345
346 const comp = this;
347
348 function onFullScreenChange() {
349
350 if (isFullScreen()) {
351 const layout = comp.layout;
352 const restoreLayout = _.cloneDeep(layout);
353 uiTransientState.restoreLayout = restoreLayout;
354 layout.left = 0;
355 layout.right = 0;
356 comp.setState({fullScreenMode: true, layout: layout, restoreLayout: restoreLayout});
357 } else {
358 comp.setState({fullScreenMode: false, layout: uiTransientState.restoreLayout});
359 }
360
361 }
362
363 if (this.fullScreenMode === false) {
364
365 if (appRoot.requestFullscreen) {
366 appRoot.requestFullscreen();
367 } else if (appRoot.msRequestFullscreen) {
368 appRoot.msRequestFullscreen();
369 } else if (appRoot.mozRequestFullScreen) {
370 appRoot.mozRequestFullScreen();
371 } else if (appRoot.webkitRequestFullscreen) {
372 appRoot.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
373 }
374
375 eventNames.map(name => {
376 document.removeEventListener(name, onFullScreenChange);
377 document.addEventListener(name, onFullScreenChange);
378 });
379
380 }
381
382 }
383
384 exitFullScreenMode() {
385
386 if (document.exitFullscreen) {
387 document.exitFullscreen();
388 } else if (document.msExitFullscreen) {
389 document.msExitFullscreen();
390 } else if (document.mozCancelFullScreen) {
391 document.mozCancelFullScreen();
392 } else if (document.webkitExitFullscreen) {
393 document.webkitExitFullscreen();
394 }
395
396 this.setState({fullScreenMode: false});
397
398 }
399
400 }
401
402 export default alt.createStore(ComposerAppStore, 'ComposerAppStore');