update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try
[osm/UI.git] / skyquake / plugins / composer / src / src / stores / ComposerAppStore.js
index 854864b..27220d4 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  *
  *   Copyright 2016 RIFT.IO Inc
  */
 'use strict';
 
-import _ from 'lodash'
+import _isNumber from 'lodash/isNumber'
+import _cloneDeep from 'lodash/cloneDeep'
+import _isEmpty from 'lodash/isEmpty'
+import _isArray from 'lodash/isArray'
+import _mergeWith from 'lodash/mergeWith'
+import _uniqBy from 'lodash/uniqBy'
+import _isEqual from 'lodash/isEqual'
+import _findIndex from 'lodash/findIndex'
+import _remove from 'lodash/remove'
+import _get from 'lodash/get';
+import _set from 'lodash/set';
 import d3 from 'd3'
 import alt from '../alt'
 import UID from '../libraries/UniqueId'
+import DescriptorModel from '../libraries/model/DescriptorModel'
 import DescriptorModelFactory from '../libraries/model/DescriptorModelFactory'
 import PanelResizeAction from '../actions/PanelResizeAction'
 import CatalogItemsActions from '../actions/CatalogItemsActions'
 import CanvasEditorActions from '../actions/CanvasEditorActions'
+import DescriptorEditorActions from '../actions/DescriptorEditorActions'
 import ComposerAppActions from '../actions/ComposerAppActions'
 import CatalogFilterActions from '../actions/CatalogFilterActions'
 import CanvasPanelTrayActions from '../actions/CanvasPanelTrayActions'
@@ -36,24 +47,30 @@ import isFullScreen from '../libraries/isFullScreen'
 import FileManagerSource from '../components/filemanager/FileManagerSource';
 import FileManagerActions from '../components/filemanager/FileManagerActions';
 
+import Property from '../libraries/model/DescriptorModelMetaProperty'
+import DescriptorModelMetaFactory from '../libraries/model/DescriptorModelMetaFactory'
+
 import React from 'react';
 
 //Hack for crouton fix. Should eventually put composer in skyquake alt context
 import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
 let NotificationError = null;
+
+import utils from '../libraries/utils';
+
 class ComponentBridge extends React.Component {
-    constructor(props) {
-        super(props);
-        NotificationError = this.props.flux.actions.global.showNotification;
-    }
-    render(){
-        return <i></i>
-    }
+       constructor(props) {
+               super(props);
+               NotificationError = this.props.flux.actions.global.showNotification;
+       }
+       render() {
+               return <i > </i>
+       }
 }
 const getDefault = (name, defaultValue) => {
        const val = window.localStorage.getItem('defaults-' + name);
        if (val) {
-               if (_.isNumber(val)) {
+               if (_isNumber(val)) {
                        if (val < 0) {
                                return setDefault(name, 0);
                        }
@@ -96,8 +113,8 @@ const uiTransientState = {};
 class ComposerAppStore {
 
        constructor() {
-        //Bridge for crouton fix
-        this.ComponentBridgeElement = SkyquakeComponent(ComponentBridge);
+               //Bridge for crouton fix
+               this.ComponentBridgeElement = SkyquakeComponent(ComponentBridge);
 
                this.exportAsync(FileManagerSource)
                // the catalog item currently being edited in the composer
@@ -116,20 +133,24 @@ class ComposerAppStore {
                this.drag = null;
                this.message = '';
                this.messageType = '';
+               this.showHelp = { onFocus: false, forAll: true, forNothing: false },
+                       this.detailPanel = { collapsed: true, hidden: false }
                this.showJSONViewer = false;
                this.showClassifiers = {};
                this.editPathsMode = false;
                this.fullScreenMode = false;
                this.panelTabShown = 'descriptor';
                //File manager values
-               this.files = [];
+               this.files = false;
                this.filesState = {};
                this.downloadJobs = {};
+               this.containers = [];
+               this.newPathName = '';
+               this.displayedPanel = 'forwarding'; //or parameter
                //End File  manager values
                this.bindListeners({
                        onResize: PanelResizeAction.RESIZE,
                        editCatalogItem: CatalogItemsActions.EDIT_CATALOG_ITEM,
-                       catalogItemMetaDataChanged: CatalogItemsActions.CATALOG_ITEM_META_DATA_CHANGED,
                        catalogItemDescriptorChanged: CatalogItemsActions.CATALOG_ITEM_DESCRIPTOR_CHANGED,
                        toggleShowMoreInfo: CanvasEditorActions.TOGGLE_SHOW_MORE_INFO,
                        showMoreInfo: CanvasEditorActions.SHOW_MORE_INFO,
@@ -140,6 +161,7 @@ class ComposerAppStore {
                        addVirtualDeploymentDescriptor: CanvasEditorActions.ADD_VIRTUAL_DEPLOYMENT_DESCRIPTOR,
                        selectModel: ComposerAppActions.SELECT_MODEL,
                        outlineModel: ComposerAppActions.OUTLINE_MODEL,
+                       modelSaveError: ComposerAppActions.RECORD_DESCRIPTOR_ERROR,
                        showError: ComposerAppActions.SHOW_ERROR,
                        clearError: ComposerAppActions.CLEAR_ERROR,
                        setDragState: ComposerAppActions.SET_DRAG_STATE,
@@ -156,53 +178,94 @@ class ComposerAppStore {
                        showDescriptor: ComposerAppActions.showDescriptor,
                        getFilelistSuccess: FileManagerActions.getFilelistSuccess,
                        updateFileLocationInput: FileManagerActions.updateFileLocationInput,
-                       sendDownloadFileRequst: FileManagerActions.sendDownloadFileRequst,
+                       sendDownloadFileRequest: FileManagerActions.sendDownloadFileRequest,
                        addFileSuccess: FileManagerActions.addFileSuccess,
                        deletePackageFile: FileManagerActions.deletePackageFile,
                        deleteFileSuccess: FileManagerActions.deleteFileSuccess,
+                       deleteFileError: FileManagerActions.deleteFileError,
                        closeFileManagerSockets: FileManagerActions.closeFileManagerSockets,
                        openFileManagerSockets: FileManagerActions.openFileManagerSockets,
                        openDownloadMonitoringSocketSuccess: FileManagerActions.openDownloadMonitoringSocketSuccess,
-                       getFilelistSocketSuccess: FileManagerActions.getFilelistSocketSuccess
+                       getFilelistSocketSuccess: FileManagerActions.getFilelistSocketSuccess,
+                       newPathNameUpdated: FileManagerActions.newPathNameUpdated,
+                       createDirectory: FileManagerActions.createDirectory,
+                       modelSetOpenState: DescriptorEditorActions.setOpenState,
+                       modelError: DescriptorEditorActions.setError,
+                       modelSet: DescriptorEditorActions.setValue,
+                       modelAssign: DescriptorEditorActions.assignValue,
+                       modelListNew: DescriptorEditorActions.addListItem,
+                       modelListRemove: DescriptorEditorActions.removeListItem,
+                       showHelpForNothing: DescriptorEditorActions.showHelpForNothing,
+                       showHelpForAll: DescriptorEditorActions.showHelpForAll,
+                       showHelpOnFocus: DescriptorEditorActions.showHelpOnFocus,
+                       collapseAllPanels: DescriptorEditorActions.collapseAllPanels,
+                       expandAllPanels: DescriptorEditorActions.expandAllPanels,
+                       modelForceOpenState: DescriptorEditorActions.expandPanel,
+                       showPanelsWithData: DescriptorEditorActions.showPanelsWithData
+
                });
-        this.exportPublicMethods({
-            closeFileManagerSockets: this.closeFileManagerSockets.bind(this)
-        })
+               this.exportPublicMethods({
+                       closeFileManagerSockets: this.closeFileManagerSockets.bind(this)
+               })
        }
 
        onResize(e) {
+               const layout = Object.assign({}, this.layout);
                if (e.type === 'resize-manager.resize.catalog-panel') {
-                       const layout = Object.assign({}, this.layout);
                        layout.left = Math.max(0, layout.left - e.moved.x);
                        if (layout.left !== this.layout.left) {
-                               this.setState({layout: layout});
+                               this.setState({
+                                       layout: layout
+                               });
                        }
                } else if (e.type === 'resize-manager.resize.details-panel') {
-                       const layout = Object.assign({}, this.layout);
                        layout.right = Math.max(0, layout.right + e.moved.x);
                        if (layout.right !== this.layout.right) {
-                               this.setState({layout: layout});
+                               this.setState({
+                                       layout: layout
+                               });
                        }
                } else if (/^resize-manager\.resize\.canvas-panel-tray/.test(e.type)) {
-                       const layout = Object.assign({}, this.layout);
                        layout.bottom = Math.max(25, layout.bottom + e.moved.y);
                        if (layout.bottom !== this.layout.bottom) {
-                               const zoom = autoZoomCanvasScale(layout.bottom) ;
+                               const zoom = autoZoomCanvasScale(layout.bottom);
                                if (this.zoom !== zoom) {
-                                       this.setState({layout: layout, zoom: zoom});
+                                       this.setState({
+                                               layout: layout,
+                                               zoom: zoom
+                                       });
                                } else {
-                                       this.setState({layout: layout});
+                                       this.setState({
+                                               layout: layout
+                                       });
                                }
                        }
-               } else if (e.type !== 'resize') {
+               } else if (e.type == 'resize') {
+                       layout.height = e.target.innerHeight;
+                       this.setState({
+                               layout: layout
+                       });
+               } else {
                        console.log('no resize handler for ', e.type, '. Do you need to add a handler in ComposerAppStore::onResize()?')
                }
                SelectionManager.refreshOutline();
        }
 
        updateItem(item) {
-               if(!document.body.classList.contains('resizing')) {
-                       this.setState({item: _.cloneDeep(item)});
+               const self = this;
+               let containers = [];
+               let cpNumber = 0;
+               if (!document.body.classList.contains('resizing')) {
+                       containers = [item].reduce(DescriptorModelFactory.buildCatalogItemFactory(CatalogDataStore.getState().catalogs), []);
+
+                       containers.filter(d => DescriptorModelFactory.isConnectionPoint(d)).forEach(d => {
+                               d.cpNumber = ++cpNumber;
+                               containers.filter(d => DescriptorModelFactory.isVnfdConnectionPointRef(d)).filter(ref => ref.key === d.key).forEach(ref => ref.cpNumber = d.cpNumber);
+                       });
+                       this.setState({
+                               containers: containers,
+                               item: _cloneDeep(item)
+                       });
                }
                SelectionManager.refreshOutline();
        }
@@ -218,46 +281,295 @@ class ComposerAppStore {
                }
                SelectionManager.select(item);
                this.updateItem(item);
-               this.openFileManagerSockets(item)
-       }
-       catalogItemMetaDataChanged(item) {
-               this.updateItem(item);
+               if (item) {
+                       this.openFileManagerSockets(item);
+               }
        }
 
        catalogItemDescriptorChanged(itemDescriptor) {
-               this.catalogItemMetaDataChanged(itemDescriptor.model);
+               this.updateItem(itemDescriptor.model);
+       }
+
+       setItemError(descriptor, path, message) {
+               // save locally and globally
+               descriptor.setUiState('error', path, message);
+               if (descriptor.parent) {
+                       let parentPath = [];
+                       while (descriptor.parent) {
+                               parentPath.push(descriptor.type);
+                               const index = descriptor.parent.model[descriptor.type].findIndex(item => item.id === descriptor.id);
+                               parentPath.push(index);
+                               descriptor = descriptor.parent;
+                       }
+                       parentPath.length && descriptor.setUiState('error', parentPath.concat(path), message);
+               } else if (path.length > 1 && descriptor[path[0]]) {
+                       // if we are indirectly editing a sub model set the error state in the sub model
+                       descriptor[path[0]][path[1]].setUiState('error', path.slice(2), message);
+               }
+       }
+
+       modelSaveError(input) {
+               const { descriptor, type, id } = input;
+               const errorMessage = {
+                       'data-missing': "Required value.",
+                       'missing-element': "Incomplete configuration."
+               }
+               if (descriptor.id === id) {
+                       let error = input.error;
+                       let rpcError = null;
+                       if (typeof error === 'string') {
+                               try {
+                                       error = JSON.parse(error);
+                               } catch (e) {
+                                       error = {};
+                               }
+                       }
+                       rpcError = error['rcp-error']
+                               || (error['rcp-reply'] && error['rcp-reply']['rcp-error'])
+                               || (error.body && error.body['rpc-reply'] && error.body['rpc-reply']['rpc-error']);
+                       if (rpcError) {
+                               const errorTag = rpcError['error-tag'];
+                               const message = errorMessage[errorTag] || errorTag;
+                               let errPath = rpcError['error-path'].trim();
+                               errPath = errPath.replace(/[-\w]*:/g, '');
+                               errPath = errPath.slice(errPath.indexOf('/' + type + '[') + 1);
+                               const path = [];
+                               let splitIndex = errPath.indexOf('/');
+                               function ripOutNodeExpression(str, complexNode) {
+                                       const expressionEnd = str.indexOf(']');
+                                       complexNode.push(str.slice(1, expressionEnd));
+                                       if (str.charAt(expressionEnd + 1) === '[') {
+                                               str = str.slice(expressionEnd + 1);
+                                               return ripOutNodeExpression(str, complexNode)
+                                       }
+                                       return str.slice(expressionEnd + 2);
+                               }
+                               while (splitIndex > -1) {
+                                       const expressionStart = errPath.indexOf('[');
+                                       if (expressionStart > 0 && expressionStart < splitIndex) {
+                                               const complexNode = [];
+                                               complexNode.push(errPath.slice(0, expressionStart));
+                                               errPath = errPath.slice(expressionStart);
+                                               errPath = ripOutNodeExpression(errPath, complexNode);
+                                               path.push(complexNode);
+                                       } else {
+                                               path.push(errPath.slice(0, splitIndex))
+                                               errPath = errPath.slice(splitIndex + 1);
+                                       }
+                                       splitIndex = errPath.indexOf('/');
+                               }
+                               const expressionStart = errPath.indexOf('[');
+                               if (expressionStart > 0) {
+                                       const complexNode = [];
+                                       complexNode.push(errPath.slice(0, expressionStart));
+                                       errPath = errPath.slice(expressionStart);
+                                       errPath = ripOutNodeExpression(errPath, complexNode);
+                               } else {
+                                       path.push(errPath.slice(0))
+                               }
+                               let model = descriptor.model;
+                               path.shift();
+                               let fullPath = path.reduce((a, p, i) => {
+                                       let element = p;
+                                       let subPath = [];
+                                       if (Array.isArray(p)) {
+                                               element = p.shift();
+                                               subPath = p;
+                                       }
+                                       a.push(element);
+                                       model = model[element];
+                                       const match = subPath.reduce((m, e) => {
+                                               const id = e.split('=');
+                                               const key = id[0];
+                                               let value = id[1];
+                                               value = value.charAt(0) === "'" ? value.split("'")[1] : value;
+                                               m.push({ key, value });
+                                               return m;
+                                       }, []);
+                                       if (match.length) {
+                                               const index = model.findIndex(obj => match.every(e => obj[e.key] == e.value));
+                                               a.push(index);
+                                               model = model[index];
+                                       }
+                                       return a;
+                               }, []);
+                               this.setItemError(descriptor, fullPath, message);
+                               this.updateItem(descriptor.getRoot().model);
+                       }
+               }
+       }
+
+       modelError(input) {
+               const { descriptor, path, message } = input;
+               this.setItemError(descriptor, path, message);
+               this.updateItem(descriptor.getRoot().model)
+       }
+
+       modelSet(input) {
+               const { descriptor, path, value } = input;
+               _set(descriptor.model, path, value);
+               this.setItemError(descriptor, path, null);
+               this.updateItem(descriptor.getRoot().model)
+               CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+       }
+
+       modelSetOpenState(input) {
+               const { descriptor, path, isOpen } = input;
+               descriptor.setUiState('opened', path, isOpen);
+               this.updateItem(descriptor.getRoot().model)
+       }
+
+       modelForceOpenState(input) {
+               const { descriptor, path } = input;
+               let openPath = [];
+               const targetPath = _isArray(path) ? path : [path];
+               targetPath.forEach(p => {
+                       openPath.push(p);
+                       descriptor.setUiState('opened', openPath, true);
+               });
+               this.updateItem(descriptor.getRoot().model)
+       }
+
+       modelAssign(input) {
+               const { descriptor, path, source } = input;
+               let obj = _get(descriptor.model, path) || {};
+               Object.assign(obj, source);
+               this.updateItem(descriptor.getRoot().model)
+               CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+       }
+
+       modelListNew(input) {
+               const { descriptor, path, property } = input;
+               const create = Property.getContainerCreateMethod(property, descriptor);
+               if (create) {
+                       const model = null;
+                       create(model, path, property);
+               } else {
+                       // get a unique name for the new list item based on the current list content
+                       // some lists, based on the key, may not get a uniqueName generated here
+                       const uniqueName = DescriptorModelMetaFactory.generateItemUniqueName(descriptor.model[property.name], property);
+                       const value = Property.createModelInstance(property, uniqueName);
+                       let list = _get(descriptor.model, path) || [];
+                       list.push(value);
+                       _set(descriptor.model, path, list);
+               }
+               const list = _get(descriptor.model, path);
+               let openPath = path.slice();
+               descriptor.setUiState('opened', openPath, true);
+               openPath.push((list.length - 1).toString());
+               descriptor.setUiState('opened', openPath, true);
+               function traverseProps(properties) {
+                       properties.forEach((p) => {
+                               if (p.type === 'list' || p.type === 'container') {
+                                       openPath.push(p.name);
+                                       descriptor.setUiState('opened', openPath, true);
+                                       p.type === 'container' && traverseProps(p.properties);
+                                       openPath.pop();
+                               }
+                       });
+               }
+               traverseProps(property.properties);
+               this.updateItem(descriptor.getRoot().model);
+               CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+       }
+
+       modelListRemove(input) {
+               const { descriptor, path, property } = input;
+               const removeMethod = Property.getContainerMethod(property, descriptor, 'remove');
+               if (removeMethod) {
+                       removeMethod(_get(descriptor.model, path));
+               } else {
+                       const index = path.pop();
+                       let list = _get(descriptor.model, path);
+                       list = list.splice(index, 1);
+               }
+               this.updateItem(descriptor.getRoot().model);
+               CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor.getRoot());
+       }
+
+       showHelpForNothing() {
+               this.setState({ showHelp: { onFocus: false, forAll: false, forNothing: true } })
+       }
+
+       showHelpForAll() {
+               this.setState({ showHelp: { onFocus: false, forAll: true, forNothing: false } })
+       }
+
+       showHelpOnFocus() {
+               this.setState({ showHelp: { onFocus: true, forAll: false, forNothing: false } })
+       }
+
+       clearOpenPanelState(descriptor) {
+               if (descriptor) {
+                       descriptor.setUiState('opened', [], {});
+                       this.updateItem(descriptor.getRoot().model);
+               }
+       }
+       collapseAllPanels(input) {
+               this.setState({ openPanelsWithData: false, collapsePanelsByDefault: true });
+               this.clearOpenPanelState(input.descriptor);
+       }
+
+       expandAllPanels(input) {
+               this.setState({ openPanelsWithData: false, collapsePanelsByDefault: false });
+               this.clearOpenPanelState(input.descriptor);
+       }
+
+       showPanelsWithData(input) {
+               this.setState({ openPanelsWithData: true, collapsePanelsByDefault: true });
+               this.clearOpenPanelState(input.descriptor);
        }
 
        showMoreInfo() {
-               this.setState({showMore: true});
+               this.setState({
+                       showMore: true
+               });
        }
 
        showLessInfo() {
-               this.setState({showMore: false});
+               this.setState({
+                       showMore: false
+               });
        }
 
        showError(data) {
-        NotificationError.defer({msg: data.errorMessage, type: 'error'})
-        // this.setState({message: data.errorMessage, messageType: 'error'});
+               NotificationError.defer({
+                       msg: data.errorMessage,
+                       type: 'error',
+                       rpcError: data.rpcError
+               })
+               // this.setState({message: data.errorMessage, messageType: 'error'});
        }
 
        clearError() {
-               this.setState({message: '', messageType: ''});
+               this.setState({
+                       message: '',
+                       messageType: ''
+               });
        }
 
        toggleShowMoreInfo() {
-               this.setState({showMore: !this.showMore});
+               this.setState({
+                       showMore: !this.showMore
+               });
        }
 
        applyDefaultLayout() {
                if (this.item && this.item.uiState && this.item.uiState.containerPositionMap) {
-                       if (!_.isEmpty(this.item.uiState.containerPositionMap)) {
+                       if (!_isEmpty(this.item.uiState.containerPositionMap)) {
                                this.item.uiState.containerPositionMap = {};
+                               this.updateItem(this.item);
                                CatalogItemsActions.catalogItemMetaDataChanged.defer(this.item);
                        }
                }
        }
 
+       updateItemNotifyLetSettleCheckMetaData(descriptor) {
+               this.updateItem(descriptor.model);
+               CatalogItemsActions.catalogItemDescriptorChanged.defer(descriptor);
+               setTimeout(() => CatalogItemsActions.catalogItemMetaDataChanged(this.item), 100);
+       }
+
        addVirtualLinkDescriptor(dropCoordinates = null) {
                let vld;
                if (this.item) {
@@ -272,8 +584,7 @@ class ComposerAppStore {
                                vld.uiState.dropCoordinates = dropCoordinates;
                                SelectionManager.clearSelectionAndRemoveOutline();
                                SelectionManager.addSelection(vld);
-                               this.updateItem(vld.getRoot().model);
-                               CatalogItemsActions.catalogItemDescriptorChanged.defer(vld.getRoot());
+                               this.updateItemNotifyLetSettleCheckMetaData(vld.getRoot());
                        }
                }
        }
@@ -285,8 +596,7 @@ class ComposerAppStore {
                        fg.uiState.dropCoordinates = dropCoordinates;
                        SelectionManager.clearSelectionAndRemoveOutline();
                        SelectionManager.addSelection(fg);
-                       this.updateItem(nsdc.model);
-                       CatalogItemsActions.catalogItemDescriptorChanged.defer(nsdc);
+                       this.updateItemNotifyLetSettleCheckMetaData(nsdc);
                }
        }
 
@@ -297,15 +607,14 @@ class ComposerAppStore {
                        vdu.uiState.dropCoordinates = dropCoordinates;
                        SelectionManager.clearSelectionAndRemoveOutline();
                        SelectionManager.addSelection(vdu);
-                       this.updateItem(vdu.getRoot().model);
-                       CatalogItemsActions.catalogItemDescriptorChanged.defer(vdu.getRoot());
+                       this.updateItemNotifyLetSettleCheckMetaData(vdu.getRoot());
                }
        }
 
        selectModel(container) {
                if (SelectionManager.select(container)) {
                        const model = DescriptorModelFactory.isContainer(container) ? container.getRoot().model : container;
-                       this.catalogItemMetaDataChanged(model);
+                       this.updateItem(model);
                }
        }
 
@@ -318,36 +627,57 @@ class ComposerAppStore {
 
        clearSelection() {
                SelectionManager.clearSelectionAndRemoveOutline();
-               this.catalogItemMetaDataChanged(this.item);
        }
 
        setDragState(dragState) {
-               this.setState({drag: dragState});
+               this.setState({
+                       drag: dragState
+               });
        }
 
        filterCatalogByType(typeValue) {
-               this.setState({filterCatalogByTypeValue: typeValue})
+               this.setState({
+                       filterCatalogByTypeValue: typeValue
+               })
        }
 
        setCanvasZoom(zoom) {
-               this.setState({zoom: zoom});
+               this.setState({
+                       zoom: zoom
+               });
        }
 
        showJsonViewer() {
-               this.setState({showJSONViewer: true});
+               this.setState({
+                       showJSONViewer: true
+               });
        }
 
        closeJsonViewer() {
-               this.setState({showJSONViewer: false});
+               this.setState({
+                       showJSONViewer: false
+               });
        }
 
-       toggleCanvasPanelTray() {
+       toggleCanvasPanelTray(event) {
                const layout = this.layout;
-               if (layout.bottom > 25) {
+               const attrMap = event.target.attributes;
+               let panelEvent = null;
+               for (let k in attrMap) {
+                       if (attrMap[k].name == 'data-event') {
+                               panelEvent = attrMap[k].nodeValue;
+                       }
+               }
+               if ((layout.bottom > 25) && ((panelEvent == this.displayedPanel) || panelEvent == 'arrow')) {
                        this.closeCanvasPanelTray();
                } else {
                        this.openCanvasPanelTray();
                }
+               if (panelEvent != 'arrow') {
+                       this.setState({
+                               displayedPanel: panelEvent
+                       })
+               }
        }
 
        openCanvasPanelTray() {
@@ -358,9 +688,15 @@ class ComposerAppStore {
                };
                const zoom = defaults.defaultPanelTrayOpenZoom;
                if (this.zoom !== zoom) {
-                       this.setState({layout: layout, zoom: zoom, restoreZoom: this.zoom});
+                       this.setState({
+                               layout: layout,
+                               zoom: zoom,
+                               restoreZoom: this.zoom
+                       });
                } else {
-                       this.setState({layout: layout});
+                       this.setState({
+                               layout: layout
+                       });
                }
        }
 
@@ -372,9 +708,16 @@ class ComposerAppStore {
                };
                const zoom = this.restoreZoom || autoZoomCanvasScale(layout.bottom);
                if (this.zoom !== zoom) {
-                       this.setState({layout: layout, zoom: zoom, restoreZoom: null});
+                       this.setState({
+                               layout: layout,
+                               zoom: zoom,
+                               restoreZoom: null
+                       });
                } else {
-                       this.setState({layout: layout, restoreZoom: null});
+                       this.setState({
+                               layout: layout,
+                               restoreZoom: null
+                       });
                }
        }
 
@@ -386,7 +729,7 @@ class ComposerAppStore {
                 */
                const eventNames = ['fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
 
-               const appRoot = document.body;//.getElementById('RIFT_wareLaunchpadComposerAppRoot');
+               const appRoot = document.body; //.getElementById('RIFT_wareLaunchpadComposerAppRoot');
 
                const comp = this;
 
@@ -394,13 +737,20 @@ class ComposerAppStore {
 
                        if (isFullScreen()) {
                                const layout = comp.layout;
-                               const restoreLayout = _.cloneDeep(layout);
+                               const restoreLayout = _cloneDeep(layout);
                                uiTransientState.restoreLayout = restoreLayout;
                                layout.left = 0;
                                layout.right = 0;
-                               comp.setState({fullScreenMode: true, layout: layout, restoreLayout: restoreLayout});
+                               comp.setState({
+                                       fullScreenMode: true,
+                                       layout: layout,
+                                       restoreLayout: restoreLayout
+                               });
                        } else {
-                               comp.setState({fullScreenMode: false, layout: uiTransientState.restoreLayout});
+                               comp.setState({
+                                       fullScreenMode: false,
+                                       layout: uiTransientState.restoreLayout
+                               });
                        }
 
                }
@@ -438,7 +788,9 @@ class ComposerAppStore {
                        document.webkitExitFullscreen();
                }
 
-               this.setState({fullScreenMode: false});
+               this.setState({
+                       fullScreenMode: false
+               });
 
        }
        showAssets() {
@@ -456,30 +808,41 @@ class ComposerAppStore {
        getFilelistSuccess(data) {
                let self = this;
                let filesState = null;
-        if (self.fileMonitoringSocketID) {
-                       filesState = addInputState( _.cloneDeep(this.filesState),data);
-                       // filesState = _.merge(self.filesState, addInputState({},data));
-                       let normalizedData = normalizeTree(data);
-                       this.setState({
-                               files: {
-                                       data: _.mergeWith(normalizedData.data, self.files.data, function(obj, src) {
-                                               return _.uniqBy(obj? obj.concat(src) : src, 'name');
-                                       }),
-                                       id: self.files.id || normalizedData.id
-                               },
-                               filesState: filesState
-                       });
-        }
+               if (self.fileMonitoringSocketID) {
+                       let newState = {};
+                       if (data.hasOwnProperty('contents')) {
+                               filesState = updateFileState(_cloneDeep(this.filesState), data);
+                               let normalizedData = normalizeTree(data);
+                               newState = {
+                                       files: {
+                                               data: _mergeWith(normalizedData.data, self.files.data, function (obj, src) {
+                                                       return _uniqBy(obj ? obj.concat(src) : src, 'name');
+                                               }),
+                                               id: normalizedData.id
+                                       },
+                                       filesState: filesState
+                               }
+                       } else {
+                               newState = {
+                                       files: false
+                               }
+                       }
+                       if (!_isEqual(newState.files, this.files) || !_isEqual(newState.fileState, this.fileState)) {
+                               this.setState(newState);
+                       }
+
+               }
+
                function normalizeTree(data) {
                        let f = {
-                               id:[],
-                               data:{}
+                               id: [],
+                               data: {}
                        };
-                       data.contents.map(getContents);
+
                        function getContents(d) {
-                               if(d.hasOwnProperty('contents')) {
+                               if (d.hasOwnProperty('contents')) {
                                        let contents = [];
-                                       d.contents.map(function(c,i) {
+                                       d.contents.map(function (c, i) {
                                                if (!c.hasOwnProperty('contents')) {
                                                        contents.push(c);
                                                } else {
@@ -490,91 +853,114 @@ class ComposerAppStore {
                                        f.data[d.name] = contents;
                                }
                        }
-                       return f;
+                       getContents(data);
+                       return f;
                }
-               function addInputState(obj, d) {
+
+               function updateFileState(obj, d) {
                        d.newFile = '';
-                       if(d.hasOwnProperty('contents')) {
-                               d.contents.map(addInputState.bind(null, obj))
-                       }
-                       if(!obj[d.name]) {
-                               obj[d.name] = '';
+                       if (d.hasOwnProperty('contents')) {
+                               d.contents.map(updateFileState.bind(null, obj))
                        }
+                       // override any "pending" state we may have initialized
+                       obj[d.name] = '';
                        return obj;
                }
        }
-       sendDownloadFileRequst(data) {
+       sendDownloadFileRequest(data) {
                let id = data.id || this.item.id;
                let type = data.type || this.item.uiState.type;
+               let assetType = data.assetType;
                let path = data.path;
                let url = data.url;
-               this.getInstance().addFile(id, type, path, url);
+               this.getInstance().addFile(id, type, assetType, path, url, data.refresh);
        }
        updateFileLocationInput = (data) => {
                let name = data.name;
                let value = data.value;
-               var filesState = _.cloneDeep(this.filesState);
+               var filesState = _cloneDeep(this.filesState);
                filesState[name] = value;
                this.setState({
                        filesState: filesState
                });
        }
        addFileSuccess = (data) => {
-               let path = data.path;
-               let fileName = data.fileName;
-               let files = _.cloneDeep(this.files);
-               let loadingIndex = files.data[path].push({
-                       status: 'DOWNLOADING',
-                       name: path + '/' + fileName
-               }) - 1;
-               this.setState({files: files});
+               if (!data.refresh) {
+                       let path = data.path;
+                       if (path.startsWith('readme')) {
+                               // this asset type stuff should be in a more common location
+                               // this is a wee bit of a hack till it is
+                               path = '.' + path.slice(6);
+                       }
+                       let fileName = data.fileName;
+                       let files = _cloneDeep(this.files);
+                       let assetGroup = files.data[path] || [];
+                       if (fileName) {
+                               let name = path + '/' + fileName;
+                               if (assetGroup.findIndex(f => f.name === name) == -1) {
+                                       assetGroup.push({
+                                               name
+                                       });
+                               }
+                       }
+                       files.data[path] = assetGroup;
+                       if (files.id.indexOf(path) == -1) {
+                               files.id.push(path);
+                       }
+                       let filesState = _cloneDeep(this.filesState);
+                       filesState[name] = "DOWNLOADING";
+                       this.setState({
+                               files,
+                               filesState
+                       });
+               }
 
        }
        startWatchingJob = () => {
                let ws = window.multiplexer.channel(this.jobSocketId);
                this.setState({
-                       jobSocket:null
+                       jobSocket: null
                })
        }
        openDownloadMonitoringSocketSuccess = (id) => {
                let self = this;
                let ws = window.multiplexer.channel(id);
-               let downloadJobs = _.cloneDeep(self.downloadJobs);
-               let newFiles = {};
+               let downloadJobs = _cloneDeep(self.downloadJobs);
+               let newFiles = false;
                ws.onmessage = (socket) => {
-            if (self.files && self.files.length > 0) {
-                let jobs = [];
-                try {
-                    jobs = JSON.parse(socket.data);
-                } catch(e) {}
-                newFiles = _.cloneDeep(self.files);
-                jobs.map(function(j) {
-                    //check if not in completed state
-                    let fullPath = j['package-path'];
-                    let path = fullPath.split('/');
-                    let fileName = path.pop();
-                    path = path.join('/');
-                    let index = _.findIndex(self.files.data[path], function(o){
-                        return fullPath == o.name
-                    });
-                    if((index > -1) && newFiles.data[path][index]) {
-                        newFiles.data[path][index].status = j.status
+                       if (self.files && self.files.length > 0) {
+                               let jobs = [];
+                               try {
+                                       jobs = JSON.parse(socket.data);
+                               } catch (e) { }
+                               newFiles = _cloneDeep(self.files);
+                               jobs.map(function (j) {
+                                       //check if not in completed state
+                                       let fullPath = j['package-path'];
+                                       let path = fullPath.split('/');
+                                       let fileName = path.pop();
+                                       path = path.join('/');
+                                       let index = _findIndex(self.files.data[path], function (o) {
+                                               return fullPath == o.name
+                                       });
+                                       if ((index > -1) && newFiles.data[path][index]) {
+                                               newFiles.data[path][index].status = j.status
                                        } else {
-                        if(j.status.toUpperCase() == 'LOADING...' || j.status.toUpperCase() == 'IN_PROGRESS') {
-                            newFiles.data[path].push({
-                                status: j.status,
-                                name: fullPath
-                            })
-                        } else {
-                            // if ()
-                        }
+                                               if (j.status.toUpperCase() == 'LOADING...' || j.status.toUpperCase() == 'IN_PROGRESS') {
+                                                       newFiles.data[path].push({
+                                                               status: j.status,
+                                                               name: fullPath
+                                                       })
+                                               } else {
+                                                       // if ()
+                                               }
                                        }
-                })
-                self.setState({
-                    files: newFiles
-                })
-                       // console.log(JSON.parse(socket.data));
-            }
+                               })
+                               self.setState({
+                                       files: newFiles
+                               })
+                               // console.log(JSON.parse(socket.data));
+                       }
                }
                this.setState({
                        jobSocketId: id,
@@ -586,65 +972,110 @@ class ComposerAppStore {
                let self = this;
                let ws = window.multiplexer.channel(id);
                ws.onmessage = (socket) => {
-            if (self.fileMonitoringSocketID) {
-                let data = [];
-                try {
-                    data = JSON.parse(socket.data);
-                } catch(e) {}
-                self.getFilelistSuccess(data)
-            }
+                       if (self.fileMonitoringSocketID) {
+                               let data = [];
+                               try {
+                                       data = JSON.parse(socket.data);
+                               } catch (e) { }
+                               self.getFilelistSuccess(data)
+                       }
                }
 
                this.setState({
+                       filesState: [],
+                       files: {
+                               id: [],
+                               data: {}
+                       },
                        fileMonitoringSocketID: id,
                        fileMonitoringSocket: ws
                })
 
        }
        closeFileManagerSockets() {
-        this.fileMonitoringSocketID = null;
-        this.setState({
-                jobSocketId : null,
-                fileMonitoringSocketID : null
-                // jobSocket : null,
-                // fileMonitoringSocket : null,
-        });
+               this.fileMonitoringSocketID = null;
+               this.setState({
+                       jobSocketId: null,
+                       fileMonitoringSocketID: null
+                       // jobSocket : null,
+                       // fileMonitoringSocket : null,
+               });
                this.jobSocket && this.jobSocket.close();
                this.fileMonitoringSocket && this.fileMonitoringSocket.close();
-        console.log('closing');
+               console.log('closing');
        }
        openFileManagerSockets(i) {
                let self = this;
                let item = i || self.item;
-               this.files = {data:[]};
-        // this.closeFileManagerSockets();
-               this.getInstance().openFileMonitoringSocket(item.id, item.uiState.type).then(function() {
-        //     // self.getInstance().openDownloadMonitoringSocket(item.id);
+               // this.closeFileManagerSockets();
+               this.getInstance().openFileMonitoringSocket(item.id, item.uiState.type).then(function () {
+                       //      // self.getInstance().openDownloadMonitoringSocket(item.id);
                });
-        this.getInstance().openDownloadMonitoringSocket(item.id);
+               this.getInstance().openDownloadMonitoringSocket(item.id);
        }
        endWatchingJob(id) {
 
        }
-       deletePackageFile(name) {
+       deletePackageFile(asset) {
+               let {
+                       assetType,
+                       path
+               } = asset;
                let id = this.item.id;
                let type = this.item.uiState.type;
-               this.getInstance().deleteFile(id, type, name);
+               this.getInstance().deleteFile(id, type, assetType, path);
        }
        deleteFileSuccess = (data) => {
-               let path = data.path.split('/')
-               let files = _.cloneDeep(this.files);
-               path.pop();
-               path = path.join('/');
-               let pathFiles = files.data[path]
-               _.remove(pathFiles, function(c) {
-                       return c.name == data.path;
+               let name = null;
+               let path = null;
+               if (data.assetFolder === 'readme') {
+                       // freak'n root folder is special
+                       name = data.path;
+                       path = ['.'];
+               } else {
+                       name = data.assetFolder + '/' + data.path;
+                       path = name.split('/');
+                       path.pop();
+               }
+               let files = _cloneDeep(this.files);
+               let filesForPath = files.data[path.join('/')]
+               _remove(filesForPath, function (c) {
+                       return c.name == name;
                });
 
                this.setState({
                        files: files
                })
        }
+       deleteFileError = (error) => {
+               const filepath = error.path;
+               const message = error.data && error.data.output ? ' (' + error.data.output['error-trace'] + ')' : ' (server error)';
+               console.log('Unable to delete', filepath, 'Error:', message);
+               ComposerAppActions.showError.defer({
+                       errorMessage: 'Unable to delete ' + filepath + message + '. '
+               });
+       }
+
+       newPathNameUpdated = (event) => {
+               const value = event.target.value;
+               this.setState({
+                       newPathName: value
+               })
+       }
+       createDirectory = (assetType) => {
+               console.log(this.newPathName);
+               this.sendDownloadFileRequest({
+                       id: this.item.id,
+                       type: this.item.uiState.type,
+                       assetType: assetType,
+                       path: this.newPathName,
+                       url: utils.getSearchParams(window.location).dev_download_server || window.location.protocol + '//' + window.location.host,
+                       refresh: true
+               });
+               this.setState({
+                       newPathName: ''
+               })
+       }
 }
 
-export default alt.createStore(ComposerAppStore, 'ComposerAppStore');
+export default alt.createStore(ComposerAppStore, 'ComposerAppStore');
\ No newline at end of file