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