4 * Copyright 2016 RIFT.IO Inc
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 import _
from 'lodash'
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'
36 import FileManagerSource
from '../components/filemanager/FileManagerSource';
37 import FileManagerActions
from '../components/filemanager/FileManagerActions';
39 import React
from 'react';
41 //Hack for crouton fix. Should eventually put composer in skyquake alt context
42 import SkyquakeComponent
from 'widgets/skyquake_container/skyquakeComponent.jsx';
43 let NotificationError
= null;
44 class ComponentBridge
extends React
.Component
{
47 NotificationError
= this.props
.flux
.actions
.global
.showNotification
;
53 const getDefault
= (name
, defaultValue
) => {
54 const val
= window
.localStorage
.getItem('defaults-' + name
);
56 if (_
.isNumber(val
)) {
58 return setDefault(name
, 0);
63 setDefault(name
, defaultValue
);
67 const setDefault
= (name
, defaultValue
) => {
68 window
.localStorage
.setItem('defaults-' + name
, defaultValue
);
72 /* the top and bottom positions are managed by css; requires div to be display: absolute*/
74 left
: getDefault('catalog-panel-start-width', 300),
75 right
: getDefault('details-panel-start-width', 365),
76 bottom
: 25 + getDefault('defaults-forwarding-graphs-panel-start-height', 0),
78 zoom
: getDefault('zoom', 100),
79 filterCatalogBy
: 'nsd',
80 defaultPanelTrayOpenZoom
: (() => {
81 let zoom
= parseFloat(getDefault('panel-tray-zoom', 75));
85 zoom
= Math
.min(100, zoom
);
86 zoom
= Math
.max(25, zoom
);
87 setDefault('panel-tray-zoom', zoom
);
92 const autoZoomCanvasScale
= d3
.scale
.linear().domain([0, 300]).range([100, 50]).clamp(true);
94 const uiTransientState
= {};
96 class ComposerAppStore
{
99 //Bridge for crouton fix
100 this.ComponentBridgeElement
= SkyquakeComponent(ComponentBridge
);
102 this.exportAsync(FileManagerSource
)
103 // the catalog item currently being edited in the composer
105 // the left and right sides of the canvas area
108 right
: defaults
.right
,
109 bottom
: defaults
.bottom
111 uiTransientState
.restoreLayout
= this.layout
;
112 this.zoom
= defaults
.zoom
;
113 this.showMore
= defaults
.showMore
;
114 this.filterCatalogByTypeValue
= defaults
.filterCatalogBy
;
115 // transient ui state
118 this.messageType
= '';
119 this.showJSONViewer
= false;
120 this.showClassifiers
= {};
121 this.editPathsMode
= false;
122 this.fullScreenMode
= false;
123 this.panelTabShown
= 'descriptor';
124 //File manager values
126 this.filesState
= {};
127 this.downloadJobs
= {};
128 this.displayedPanel
= 'forwarding' //or parameter
129 //End File manager values
131 onResize
: PanelResizeAction
.RESIZE
,
132 editCatalogItem
: CatalogItemsActions
.EDIT_CATALOG_ITEM
,
133 catalogItemMetaDataChanged
: CatalogItemsActions
.CATALOG_ITEM_META_DATA_CHANGED
,
134 catalogItemDescriptorChanged
: CatalogItemsActions
.CATALOG_ITEM_DESCRIPTOR_CHANGED
,
135 toggleShowMoreInfo
: CanvasEditorActions
.TOGGLE_SHOW_MORE_INFO
,
136 showMoreInfo
: CanvasEditorActions
.SHOW_MORE_INFO
,
137 showLessInfo
: CanvasEditorActions
.SHOW_LESS_INFO
,
138 applyDefaultLayout
: CanvasEditorActions
.APPLY_DEFAULT_LAYOUT
,
139 addVirtualLinkDescriptor
: CanvasEditorActions
.ADD_VIRTUAL_LINK_DESCRIPTOR
,
140 addForwardingGraphDescriptor
: CanvasEditorActions
.ADD_FORWARDING_GRAPH_DESCRIPTOR
,
141 addVirtualDeploymentDescriptor
: CanvasEditorActions
.ADD_VIRTUAL_DEPLOYMENT_DESCRIPTOR
,
142 selectModel
: ComposerAppActions
.SELECT_MODEL
,
143 outlineModel
: ComposerAppActions
.OUTLINE_MODEL
,
144 showError
: ComposerAppActions
.SHOW_ERROR
,
145 clearError
: ComposerAppActions
.CLEAR_ERROR
,
146 setDragState
: ComposerAppActions
.SET_DRAG_STATE
,
147 filterCatalogByType
: CatalogFilterActions
.FILTER_BY_TYPE
,
148 setCanvasZoom
: CanvasEditorActions
.SET_CANVAS_ZOOM
,
149 showJsonViewer
: ComposerAppActions
.SHOW_JSON_VIEWER
,
150 closeJsonViewer
: ComposerAppActions
.CLOSE_JSON_VIEWER
,
151 toggleCanvasPanelTray
: CanvasPanelTrayActions
.TOGGLE_OPEN_CLOSE
,
152 openCanvasPanelTray
: CanvasPanelTrayActions
.OPEN
,
153 closeCanvasPanelTray
: CanvasPanelTrayActions
.CLOSE
,
154 enterFullScreenMode
: ComposerAppActions
.ENTER_FULL_SCREEN_MODE
,
155 exitFullScreenMode
: ComposerAppActions
.EXIT_FULL_SCREEN_MODE
,
156 showAssets
: ComposerAppActions
.showAssets
,
157 showDescriptor
: ComposerAppActions
.showDescriptor
,
158 getFilelistSuccess
: FileManagerActions
.getFilelistSuccess
,
159 updateFileLocationInput
: FileManagerActions
.updateFileLocationInput
,
160 sendDownloadFileRequst
: FileManagerActions
.sendDownloadFileRequst
,
161 addFileSuccess
: FileManagerActions
.addFileSuccess
,
162 deletePackageFile
: FileManagerActions
.deletePackageFile
,
163 deleteFileSuccess
: FileManagerActions
.deleteFileSuccess
,
164 closeFileManagerSockets
: FileManagerActions
.closeFileManagerSockets
,
165 openFileManagerSockets
: FileManagerActions
.openFileManagerSockets
,
166 openDownloadMonitoringSocketSuccess
: FileManagerActions
.openDownloadMonitoringSocketSuccess
,
167 getFilelistSocketSuccess
: FileManagerActions
.getFilelistSocketSuccess
169 this.exportPublicMethods({
170 closeFileManagerSockets
: this.closeFileManagerSockets
.bind(this)
175 if (e
.type
=== 'resize-manager.resize.catalog-panel') {
176 const layout
= Object
.assign({}, this.layout
);
177 layout
.left
= Math
.max(0, layout
.left
- e
.moved
.x
);
178 if (layout
.left
!== this.layout
.left
) {
179 this.setState({layout
: layout
});
181 } else if (e
.type
=== 'resize-manager.resize.details-panel') {
182 const layout
= Object
.assign({}, this.layout
);
183 layout
.right
= Math
.max(0, layout
.right
+ e
.moved
.x
);
184 if (layout
.right
!== this.layout
.right
) {
185 this.setState({layout
: layout
});
187 } else if (/^resize-manager\.resize\.canvas-panel-tray/.test(e
.type
)) {
188 const layout
= Object
.assign({}, this.layout
);
189 layout
.bottom
= Math
.max(25, layout
.bottom
+ e
.moved
.y
);
190 if (layout
.bottom
!== this.layout
.bottom
) {
191 const zoom
= autoZoomCanvasScale(layout
.bottom
) ;
192 if (this.zoom
!== zoom
) {
193 this.setState({layout
: layout
, zoom
: zoom
});
195 this.setState({layout
: layout
});
198 } else if (e
.type
!== 'resize') {
199 console
.log('no resize handler for ', e
.type
, '. Do you need to add a handler in ComposerAppStore::onResize()?')
201 SelectionManager
.refreshOutline();
205 if(!document
.body
.classList
.contains('resizing')) {
206 this.setState({item
: _
.cloneDeep(item
)});
208 SelectionManager
.refreshOutline();
211 editCatalogItem(item
) {
213 self
.closeFileManagerSockets();
214 if (item
&& item
.uiState
) {
215 item
.uiState
.isOpenForEdit
= true;
216 if (item
.uiState
.type
!== 'nsd') {
217 this.closeCanvasPanelTray();
220 SelectionManager
.select(item
);
221 this.updateItem(item
);
222 this.openFileManagerSockets(item
)
224 catalogItemMetaDataChanged(item
) {
225 this.updateItem(item
);
228 catalogItemDescriptorChanged(itemDescriptor
) {
229 this.catalogItemMetaDataChanged(itemDescriptor
.model
);
233 this.setState({showMore
: true});
237 this.setState({showMore
: false});
241 NotificationError
.defer({msg
: data
.errorMessage
, type
: 'error'})
242 // this.setState({message: data.errorMessage, messageType: 'error'});
246 this.setState({message
: '', messageType
: ''});
249 toggleShowMoreInfo() {
250 this.setState({showMore
: !this.showMore
});
253 applyDefaultLayout() {
254 if (this.item
&& this.item
.uiState
&& this.item
.uiState
.containerPositionMap
) {
255 if (!_
.isEmpty(this.item
.uiState
.containerPositionMap
)) {
256 this.item
.uiState
.containerPositionMap
= {};
257 CatalogItemsActions
.catalogItemMetaDataChanged
.defer(this.item
);
262 addVirtualLinkDescriptor(dropCoordinates
= null) {
265 if (this.item
.uiState
.type
=== 'nsd') {
266 const nsdc
= DescriptorModelFactory
.newNetworkService(this.item
);
267 vld
= nsdc
.createVld();
268 } else if (this.item
.uiState
.type
=== 'vnfd') {
269 const vnfd
= DescriptorModelFactory
.newVirtualNetworkFunction(this.item
);
270 vld
= vnfd
.createVld();
273 vld
.uiState
.dropCoordinates
= dropCoordinates
;
274 SelectionManager
.clearSelectionAndRemoveOutline();
275 SelectionManager
.addSelection(vld
);
276 this.updateItem(vld
.getRoot().model
);
277 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(vld
.getRoot());
282 addForwardingGraphDescriptor(dropCoordinates
= null) {
283 if (this.item
&& this.item
.uiState
.type
=== 'nsd') {
284 const nsdc
= DescriptorModelFactory
.newNetworkService(this.item
);
285 const fg
= nsdc
.createVnffgd();
286 fg
.uiState
.dropCoordinates
= dropCoordinates
;
287 SelectionManager
.clearSelectionAndRemoveOutline();
288 SelectionManager
.addSelection(fg
);
289 this.updateItem(nsdc
.model
);
290 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(nsdc
);
294 addVirtualDeploymentDescriptor(dropCoordinates
= null) {
295 if (this.item
.uiState
.type
=== 'vnfd') {
296 const vnfd
= DescriptorModelFactory
.newVirtualNetworkFunction(this.item
);
297 const vdu
= vnfd
.createVdu();
298 vdu
.uiState
.dropCoordinates
= dropCoordinates
;
299 SelectionManager
.clearSelectionAndRemoveOutline();
300 SelectionManager
.addSelection(vdu
);
301 this.updateItem(vdu
.getRoot().model
);
302 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(vdu
.getRoot());
306 selectModel(container
) {
307 if (SelectionManager
.select(container
)) {
308 const model
= DescriptorModelFactory
.isContainer(container
) ? container
.getRoot().model
: container
;
309 this.catalogItemMetaDataChanged(model
);
314 const uid
= UID
.from(obj
);
315 requestAnimationFrame(() => {
316 SelectionManager
.outline(Array
.from(document
.querySelectorAll(`[data-uid="${uid}"]`)));
321 SelectionManager
.clearSelectionAndRemoveOutline();
322 this.catalogItemMetaDataChanged(this.item
);
325 setDragState(dragState
) {
326 this.setState({drag
: dragState
});
329 filterCatalogByType(typeValue
) {
330 this.setState({filterCatalogByTypeValue
: typeValue
})
333 setCanvasZoom(zoom
) {
334 this.setState({zoom
: zoom
});
338 this.setState({showJSONViewer
: true});
342 this.setState({showJSONViewer
: false});
345 toggleCanvasPanelTray(event
) {
346 const layout
= this.layout
;
347 const attrMap
= event
.target
.attributes
;
348 let panelEvent
= null;
349 for(let k
in attrMap
) {
350 if(attrMap
[k
].name
== 'data-event') {
351 panelEvent
= attrMap
[k
].nodeValue
;
354 if ((layout
.bottom
> 25) && ((panelEvent
== this.displayedPanel
) || panelEvent
== 'arrow')) {
355 this.closeCanvasPanelTray();
357 this.openCanvasPanelTray();
359 if(panelEvent
!= 'arrow'){
360 this.setState({displayedPanel
: panelEvent
})
364 openCanvasPanelTray() {
366 left
: this.layout
.left
,
367 right
: this.layout
.right
,
370 const zoom
= defaults
.defaultPanelTrayOpenZoom
;
371 if (this.zoom
!== zoom
) {
372 this.setState({layout
: layout
, zoom
: zoom
, restoreZoom
: this.zoom
});
374 this.setState({layout
: layout
});
378 closeCanvasPanelTray() {
380 left
: this.layout
.left
,
381 right
: this.layout
.right
,
384 const zoom
= this.restoreZoom
|| autoZoomCanvasScale(layout
.bottom
);
385 if (this.zoom
!== zoom
) {
386 this.setState({layout
: layout
, zoom
: zoom
, restoreZoom
: null});
388 this.setState({layout
: layout
, restoreZoom
: null});
392 enterFullScreenMode() {
395 * https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
396 * This is an experimental api but works our target browsers and ignored by others
398 const eventNames
= ['fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
400 const appRoot
= document
.body
;//.getElementById('RIFT_wareLaunchpadComposerAppRoot');
404 function onFullScreenChange() {
406 if (isFullScreen()) {
407 const layout
= comp
.layout
;
408 const restoreLayout
= _
.cloneDeep(layout
);
409 uiTransientState
.restoreLayout
= restoreLayout
;
412 comp
.setState({fullScreenMode
: true, layout
: layout
, restoreLayout
: restoreLayout
});
414 comp
.setState({fullScreenMode
: false, layout
: uiTransientState
.restoreLayout
});
419 if (this.fullScreenMode
=== false) {
421 if (appRoot
.requestFullscreen
) {
422 appRoot
.requestFullscreen();
423 } else if (appRoot
.msRequestFullscreen
) {
424 appRoot
.msRequestFullscreen();
425 } else if (appRoot
.mozRequestFullScreen
) {
426 appRoot
.mozRequestFullScreen();
427 } else if (appRoot
.webkitRequestFullscreen
) {
428 appRoot
.webkitRequestFullscreen(Element
.ALLOW_KEYBOARD_INPUT
);
431 eventNames
.map(name
=> {
432 document
.removeEventListener(name
, onFullScreenChange
);
433 document
.addEventListener(name
, onFullScreenChange
);
440 exitFullScreenMode() {
442 if (document
.exitFullscreen
) {
443 document
.exitFullscreen();
444 } else if (document
.msExitFullscreen
) {
445 document
.msExitFullscreen();
446 } else if (document
.mozCancelFullScreen
) {
447 document
.mozCancelFullScreen();
448 } else if (document
.webkitExitFullscreen
) {
449 document
.webkitExitFullscreen();
452 this.setState({fullScreenMode
: false});
457 panelTabShown
: 'assets'
462 panelTabShown
: 'descriptor'
466 //File Manager methods
467 getFilelistSuccess(data
) {
469 let filesState
= null;
470 if (self
.fileMonitoringSocketID
) {
472 if(data
.hasOwnProperty('contents')) {
473 filesState
= addInputState( _
.cloneDeep(this.filesState
),data
);
474 // filesState = _.merge(self.filesState, addInputState({},data));
475 let normalizedData
= normalizeTree(data
);
478 data
: _
.mergeWith(normalizedData
.data
, self
.files
.data
, function(obj
, src
) {
479 return _
.uniqBy(obj
? obj
.concat(src
) : src
, 'name');
481 id
: self
.files
.id
|| normalizedData
.id
483 filesState
: filesState
491 this.setState(newState
);
493 function normalizeTree(data
) {
498 data
.contents
.map(getContents
);
499 function getContents(d
) {
500 if(d
.hasOwnProperty('contents')) {
502 d
.contents
.map(function(c
,i
) {
503 if (!c
.hasOwnProperty('contents')) {
510 f
.data
[d
.name
] = contents
;
515 function addInputState(obj
, d
) {
517 if(d
.hasOwnProperty('contents')) {
518 d
.contents
.map(addInputState
.bind(null, obj
))
526 sendDownloadFileRequst(data
) {
527 let id
= data
.id
|| this.item
.id
;
528 let type
= data
.type
|| this.item
.uiState
.type
;
529 let path
= data
.path
;
531 this.getInstance().addFile(id
, type
, path
, url
);
533 updateFileLocationInput
= (data
) => {
534 let name
= data
.name
;
535 let value
= data
.value
;
536 var filesState
= _
.cloneDeep(this.filesState
);
537 filesState
[name
] = value
;
539 filesState
: filesState
542 addFileSuccess
= (data
) => {
543 let path
= data
.path
;
544 let fileName
= data
.fileName
;
545 let files
= _
.cloneDeep(this.files
);
546 let loadingIndex
= files
.data
[path
].push({
547 status
: 'DOWNLOADING',
548 name
: path
+ '/' + fileName
550 this.setState({files
: files
});
553 startWatchingJob
= () => {
554 let ws
= window
.multiplexer
.channel(this.jobSocketId
);
559 openDownloadMonitoringSocketSuccess
= (id
) => {
561 let ws
= window
.multiplexer
.channel(id
);
562 let downloadJobs
= _
.cloneDeep(self
.downloadJobs
);
563 let newFiles
= false;
564 ws
.onmessage
= (socket
) => {
565 if (self
.files
&& self
.files
.length
> 0) {
568 jobs
= JSON
.parse(socket
.data
);
570 newFiles
= _
.cloneDeep(self
.files
);
571 jobs
.map(function(j
) {
572 //check if not in completed state
573 let fullPath
= j
['package-path'];
574 let path
= fullPath
.split('/');
575 let fileName
= path
.pop();
576 path
= path
.join('/');
577 let index
= _
.findIndex(self
.files
.data
[path
], function(o
){
578 return fullPath
== o
.name
580 if((index
> -1) && newFiles
.data
[path
][index
]) {
581 newFiles
.data
[path
][index
].status
= j
.status
583 if(j
.status
.toUpperCase() == 'LOADING...' || j
.status
.toUpperCase() == 'IN_PROGRESS') {
584 newFiles
.data
[path
].push({
596 // console.log(JSON.parse(socket.data));
605 getFilelistSocketSuccess
= (id
) => {
607 let ws
= window
.multiplexer
.channel(id
);
608 ws
.onmessage
= (socket
) => {
609 if (self
.fileMonitoringSocketID
) {
612 data
= JSON
.parse(socket
.data
);
614 self
.getFilelistSuccess(data
)
619 fileMonitoringSocketID
: id
,
620 fileMonitoringSocket
: ws
624 closeFileManagerSockets() {
625 this.fileMonitoringSocketID
= null;
628 fileMonitoringSocketID
: null
630 // fileMonitoringSocket : null,
632 this.jobSocket
&& this.jobSocket
.close();
633 this.fileMonitoringSocket
&& this.fileMonitoringSocket
.close();
634 console
.log('closing');
636 openFileManagerSockets(i
) {
638 let item
= i
|| self
.item
;
639 // this.closeFileManagerSockets();
640 this.getInstance().openFileMonitoringSocket(item
.id
, item
.uiState
.type
).then(function() {
641 // // self.getInstance().openDownloadMonitoringSocket(item.id);
643 this.getInstance().openDownloadMonitoringSocket(item
.id
);
648 deletePackageFile(name
) {
649 let id
= this.item
.id
;
650 let type
= this.item
.uiState
.type
;
651 this.getInstance().deleteFile(id
, type
, name
);
653 deleteFileSuccess
= (data
) => {
654 let path
= data
.path
.split('/')
655 let files
= _
.cloneDeep(this.files
);
657 path
= path
.join('/');
658 let pathFiles
= files
.data
[path
]
659 _
.remove(pathFiles
, function(c
) {
660 return c
.name
== data
.path
;
669 export default alt
.createStore(ComposerAppStore
, 'ComposerAppStore');