update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / composer / src / src / stores / ComposerAppStore.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 import _isNumber from 'lodash/isNumber'
21 import _cloneDeep from 'lodash/cloneDeep'
22 import _isEmpty from 'lodash/isEmpty'
23 import _isArray from 'lodash/isArray'
24 import _mergeWith from 'lodash/mergeWith'
25 import _uniqBy from 'lodash/uniqBy'
26 import _isEqual from 'lodash/isEqual'
27 import _findIndex from 'lodash/findIndex'
28 import _remove from 'lodash/remove'
29 import _get from 'lodash/get';
30 import _set from 'lodash/set';
31 import d3 from 'd3'
32 import alt from '../alt'
33 import UID from '../libraries/UniqueId'
34 import DescriptorModel from '../libraries/model/DescriptorModel'
35 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
36 import PanelResizeAction from '../actions/PanelResizeAction'
37 import CatalogItemsActions from '../actions/CatalogItemsActions'
38 import CanvasEditorActions from '../actions/CanvasEditorActions'
39 import DescriptorEditorActions from '../actions/DescriptorEditorActions'
40 import ComposerAppActions from '../actions/ComposerAppActions'
41 import CatalogFilterActions from '../actions/CatalogFilterActions'
42 import CanvasPanelTrayActions from '../actions/CanvasPanelTrayActions'
43 import SelectionManager from '../libraries/SelectionManager'
44 import CatalogDataStore from '../stores/CatalogDataStore'
45 import isFullScreen from '../libraries/isFullScreen'
46
47 import FileManagerSource from '../components/filemanager/FileManagerSource';
48 import FileManagerActions from '../components/filemanager/FileManagerActions';
49
50 import Property from '../libraries/model/DescriptorModelMetaProperty'
51 import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
52
53 import React from 'react';
54
55 //Hack for crouton fix. Should eventually put composer in skyquake alt context
56 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
57 let NotificationError = null;
58
59 import utils from '../libraries/utils';
60
61 class ComponentBridge extends React.Component {
62 constructor(props) {
63 super(props);
64 NotificationError = this.props.flux.actions.global.showNotification;
65 }
66 render() {
67 return <i > </i>
68 }
69 }
70 const getDefault = (name, defaultValue) => {
71 const val = window.localStorage.getItem('defaults-' + name);
72 if (val) {
73 if (_isNumber(val)) {
74 if (val < 0) {
75 return setDefault(name, 0);
76 }
77 }
78 return Number(val);
79 }
80 setDefault(name, defaultValue);
81 return defaultValue;
82 };
83
84 const setDefault = (name, defaultValue) => {
85 window.localStorage.setItem('defaults-' + name, defaultValue);
86 return defaultValue;
87 };
88
89 /* the top and bottom positions are managed by css; requires div to be display: absolute*/
90 const defaults = {
91 left: getDefault('catalog-panel-start-width', 300),
92 right: getDefault('details-panel-start-width', 365),
93 bottom: 25 + getDefault('defaults-forwarding-graphs-panel-start-height', 0),
94 showMore: false,
95 zoom: getDefault('zoom', 100),
96 filterCatalogBy: 'nsd',
97 defaultPanelTrayOpenZoom: (() => {
98 let zoom = parseFloat(getDefault('panel-tray-zoom', 75));
99 if (isNaN(zoom)) {
100 zoom = 75;
101 }
102 zoom = Math.min(100, zoom);
103 zoom = Math.max(25, zoom);
104 setDefault('panel-tray-zoom', zoom);
105 return zoom;
106 })()
107 };
108
109 const autoZoomCanvasScale = d3.scale.linear().domain([0, 300]).range([100, 50]).clamp(true);
110
111 const uiTransientState = {};
112
113 class ComposerAppStore {
114
115 constructor() {
116 //Bridge for crouton fix
117 this.ComponentBridgeElement = SkyquakeComponent(ComponentBridge);
118
119 this.exportAsync(FileManagerSource)
120 // the catalog item currently being edited in the composer
121 this.item = null;
122 // the left and right sides of the canvas area
123 this.layout = {
124 left: defaults.left,
125 right: defaults.right,
126 bottom: defaults.bottom
127 };
128 uiTransientState.restoreLayout = this.layout;
129 this.zoom = defaults.zoom;
130 this.showMore = defaults.showMore;
131 this.filterCatalogByTypeValue = defaults.filterCatalogBy;
132 // transient ui state
133 this.drag = null;
134 this.message = '';
135 this.messageType = '';
136 this.showHelp = { onFocus: false, forAll: true, forNothing: false },
137 this.detailPanel = { collapsed: true, hidden: false }
138 this.showJSONViewer = false;
139 this.showClassifiers = {};
140 this.editPathsMode = false;
141 this.fullScreenMode = false;
142 this.panelTabShown = 'descriptor';
143 //File manager values
144 this.files = false;
145 this.filesState = {};
146 this.downloadJobs = {};
147 this.containers = [];
148 this.newPathName = '';
149 this.displayedPanel = 'forwarding'; //or parameter
150 //End File manager values
151 this.bindListeners({
152 onResize: PanelResizeAction.RESIZE,
153 editCatalogItem: CatalogItemsActions.EDIT_CATALOG_ITEM,
154 catalogItemDescriptorChanged: CatalogItemsActions.CATALOG_ITEM_DESCRIPTOR_CHANGED,
155 toggleShowMoreInfo: CanvasEditorActions.TOGGLE_SHOW_MORE_INFO,
156 showMoreInfo: CanvasEditorActions.SHOW_MORE_INFO,
157 showLessInfo: CanvasEditorActions.SHOW_LESS_INFO,
158 applyDefaultLayout: CanvasEditorActions.APPLY_DEFAULT_LAYOUT,
159 addVirtualLinkDescriptor: CanvasEditorActions.ADD_VIRTUAL_LINK_DESCRIPTOR,
160 addForwardingGraphDescriptor: CanvasEditorActions.ADD_FORWARDING_GRAPH_DESCRIPTOR,
161 addVirtualDeploymentDescriptor: CanvasEditorActions.ADD_VIRTUAL_DEPLOYMENT_DESCRIPTOR,
162 selectModel: ComposerAppActions.SELECT_MODEL,
163 outlineModel: ComposerAppActions.OUTLINE_MODEL,
164 modelSaveError: ComposerAppActions.RECORD_DESCRIPTOR_ERROR,
165 showError: ComposerAppActions.SHOW_ERROR,
166 clearError: ComposerAppActions.CLEAR_ERROR,
167 setDragState: ComposerAppActions.SET_DRAG_STATE,
168 filterCatalogByType: CatalogFilterActions.FILTER_BY_TYPE,
169 setCanvasZoom: CanvasEditorActions.SET_CANVAS_ZOOM,
170 showJsonViewer: ComposerAppActions.SHOW_JSON_VIEWER,
171 closeJsonViewer: ComposerAppActions.CLOSE_JSON_VIEWER,
172 toggleCanvasPanelTray: CanvasPanelTrayActions.TOGGLE_OPEN_CLOSE,
173 openCanvasPanelTray: CanvasPanelTrayActions.OPEN,
174 closeCanvasPanelTray: CanvasPanelTrayActions.CLOSE,
175 enterFullScreenMode: ComposerAppActions.ENTER_FULL_SCREEN_MODE,
176 exitFullScreenMode: ComposerAppActions.EXIT_FULL_SCREEN_MODE,
177 showAssets: ComposerAppActions.showAssets,
178 showDescriptor: ComposerAppActions.showDescriptor,
179 getFilelistSuccess: FileManagerActions.getFilelistSuccess,
180 updateFileLocationInput: FileManagerActions.updateFileLocationInput,
181 sendDownloadFileRequest: FileManagerActions.sendDownloadFileRequest,
182 addFileSuccess: FileManagerActions.addFileSuccess,
183 deletePackageFile: FileManagerActions.deletePackageFile,
184 deleteFileSuccess: FileManagerActions.deleteFileSuccess,
185 deleteFileError: FileManagerActions.deleteFileError,
186 closeFileManagerSockets: FileManagerActions.closeFileManagerSockets,
187 openFileManagerSockets: FileManagerActions.openFileManagerSockets,
188 openDownloadMonitoringSocketSuccess: FileManagerActions.openDownloadMonitoringSocketSuccess,
189 getFilelistSocketSuccess: FileManagerActions.getFilelistSocketSuccess,
190 newPathNameUpdated: FileManagerActions.newPathNameUpdated,
191 createDirectory: FileManagerActions.createDirectory,
192 modelSetOpenState: DescriptorEditorActions.setOpenState,
193 modelError: DescriptorEditorActions.setError,
194 modelSet: DescriptorEditorActions.setValue,
195 modelAssign: DescriptorEditorActions.assignValue,
196 modelListNew: DescriptorEditorActions.addListItem,
197 modelListRemove: DescriptorEditorActions.removeListItem,
198 showHelpForNothing: DescriptorEditorActions.showHelpForNothing,
199 showHelpForAll: DescriptorEditorActions.showHelpForAll,
200 showHelpOnFocus: DescriptorEditorActions.showHelpOnFocus,
201 collapseAllPanels: DescriptorEditorActions.collapseAllPanels,
202 expandAllPanels: DescriptorEditorActions.expandAllPanels,
203 modelForceOpenState: DescriptorEditorActions.expandPanel,
204 showPanelsWithData: DescriptorEditorActions.showPanelsWithData
205
206 });
207 this.exportPublicMethods({
208 closeFileManagerSockets: this.closeFileManagerSockets.bind(this)
209 })
210 }
211
212 onResize(e) {
213 const layout = Object.assign({}, this.layout);
214 if (e.type === 'resize-manager.resize.catalog-panel') {
215 layout.left = Math.max(0, layout.left - e.moved.x);
216 if (layout.left !== this.layout.left) {
217 this.setState({
218 layout: layout
219 });
220 }
221 } else if (e.type === 'resize-manager.resize.details-panel') {
222 layout.right = Math.max(0, layout.right + e.moved.x);
223 if (layout.right !== this.layout.right) {
224 this.setState({
225 layout: layout
226 });
227 }
228 } else if (/^resize-manager\.resize\.canvas-panel-tray/.test(e.type)) {
229 layout.bottom = Math.max(25, layout.bottom + e.moved.y);
230 if (layout.bottom !== this.layout.bottom) {
231 const zoom = autoZoomCanvasScale(layout.bottom);
232 if (this.zoom !== zoom) {
233 this.setState({
234 layout: layout,
235 zoom: zoom
236 });
237 } else {
238 this.setState({
239 layout: layout
240 });
241 }
242 }
243 } else if (e.type == 'resize') {
244 layout.height = e.target.innerHeight;
245 this.setState({
246 layout: layout
247 });
248 } else {
249 console.log('no resize handler for ', e.type, '. Do you need to add a handler in ComposerAppStore::onResize()?')
250 }
251 SelectionManager.refreshOutline();
252 }
253
254 updateItem(item) {
255 const self = this;
256 let containers = [];
257 let cpNumber = 0;
258 if (!document.body.classList.contains('resizing')) {
259 containers = [item].reduce(DescriptorModelFactory.buildCatalogItemFactory(CatalogDataStore.getState().catalogs), []);
260
261 containers.filter(d => DescriptorModelFactory.isConnectionPoint(d)).forEach(d => {
262 d.cpNumber = ++cpNumber;
263 containers.filter(d => DescriptorModelFactory.isVnfdConnectionPointRef(d)).filter(ref => ref.key === d.key).forEach(ref => ref.cpNumber = d.cpNumber);
264 });
265 this.setState({
266 containers: containers,
267 item: _cloneDeep(item)
268 });
269 }
270 SelectionManager.refreshOutline();
271 }
272
273 editCatalogItem(item) {
274 let self = this;
275 self.closeFileManagerSockets();
276 if (item && item.uiState) {
277 item.uiState.isOpenForEdit = true;
278 if (item.uiState.type !== 'nsd') {
279 this.closeCanvasPanelTray();
280 }
281 }
282 SelectionManager.select(item);
283 this.updateItem(item);
284 if (item) {
285 this.openFileManagerSockets(item);
286 }
287 }
288
289 catalogItemDescriptorChanged(itemDescriptor) {
290 this.updateItem(itemDescriptor.model);
291 }
292
293 setItemError(descriptor, path, message) {
294 // save locally and globally
295 descriptor.setUiState('error', path, message);
296 if (descriptor.parent) {
297 let parentPath = [];
298 while (descriptor.parent) {
299 parentPath.push(descriptor.type);
300 const index = descriptor.parent.model[descriptor.type].findIndex(item => item.id === descriptor.id);
301 parentPath.push(index);
302 descriptor = descriptor.parent;
303 }
304 parentPath.length && descriptor.setUiState('error', parentPath.concat(path), message);
305 } else if (path.length > 1 && descriptor[path[0]]) {
306 // if we are indirectly editing a sub model set the error state in the sub model
307 descriptor[path[0]][path[1]].setUiState('error', path.slice(2), message);
308 }
309 }
310
311 modelSaveError(input) {
312 const { descriptor, type, id } = input;
313 const errorMessage = {
314 'data-missing': "Required value.",
315 'missing-element': "Incomplete configuration."
316 }
317 if (descriptor.id === id) {
318 let error = input.error;
319 let rpcError = null;
320 if (typeof error === 'string') {
321 try {
322 error = JSON.parse(error);
323 } catch (e) {
324 error = {};
325 }
326 }
327 rpcError = error['rcp-error']
328 || (error['rcp-reply'] && error['rcp-reply']['rcp-error'])
329 || (error.body && error.body['rpc-reply'] && error.body['rpc-reply']['rpc-error']);
330 if (rpcError) {
331 const errorTag = rpcError['error-tag'];
332 const message = errorMessage[errorTag] || errorTag;
333 let errPath = rpcError['error-path'].trim();
334 errPath = errPath.replace(/[-\w]*:/g, '');
335 errPath = errPath.slice(errPath.indexOf('/' + type + '[') + 1);
336 const path = [];
337 let splitIndex = errPath.indexOf('/');
338 function ripOutNodeExpression(str, complexNode) {
339 const expressionEnd = str.indexOf(']');
340 complexNode.push(str.slice(1, expressionEnd));
341 if (str.charAt(expressionEnd + 1) === '[') {
342 str = str.slice(expressionEnd + 1);
343 return ripOutNodeExpression(str, complexNode)
344 }
345 return str.slice(expressionEnd + 2);
346 }
347 while (splitIndex > -1) {
348 const expressionStart = errPath.indexOf('[');
349 if (expressionStart > 0 && expressionStart < splitIndex) {
350 const complexNode = [];
351 complexNode.push(errPath.slice(0, expressionStart));
352 errPath = errPath.slice(expressionStart);
353 errPath = ripOutNodeExpression(errPath, complexNode);
354 path.push(complexNode);
355 } else {
356 path.push(errPath.slice(0, splitIndex))
357 errPath = errPath.slice(splitIndex + 1);
358 }
359 splitIndex = errPath.indexOf('/');
360 }
361 const expressionStart = errPath.indexOf('[');
362 if (expressionStart > 0) {
363 const complexNode = [];
364 complexNode.push(errPath.slice(0, expressionStart));
365 errPath = errPath.slice(expressionStart);
366 errPath = ripOutNodeExpression(errPath, complexNode);
367 } else {
368 path.push(errPath.slice(0))
369 }
370 let model = descriptor.model;
371 path.shift();
372 let fullPath = path.reduce((a, p, i) => {
373 let element = p;
374 let subPath = [];
375 if (Array.isArray(p)) {
376 element = p.shift();
377 subPath = p;
378 }
379 a.push(element);
380 model = model[element];
381 const match = subPath.reduce((m, e) => {
382 const id = e.split('=');
383 const key = id[0];
384 let value = id[1];
385 value = value.charAt(0) === "'" ? value.split("'")[1] : value;
386 m.push({ key, value });
387 return m;
388 }, []);
389 if (match.length) {
390 const index = model.findIndex(obj => match.every(e => obj[e.key] == e.value));
391 a.push(index);
392 model = model[index];
393 }
394 return a;
395 }, []);
396 this.setItemError(descriptor, fullPath, message);
397 this.updateItem(descriptor.getRoot().model);
398 }
399 }
400 }
401
402 modelError(input) {
403 const { descriptor, path, message } = input;
404 this.setItemError(descriptor, path, message);
405 this.updateItem(descriptor.getRoot().model)
406 }
407
408 modelSet(input) {
409 const { descriptor, path, value } = input;
410 _set(descriptor.model, path, value);
411 this.setItemError(descriptor, path, null);
412 this.updateItem(descriptor.getRoot().model)
413 CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
414 }
415
416 modelSetOpenState(input) {
417 const { descriptor, path, isOpen } = input;
418 descriptor.setUiState('opened', path, isOpen);
419 this.updateItem(descriptor.getRoot().model)
420 }
421
422 modelForceOpenState(input) {
423 const { descriptor, path } = input;
424 let openPath = [];
425 const targetPath = _isArray(path) ? path : [path];
426 targetPath.forEach(p => {
427 openPath.push(p);
428 descriptor.setUiState('opened', openPath, true);
429 });
430 this.updateItem(descriptor.getRoot().model)
431 }
432
433 modelAssign(input) {
434 const { descriptor, path, source } = input;
435 let obj = _get(descriptor.model, path) || {};
436 Object.assign(obj, source);
437 this.updateItem(descriptor.getRoot().model)
438 CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
439 }
440
441 modelListNew(input) {
442 const { descriptor, path, property } = input;
443 const create = Property.getContainerCreateMethod(property, descriptor);
444 if (create) {
445 const model = null;
446 create(model, path, property);
447 } else {
448 // get a unique name for the new list item based on the current list content
449 // some lists, based on the key, may not get a uniqueName generated here
450 const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(descriptor.model[property.name], property);
451 const value = Property.createModelInstance(property, uniqueName);
452 let list = _get(descriptor.model, path) || [];
453 list.push(value);
454 _set(descriptor.model, path, list);
455 }
456 const list = _get(descriptor.model, path);
457 let openPath = path.slice();
458 descriptor.setUiState('opened', openPath, true);
459 openPath.push((list.length - 1).toString());
460 descriptor.setUiState('opened', openPath, true);
461 function traverseProps(properties) {
462 properties.forEach((p) => {
463 if (p.type === 'list' || p.type === 'container') {
464 openPath.push(p.name);
465 descriptor.setUiState('opened', openPath, true);
466 p.type === 'container' && traverseProps(p.properties);
467 openPath.pop();
468 }
469 });
470 }
471 traverseProps(property.properties);
472 this.updateItem(descriptor.getRoot().model);
473 CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
474 }
475
476 modelListRemove(input) {
477 const { descriptor, path, property } = input;
478 const removeMethod = Property.getContainerMethod(property, descriptor, 'remove');
479 if (removeMethod) {
480 removeMethod(_get(descriptor.model, path));
481 } else {
482 const index = path.pop();
483 let list = _get(descriptor.model, path);
484 list = list.splice(index, 1);
485 }
486 this.updateItem(descriptor.getRoot().model);
487 CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
488 }
489
490 showHelpForNothing() {
491 this.setState({ showHelp: { onFocus: false, forAll: false, forNothing: true } })
492 }
493
494 showHelpForAll() {
495 this.setState({ showHelp: { onFocus: false, forAll: true, forNothing: false } })
496 }
497
498 showHelpOnFocus() {
499 this.setState({ showHelp: { onFocus: true, forAll: false, forNothing: false } })
500 }
501
502 clearOpenPanelState(descriptor) {
503 if (descriptor) {
504 descriptor.setUiState('opened', [], {});
505 this.updateItem(descriptor.getRoot().model);
506 }
507 }
508 collapseAllPanels(input) {
509 this.setState({ openPanelsWithData: false, collapsePanelsByDefault: true });
510 this.clearOpenPanelState(input.descriptor);
511 }
512
513 expandAllPanels(input) {
514 this.setState({ openPanelsWithData: false, collapsePanelsByDefault: false });
515 this.clearOpenPanelState(input.descriptor);
516 }
517
518 showPanelsWithData(input) {
519 this.setState({ openPanelsWithData: true, collapsePanelsByDefault: true });
520 this.clearOpenPanelState(input.descriptor);
521 }
522
523 showMoreInfo() {
524 this.setState({
525 showMore: true
526 });
527 }
528
529 showLessInfo() {
530 this.setState({
531 showMore: false
532 });
533 }
534
535 showError(data) {
536 NotificationError.defer({
537 msg: data.errorMessage,
538 type: 'error',
539 rpcError: data.rpcError
540 })
541 // this.setState({message: data.errorMessage, messageType: 'error'});
542 }
543
544 clearError() {
545 this.setState({
546 message: '',
547 messageType: ''
548 });
549 }
550
551 toggleShowMoreInfo() {
552 this.setState({
553 showMore: !this.showMore
554 });
555 }
556
557 applyDefaultLayout() {
558 if (this.item && this.item.uiState && this.item.uiState.containerPositionMap) {
559 if (!_isEmpty(this.item.uiState.containerPositionMap)) {
560 this.item.uiState.containerPositionMap = {};
561 this.updateItem(this.item);
562 CatalogItemsActions.catalogItemMetaDataChanged.defer(this.item);
563 }
564 }
565 }
566
567 updateItemNotifyLetSettleCheckMetaData(descriptor) {
568 this.updateItem(descriptor.model);
569 CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor);
570 setTimeout(() => CatalogItemsActions.catalogItemMetaDataChanged(this.item), 100);
571 }
572
573 addVirtualLinkDescriptor(dropCoordinates = null) {
574 let vld;
575 if (this.item) {
576 if (this.item.uiState.type === 'nsd') {
577 const nsdc = DescriptorModelFactory.newNetworkService(this.item);
578 vld = nsdc.createVld();
579 } else if (this.item.uiState.type === 'vnfd') {
580 const vnfd = DescriptorModelFactory.newVirtualNetworkFunction(this.item);
581 vld = vnfd.createVld();
582 }
583 if (vld) {
584 vld.uiState.dropCoordinates = dropCoordinates;
585 SelectionManager.clearSelectionAndRemoveOutline();
586 SelectionManager.addSelection(vld);
587 this.updateItemNotifyLetSettleCheckMetaData(vld.getRoot());
588 }
589 }
590 }
591
592 addForwardingGraphDescriptor(dropCoordinates = null) {
593 if (this.item && this.item.uiState.type === 'nsd') {
594 const nsdc = DescriptorModelFactory.newNetworkService(this.item);
595 const fg = nsdc.createVnffgd();
596 fg.uiState.dropCoordinates = dropCoordinates;
597 SelectionManager.clearSelectionAndRemoveOutline();
598 SelectionManager.addSelection(fg);
599 this.updateItemNotifyLetSettleCheckMetaData(nsdc);
600 }
601 }
602
603 addVirtualDeploymentDescriptor(dropCoordinates = null) {
604 if (this.item.uiState.type === 'vnfd') {
605 const vnfd = DescriptorModelFactory.newVirtualNetworkFunction(this.item);
606 const vdu = vnfd.createVdu();
607 vdu.uiState.dropCoordinates = dropCoordinates;
608 SelectionManager.clearSelectionAndRemoveOutline();
609 SelectionManager.addSelection(vdu);
610 this.updateItemNotifyLetSettleCheckMetaData(vdu.getRoot());
611 }
612 }
613
614 selectModel(container) {
615 if (SelectionManager.select(container)) {
616 const model = DescriptorModelFactory.isContainer(container) ? container.getRoot().model : container;
617 this.updateItem(model);
618 }
619 }
620
621 outlineModel(obj) {
622 const uid = UID.from(obj);
623 requestAnimationFrame(() => {
624 SelectionManager.outline(Array.from(document.querySelectorAll(`[data-uid="${uid}"]`)));
625 });
626 }
627
628 clearSelection() {
629 SelectionManager.clearSelectionAndRemoveOutline();
630 }
631
632 setDragState(dragState) {
633 this.setState({
634 drag: dragState
635 });
636 }
637
638 filterCatalogByType(typeValue) {
639 this.setState({
640 filterCatalogByTypeValue: typeValue
641 })
642 }
643
644 setCanvasZoom(zoom) {
645 this.setState({
646 zoom: zoom
647 });
648 }
649
650 showJsonViewer() {
651 this.setState({
652 showJSONViewer: true
653 });
654 }
655
656 closeJsonViewer() {
657 this.setState({
658 showJSONViewer: false
659 });
660 }
661
662 toggleCanvasPanelTray(event) {
663 const layout = this.layout;
664 const attrMap = event.target.attributes;
665 let panelEvent = null;
666 for (let k in attrMap) {
667 if (attrMap[k].name == 'data-event') {
668 panelEvent = attrMap[k].nodeValue;
669 }
670 }
671 if ((layout.bottom > 25) && ((panelEvent == this.displayedPanel) || panelEvent == 'arrow')) {
672 this.closeCanvasPanelTray();
673 } else {
674 this.openCanvasPanelTray();
675 }
676 if (panelEvent != 'arrow') {
677 this.setState({
678 displayedPanel: panelEvent
679 })
680 }
681 }
682
683 openCanvasPanelTray() {
684 const layout = {
685 left: this.layout.left,
686 right: this.layout.right,
687 bottom: 300
688 };
689 const zoom = defaults.defaultPanelTrayOpenZoom;
690 if (this.zoom !== zoom) {
691 this.setState({
692 layout: layout,
693 zoom: zoom,
694 restoreZoom: this.zoom
695 });
696 } else {
697 this.setState({
698 layout: layout
699 });
700 }
701 }
702
703 closeCanvasPanelTray() {
704 const layout = {
705 left: this.layout.left,
706 right: this.layout.right,
707 bottom: 25
708 };
709 const zoom = this.restoreZoom || autoZoomCanvasScale(layout.bottom);
710 if (this.zoom !== zoom) {
711 this.setState({
712 layout: layout,
713 zoom: zoom,
714 restoreZoom: null
715 });
716 } else {
717 this.setState({
718 layout: layout,
719 restoreZoom: null
720 });
721 }
722 }
723
724 enterFullScreenMode() {
725
726 /**
727 * https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
728 * This is an experimental api but works our target browsers and ignored by others
729 */
730 const eventNames = ['fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
731
732 const appRoot = document.body; //.getElementById('RIFT_wareLaunchpadComposerAppRoot');
733
734 const comp = this;
735
736 function onFullScreenChange() {
737
738 if (isFullScreen()) {
739 const layout = comp.layout;
740 const restoreLayout = _cloneDeep(layout);
741 uiTransientState.restoreLayout = restoreLayout;
742 layout.left = 0;
743 layout.right = 0;
744 comp.setState({
745 fullScreenMode: true,
746 layout: layout,
747 restoreLayout: restoreLayout
748 });
749 } else {
750 comp.setState({
751 fullScreenMode: false,
752 layout: uiTransientState.restoreLayout
753 });
754 }
755
756 }
757
758 if (this.fullScreenMode === false) {
759
760 if (appRoot.requestFullscreen) {
761 appRoot.requestFullscreen();
762 } else if (appRoot.msRequestFullscreen) {
763 appRoot.msRequestFullscreen();
764 } else if (appRoot.mozRequestFullScreen) {
765 appRoot.mozRequestFullScreen();
766 } else if (appRoot.webkitRequestFullscreen) {
767 appRoot.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
768 }
769
770 eventNames.map(name => {
771 document.removeEventListener(name, onFullScreenChange);
772 document.addEventListener(name, onFullScreenChange);
773 });
774
775 }
776
777 }
778
779 exitFullScreenMode() {
780
781 if (document.exitFullscreen) {
782 document.exitFullscreen();
783 } else if (document.msExitFullscreen) {
784 document.msExitFullscreen();
785 } else if (document.mozCancelFullScreen) {
786 document.mozCancelFullScreen();
787 } else if (document.webkitExitFullscreen) {
788 document.webkitExitFullscreen();
789 }
790
791 this.setState({
792 fullScreenMode: false
793 });
794
795 }
796 showAssets() {
797 this.setState({
798 panelTabShown: 'assets'
799 });
800 }
801 showDescriptor() {
802 this.setState({
803 panelTabShown: 'descriptor'
804 });
805 }
806
807 //File Manager methods
808 getFilelistSuccess(data) {
809 let self = this;
810 let filesState = null;
811 if (self.fileMonitoringSocketID) {
812 let newState = {};
813 if (data.hasOwnProperty('contents')) {
814 filesState = updateFileState(_cloneDeep(this.filesState), data);
815 let normalizedData = normalizeTree(data);
816 newState = {
817 files: {
818 data: _mergeWith(normalizedData.data, self.files.data, function (obj, src) {
819 return _uniqBy(obj ? obj.concat(src) : src, 'name');
820 }),
821 id: normalizedData.id
822 },
823 filesState: filesState
824 }
825 } else {
826 newState = {
827 files: false
828 }
829 }
830 if (!_isEqual(newState.files, this.files) || !_isEqual(newState.fileState, this.fileState)) {
831 this.setState(newState);
832 }
833
834 }
835
836 function normalizeTree(data) {
837 let f = {
838 id: [],
839 data: {}
840 };
841
842 function getContents(d) {
843 if (d.hasOwnProperty('contents')) {
844 let contents = [];
845 d.contents.map(function (c, i) {
846 if (!c.hasOwnProperty('contents')) {
847 contents.push(c);
848 } else {
849 getContents(c);
850 }
851 })
852 f.id.push(d.name);
853 f.data[d.name] = contents;
854 }
855 }
856 getContents(data);
857 return f;
858 }
859
860 function updateFileState(obj, d) {
861 d.newFile = '';
862 if (d.hasOwnProperty('contents')) {
863 d.contents.map(updateFileState.bind(null, obj))
864 }
865 // override any "pending" state we may have initialized
866 obj[d.name] = '';
867 return obj;
868 }
869 }
870 sendDownloadFileRequest(data) {
871 let id = data.id || this.item.id;
872 let type = data.type || this.item.uiState.type;
873 let assetType = data.assetType;
874 let path = data.path;
875 let url = data.url;
876 this.getInstance().addFile(id, type, assetType, path, url, data.refresh);
877 }
878 updateFileLocationInput = (data) => {
879 let name = data.name;
880 let value = data.value;
881 var filesState = _cloneDeep(this.filesState);
882 filesState[name] = value;
883 this.setState({
884 filesState: filesState
885 });
886 }
887 addFileSuccess = (data) => {
888 if (!data.refresh) {
889 let path = data.path;
890 if (path.startsWith('readme')) {
891 // this asset type stuff should be in a more common location
892 // this is a wee bit of a hack till it is
893 path = '.' + path.slice(6);
894 }
895 let fileName = data.fileName;
896 let files = _cloneDeep(this.files);
897 let assetGroup = files.data[path] || [];
898 if (fileName) {
899 let name = path + '/' + fileName;
900 if (assetGroup.findIndex(f => f.name === name) == -1) {
901 assetGroup.push({
902 name
903 });
904 }
905 }
906 files.data[path] = assetGroup;
907 if (files.id.indexOf(path) == -1) {
908 files.id.push(path);
909 }
910 let filesState = _cloneDeep(this.filesState);
911 filesState[name] = "DOWNLOADING";
912 this.setState({
913 files,
914 filesState
915 });
916 }
917
918 }
919 startWatchingJob = () => {
920 let ws = window.multiplexer.channel(this.jobSocketId);
921 this.setState({
922 jobSocket: null
923 })
924 }
925 openDownloadMonitoringSocketSuccess = (id) => {
926 let self = this;
927 let ws = window.multiplexer.channel(id);
928 let downloadJobs = _cloneDeep(self.downloadJobs);
929 let newFiles = false;
930 ws.onmessage = (socket) => {
931 if (self.files && self.files.length > 0) {
932 let jobs = [];
933 try {
934 jobs = JSON.parse(socket.data);
935 } catch (e) { }
936 newFiles = _cloneDeep(self.files);
937 jobs.map(function (j) {
938 //check if not in completed state
939 let fullPath = j['package-path'];
940 let path = fullPath.split('/');
941 let fileName = path.pop();
942 path = path.join('/');
943 let index = _findIndex(self.files.data[path], function (o) {
944 return fullPath == o.name
945 });
946 if ((index > -1) && newFiles.data[path][index]) {
947 newFiles.data[path][index].status = j.status
948 } else {
949 if (j.status.toUpperCase() == 'LOADING...' || j.status.toUpperCase() == 'IN_PROGRESS') {
950 newFiles.data[path].push({
951 status: j.status,
952 name: fullPath
953 })
954 } else {
955 // if ()
956 }
957 }
958 })
959 self.setState({
960 files: newFiles
961 })
962 // console.log(JSON.parse(socket.data));
963 }
964 }
965 this.setState({
966 jobSocketId: id,
967 jobSocket: ws
968 })
969
970 }
971 getFilelistSocketSuccess = (id) => {
972 let self = this;
973 let ws = window.multiplexer.channel(id);
974 ws.onmessage = (socket) => {
975 if (self.fileMonitoringSocketID) {
976 let data = [];
977 try {
978 data = JSON.parse(socket.data);
979 } catch (e) { }
980 self.getFilelistSuccess(data)
981 }
982 }
983
984 this.setState({
985 filesState: [],
986 files: {
987 id: [],
988 data: {}
989 },
990 fileMonitoringSocketID: id,
991 fileMonitoringSocket: ws
992 })
993
994 }
995 closeFileManagerSockets() {
996 this.fileMonitoringSocketID = null;
997 this.setState({
998 jobSocketId: null,
999 fileMonitoringSocketID: null
1000 // jobSocket : null,
1001 // fileMonitoringSocket : null,
1002 });
1003 this.jobSocket && this.jobSocket.close();
1004 this.fileMonitoringSocket && this.fileMonitoringSocket.close();
1005 console.log('closing');
1006 }
1007 openFileManagerSockets(i) {
1008 let self = this;
1009 let item = i || self.item;
1010 // this.closeFileManagerSockets();
1011 this.getInstance().openFileMonitoringSocket(item.id, item.uiState.type).then(function () {
1012 // // self.getInstance().openDownloadMonitoringSocket(item.id);
1013 });
1014 this.getInstance().openDownloadMonitoringSocket(item.id);
1015 }
1016 endWatchingJob(id) {
1017
1018 }
1019 deletePackageFile(asset) {
1020 let {
1021 assetType,
1022 path
1023 } = asset;
1024 let id = this.item.id;
1025 let type = this.item.uiState.type;
1026 this.getInstance().deleteFile(id, type, assetType, path);
1027 }
1028 deleteFileSuccess = (data) => {
1029 let name = null;
1030 let path = null;
1031 if (data.assetFolder === 'readme') {
1032 // freak'n root folder is special
1033 name = data.path;
1034 path = ['.'];
1035 } else {
1036 name = data.assetFolder + '/' + data.path;
1037 path = name.split('/');
1038 path.pop();
1039 }
1040 let files = _cloneDeep(this.files);
1041 let filesForPath = files.data[path.join('/')]
1042 _remove(filesForPath, function (c) {
1043 return c.name == name;
1044 });
1045
1046 this.setState({
1047 files: files
1048 })
1049 }
1050 deleteFileError = (error) => {
1051 const filepath = error.path;
1052 const message = error.data && error.data.output ? ' (' + error.data.output['error-trace'] + ')' : ' (server error)';
1053 console.log('Unable to delete', filepath, 'Error:', message);
1054 ComposerAppActions.showError.defer({
1055 errorMessage: 'Unable to delete ' + filepath + message + '. '
1056 });
1057 }
1058
1059 newPathNameUpdated = (event) => {
1060 const value = event.target.value;
1061 this.setState({
1062 newPathName: value
1063 })
1064 }
1065 createDirectory = (assetType) => {
1066 console.log(this.newPathName);
1067 this.sendDownloadFileRequest({
1068 id: this.item.id,
1069 type: this.item.uiState.type,
1070 assetType: assetType,
1071 path: this.newPathName,
1072 url: utils.getSearchParams(window.location).dev_download_server || window.location.protocol + '//' + window.location.host,
1073 refresh: true
1074 });
1075 this.setState({
1076 newPathName: ''
1077 })
1078 }
1079 }
1080
1081 export default alt.createStore(ComposerAppStore, 'ComposerAppStore');