8de0f00122abcb96dded54ec4c29eadc182f4ebd
[osm/UI.git] / skyquake / plugins / composer / src / src / components / CatalogPanel.js
1
2 /*
3 *
4 * Copyright 2016 RIFT.IO Inc
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19 'use strict';
20
21 import _includes from 'lodash/includes'
22 import React from 'react'
23 import ReactDOM from 'react-dom'
24 import messages from './messages'
25 import ClassNames from 'classnames'
26 import UploadDropZone from '../libraries/CatalogPackageManagerUploadDropZone'
27 import PureRenderMixin from 'react-addons-pure-render-mixin'
28 import DropTarget from './DropTarget'
29 import DropZonePanel from './DropZonePanel'
30 import CatalogItems from './CatalogItems'
31 import CatalogFilter from './CatalogFilter'
32 import CatalogPanelTray from './CatalogPanelTray'
33 import CatalogPanelToolbar from './CatalogPanelToolbar'
34 import CatalogPackageManager from './CatalogPackageManager'
35 import CatalogItemsActions from '../actions/CatalogItemsActions'
36 import CatalogPanelTrayActions from '../actions/CatalogPanelTrayActions'
37 import ComposerAppActions from '../actions/ComposerAppActions'
38 import ComposerAppStore from '../stores/ComposerAppStore'
39 import CatalogPanelStore from '../stores/CatalogPanelStore'
40 import LoadingIndicator from './LoadingIndicator'
41 import SelectionManager from '../libraries/SelectionManager'
42 import { isRBACValid } from 'widgets/skyquake_rbac/skyquakeRBAC';
43 import ROLES from 'utils/roleConstants.js';
44
45 import '../styles/CatalogPanel.scss'
46
47 const createDropZone = function (action, clickable, dropTarget) {
48 const dropZone = new UploadDropZone(ReactDOM.findDOMNode(dropTarget), [clickable], action);
49 dropZone.on('dragover', this.onDragOver);
50 dropZone.on('dragend', this.onDragEnd);
51 dropZone.on('addedfile', this.onFileAdded);
52 return dropZone;
53 };
54
55 const uiTransientState = {
56 isDrop: false,
57 isDragging: false,
58 isDraggingFiles: false,
59 timeoutId: 0
60 };
61
62 const CatalogPanel = React.createClass({
63 mixins: [PureRenderMixin],
64 getInitialState() {
65 return CatalogPanelStore.getState();
66 },
67 getDefaultProps() {
68 },
69 componentWillMount() {
70 },
71 componentDidMount() {
72 CatalogPanelStore.listen(this.onChange);
73 document.body.addEventListener('dragover', this.onDragOver);
74 document.body.addEventListener('dragend', this.onDragEnd);
75 window.addEventListener('dragend', this.onDragEnd);
76 },
77 componentDidUpdate() {
78 },
79 componentWillUnmount() {
80 CatalogPanelStore.unlisten(this.onChange);
81 document.body.removeEventListener('dragover', this.onDragOver);
82 document.body.removeEventListener('dragend', this.onDragEnd);
83 window.removeEventListener('dragend', this.onDragEnd);
84 },
85 contextTypes: {
86 userProfile: React.PropTypes.object
87 },
88 render() {
89 const User = this.context.userProfile || {};
90 const isModifiableByUser = isRBACValid(User, [ROLES.PROJECT.PROJECT_ADMIN, ROLES.PROJECT.CATALOG_ADMIN]);
91
92 const onDropCatalogItem = e => {
93 e.preventDefault();
94 clearTimeout(uiTransientState.timeoutId);
95 uiTransientState.isDragging = false;
96 uiTransientState.isDrop = true;
97 const item = JSON.parse(e.dataTransfer.getData('text')).item;
98 CatalogItemsActions.exportSelectedCatalogItems(item);
99 CatalogPanelTrayActions.open();
100 };
101
102 const onDropUpdatePackage = e => {
103 e.preventDefault();
104 clearTimeout(uiTransientState.timeoutId);
105 uiTransientState.isDragging = false;
106 uiTransientState.isDrop = true;
107 CatalogPanelTrayActions.open();
108 };
109
110 const onDropOnboardPackage = e => {
111 e.preventDefault();
112 clearTimeout(uiTransientState.timeoutId);
113 uiTransientState.isDragging = false;
114 uiTransientState.isDrop = true;
115 CatalogPanelTrayActions.open();
116 };
117
118 const isDraggingItem = uiTransientState.isDragging && !uiTransientState.isDraggingFiles;
119 const isDraggingFiles = uiTransientState.isDragging && uiTransientState.isDraggingFiles;
120 const updateDropZone = createDropZone.bind(this, UploadDropZone.ACTIONS.update, '.action-update-catalog-package');
121 const onboardDropZone = createDropZone.bind(this, UploadDropZone.ACTIONS.onboard, '.action-onboard-catalog-package');
122 const className = ClassNames('CatalogPanel', { '-is-tray-open': this.state.isTrayOpen });
123 const hasNoCatalogs = this.props.hasNoCatalogs;
124 const isLoading = this.props.isLoading;
125 const packageManagerPanel = (
126 <CatalogPanelTray show={this.state.isTrayOpen}>
127 <DropZonePanel show={isDraggingItem} title="Drop catalog item to export.">
128 <DropTarget className="action-export-catalog-items" onDrop={onDropCatalogItem}>
129 <span>Export catalog item.</span>
130 </DropTarget>
131 </DropZonePanel>
132 <DropZonePanel show={isDraggingFiles}>
133 <DropTarget className="action-onboard-catalog-package" dropZone={onboardDropZone} onDrop={onDropOnboardPackage}>
134 <span>On-board new catalog package.</span>
135 </DropTarget>
136 <DropTarget className="action-update-catalog-package" dropZone={updateDropZone} onDrop={onDropUpdatePackage}>
137 <span>Update existing catalog package.</span>
138 </DropTarget>
139 </DropZonePanel>
140 <CatalogPackageManager />
141 </CatalogPanelTray>
142 );
143 return (
144 <div className={className} data-resizable="right" data-resizable-handle-offset="0 6" style={{ width: this.props.layout.left }}>
145 <CatalogPanelToolbar rbacDisabled={this.props.rbacDisabled} />
146 <div className="CatalogPanelBody">
147 {(() => {
148 if (isLoading) {
149 return (
150 <div className="LoaderWrapper">
151 <LoadingIndicator />
152 </div>
153 )
154 }
155 if (hasNoCatalogs) {
156 return messages.catalogWelcome(isModifiableByUser);
157 }
158 return (
159 <div>
160 <CatalogFilter filterByType={this.props.filterByType} />
161 <CatalogItems filterByType={this.props.filterByType} />
162 </div>
163 );
164 })()}
165 </div>
166 {isModifiableByUser ? packageManagerPanel : null}
167 </div>
168 );
169
170 },
171 onChange(state) {
172 this.setState(state);
173 },
174 onDragOver(e) {
175 // NOTE do not call preventDefault here - see DropTarget
176 if (!uiTransientState.isDragging) {
177 uiTransientState.isDrop = false;
178 uiTransientState.isDragging = true;
179 uiTransientState.wasTrayOpen = this.state.isTrayOpen;
180 uiTransientState.isDraggingFiles = _includes(e.dataTransfer.types, 'Files');
181 const dragState = ComposerAppStore.getState().drag || {};
182 if (uiTransientState.isDraggingFiles || (dragState.type === 'catalog-item')) {
183 CatalogPanelTrayActions.open();
184 }
185 }
186 e.dataTransfer.dropEffect = 'none';
187 // the drag-end event does not fire on drag events that originate
188 // outside of the browser, e.g. dragging files from desktop, so
189 // we use this debounced callback to simulate a drag-end event
190 clearTimeout(uiTransientState.timeoutId);
191 uiTransientState.timeoutId = setTimeout(() => {
192 this.onDragEnd();
193 }, 400);
194 },
195 onDragEnd() {
196 clearTimeout(uiTransientState.timeoutId);
197 if (uiTransientState.isDragging) {
198 uiTransientState.isDragging = false;
199 if (uiTransientState.isDrop || uiTransientState.wasTrayOpen) {
200 CatalogPanelTrayActions.open();
201 } else {
202 CatalogPanelTrayActions.close();
203 }
204 }
205 },
206 onFileAdded() {
207 CatalogPanelTrayActions.open();
208 }
209 });
210
211 export default CatalogPanel;