NOTICKET: Merging OSM/master to OSM/projects
[osm/UI.git] / skyquake / framework / widgets / skyquake_nav / skyquakeNav.jsx
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
19 import React from 'react';
20 import { Link } from 'react-router';
21 import Utils from 'utils/utils.js';
22 import Crouton from 'react-crouton';
23 import 'style/common.scss';
24
25 import './skyquakeNav.scss';
26 import SelectOption from '../form_controls/selectOption.jsx';
27 import {FormSection} from '../form_controls/formControls.jsx';
28 import {isRBACValid, SkyquakeRBAC} from 'widgets/skyquake_rbac/skyquakeRBAC.jsx';
29
30 //Temporary, until api server is on same port as webserver
31 import rw from 'utils/rw.js';
32
33 var API_SERVER = rw.getSearchParams(window.location).api_server;
34 var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
35
36 //
37 // Internal classes/functions
38 //
39
40 class LogoutAppMenuItem extends React.Component {
41     handleLogout() {
42         Utils.clearAuthentication();
43     }
44     render() {
45         return (
46             <div className="app">
47                 <h2>
48                     <a onClick={this.handleLogout}>
49                         Logout
50                     </a>
51                 </h2>
52             </div>
53         );
54     }
55 }
56
57 class SelectProject extends React.Component {
58     constructor(props) {
59         super(props);
60     }
61     selectProject(e) {
62         let value = JSON.parse(e.currentTarget.value);
63         console.log('selected project', value)
64     }
65     render() {
66         let props = this.props;
67         let currentValue = JSON.stringify(props.currentProject);
68         let projects = this.props.projects && this.props.projects.map((p,i) => {
69             return {
70                 label: p.name,
71                 value: p.name
72             }
73         });
74         let hasProjects = (this.props.projects && (this.props.projects.length > 0))
75         return (
76             <div className="userSection app">
77                 {
78                     hasProjects ?  'Project:' : 'No Projects Assigned'
79                 }
80                 {
81                     hasProjects ?
82                     <SelectOption
83                         options={projects}
84                         value={currentValue}
85                         defaultValue={currentValue}
86                         onChange={props.onSelectProject}
87                         className="projectSelect" />
88                     : null
89                 }
90             </div>
91         )
92     }
93 }
94
95 class UserNav extends React.Component {
96     constructor(props) {
97         super(props);
98     }
99     handleLogout() {
100         Utils.clearAuthentication();
101     }
102     selectProject(e) {
103         let value = JSON.parse(e.currentTarget.value)
104         console.log('selected project', value)
105     }
106     render() {
107         let props = this.props;
108         let userProfileLink = '';
109         this.props.nav['user_management'] && this.props.nav['user_management'].routes.map((r) => {
110             if(r.unique) {
111                 userProfileLink = returnLinkItem(r, props.currentUser)
112             }
113         })
114         return (
115             <div className="app">
116                 <h2>
117                     USER: {userProfileLink}
118                     <span className="oi" data-glyph="caret-bottom"></span>
119                 </h2>
120                 <ul className="menu">
121                     <li>
122                         <a onClick={this.handleLogout}>
123                             Logout
124                         </a>
125                     </li>
126                 </ul>
127             </div>
128         )
129     }
130 }
131
132 UserNav.defaultProps = {
133     projects: [
134
135     ]
136 }
137
138 //
139 // Exported classes and functions
140 //
141
142 //
143 /**
144  * Skyquake Nav Component. Provides navigation functionality between all plugins
145  */
146 export default class skyquakeNav extends React.Component {
147     constructor(props) {
148         super(props);
149         this.state = {};
150         this.state.validateErrorEvent = 0;
151         this.state.validateErrorMsg = '';
152     }
153     componentDidMount() {
154         this.props.store.openProjectSocket();
155         this.props.store.getUserProfile();
156     }
157     validateError = (msg) => {
158         this.setState({
159             validateErrorEvent: true,
160             validateErrorMsg: msg
161         });
162     }
163     validateReset = () => {
164         this.setState({
165             validateErrorEvent: false
166         });
167     }
168     returnCrouton = () => {
169         return <Crouton
170             id={Date.now()}
171             message={this.state.validateErrorMsg}
172             type={"error"}
173             hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
174             onDismiss={this.validateReset}
175         />;
176     }
177     render() {
178         let html;
179         html = (
180                 <div>
181                 {this.returnCrouton()}
182             <nav className="skyquakeNav">
183                 {buildNav.call(this, this.props.nav, this.props.currentPlugin, this.props)}
184             </nav>
185
186             </div>
187         )
188         return html;
189     }
190 }
191 skyquakeNav.defaultProps = {
192     nav: {}
193 }
194 skyquakeNav.contextTypes = {
195   userProfile: React.PropTypes.object
196 };
197 /**
198  * Returns a React Component
199  * @param  {object} link  Information about the nav link
200  * @param  {string} link.route Hash route that the SPA should resolve
201  * @param  {string} link.name Link name to be displayed
202  * @param  {number} index index of current array item
203  * @return {object} component A React LI Component
204  */
205 //This should be extended to also make use of internal/external links and determine if the link should refer to an outside plugin or itself.
206 export function buildNavListItem (k, link, index) {
207     let html = false;
208     if (link.type == 'external') {
209         this.hasSubNav[k] = true;
210         html = (
211             <li key={index}>
212                 {returnLinkItem(link)}
213             </li>
214         );
215     }
216     return html;
217 }
218
219 /**
220  * Builds a link to a React Router route or a new plugin route.
221  * @param  {object} link Routing information from nav object.
222  * @return {object}  component   returns a react component that links to a new route.
223  */
224 export function returnLinkItem(link, label) {
225     let ref;
226     let route = link.route;
227     if(link.isExternal) {
228         ref = (
229             <a href={route}>{label || link.label}</a>
230         )
231     } else {
232         if(link.path && link.path.replace(' ', '') != '') {
233             route = link.path;
234         }
235         if(link.query) {
236             let query = {};
237             query[link.query] = '';
238             route = {
239                 pathname: route,
240                 query: query
241             }
242         }
243         ref = (
244             <Link to={route}>
245                 {label || link.label}
246             </Link>
247         )
248     }
249     return ref;
250 }
251
252
253
254
255 /**
256  * Constructs nav for each plugin, along with available subnavs
257  * @param  {array} nav List returned from /nav endpoint.
258  * @return {array}     List of constructed nav element for each plugin
259  */
260 export function buildNav(nav, currentPlugin, props) {
261     let navList = [];
262     let navListHTML = [];
263     let secondaryNav = [];
264     let adminNav = [];
265     let self = this;
266     const User = this.context.userProfile;
267     self.hasSubNav = {};
268     let secondaryNavHTML = (
269         <div className="secondaryNav" key="secondaryNav">
270             {secondaryNav}
271             <div className="app admin">
272                 <h2>
273                     <a>
274                         ADMIN <span className="oi" data-glyph="caret-bottom"></span>
275                     </a>
276                 </h2>
277                 <ul className="menu">
278                     {
279                         adminNav
280                     }
281                 </ul>
282             </div>
283             <SelectProject
284                 onSelectProject={props.store.selectActiveProject}
285                 projects={props.projects}
286                 currentProject={props.currentProject} />
287             <UserNav
288                 currentUser={props.currentUser}
289                 nav={nav}  />
290         </div>
291     )
292     for (let k in nav) {
293         if (nav.hasOwnProperty(k)) {
294             self.hasSubNav[k] = false;
295             let header = null;
296             let navClass = "app";
297             let routes = nav[k].routes;
298             let navItem = {};
299             //Primary plugin title and link to dashboard.
300             let route;
301             let NavList;
302             if (API_SERVER) {
303                 route = routes[0].isExternal ? '/' + k + '/index.html?api_server=' + API_SERVER + '' + '&upload_server=' + UPLOAD_SERVER + '' : '';
304             } else {
305                 route = routes[0].isExternal ? '/' + k + '/' : '';
306             }
307             let dashboardLink = returnLinkItem({
308                 isExternal: routes[0].isExternal,
309                 pluginName: nav[k].pluginName,
310                 label: nav[k].label || k,
311                 route: route
312             });
313             let shouldAllow = nav[k].allow || ['*'];
314             if (nav[k].pluginName == currentPlugin) {
315                 navClass += " active";
316             }
317             NavList = nav[k].routes.map(buildNavListItem.bind(self, k));
318             navItem.priority = nav[k].priority;
319             navItem.order = nav[k].order;
320             if(nav[k].admin_link) {
321
322                 if (isRBACValid(User, shouldAllow) ){
323                     adminNav.push((
324                         <li key={nav[k].name}>
325                             {dashboardLink}
326                         </li>
327                     ))
328                 }
329             } else {
330                 if (isRBACValid(User, shouldAllow) ){
331                     navItem.html = (
332                         <div  key={k} className={navClass}>
333                             <h2>{dashboardLink} {self.hasSubNav[k] ? <span className="oi" data-glyph="caret-bottom"></span> : ''}</h2>
334                             <ul className="menu">
335                                 {
336                                     NavList
337                                 }
338                             </ul>
339                         </div>
340                     );
341                 }
342             navList.push(navItem)
343             }
344
345         }
346     }
347     //Sorts nav items by order and returns only the markup
348     navListHTML = navList.sort((a,b) => a.order - b.order).map(function(n) {
349         if((n.priority  < 2)){
350             return n.html;
351         } else {
352             secondaryNav.push(n.html);
353         }
354     });
355     navListHTML.push(secondaryNavHTML);
356     return navListHTML;
357 }