github.com/replicatedhq/ship@v0.55.0/web/init/src/components/shared/NavBar.jsx (about) 1 import React, { Fragment } from "react"; 2 import assign from "object-assign"; 3 import { Link, withRouter } from "react-router-dom"; 4 import StepNumbers from "./StepNumbers"; 5 import upperFirst from "lodash/upperFirst"; 6 import NavItem from "./NavItem"; 7 // This is hardcoded for now as we're bundling it from `@replicatedhq/ship-init` 8 // and then re-bundling the svg as a part of the `@replicatedhq/ship-app` bundle 9 // for it to be served via the ship binary. As part of the bundling process the name 10 // is mutated to a data-uri twice. 11 // Do not remove the import. 12 import "../../assets/images/ship-logo.png"; 13 const shipLogo = "static/media/b3d517c0409239a363a3c18ce9a0eda2.b3d517c0.png"; 14 export class NavBar extends React.Component { 15 16 constructor() { 17 super(); 18 this.state = { 19 navDetails: { 20 name: "", 21 icon: "", 22 }, 23 imageLoaded: false, 24 }; 25 } 26 27 isActive = (pathname = "") => { 28 return (item = {}) => { 29 if (!item.linkTo) return false; 30 return pathname.indexOf(`${item.linkTo}`) > -1; 31 }; 32 } 33 34 handleRouteChange = (route, dropdownKey) => { 35 if (this.state[`${dropdownKey}Active`]) { 36 this.setState({ 37 [`${dropdownKey}Active`]: false 38 }); 39 } 40 const { basePath } = this.props; 41 this.props.history.push(`${basePath}/${route}`); 42 } 43 44 handleLogOut = (e) => { 45 e.preventDefault(); 46 } 47 48 getNavItems = () => { 49 const token = true; 50 return[ !token ? null : 51 { 52 id: 0, 53 label: "Dashboard", 54 linkTo: "/dashboard", 55 isActive: this.props.location.pathname === "/dashboard", 56 position: "left", 57 }, 58 { 59 id: 1, 60 label: "Audit log", 61 linkTo: "/audit-log", 62 isActive: this.props.location.pathname === "/audit-log", 63 position: "left", 64 }, 65 !token ? null : { 66 id: 2, 67 label: "Logout", 68 onClick: (e) => { this.handleLogOut(e) }, 69 position: "right" 70 } 71 ]; 72 } 73 74 combineItems = (methods) => { 75 return methods.reduce((accum, method) => ( 76 accum.concat(method(this.props)) 77 ), []); 78 } 79 80 onClick = (item) => { 81 return (e, ...rest) => { 82 const activeKey = `${item.dropdownLabel || item.id || ""}Active`; 83 if (item.href) return; 84 if (typeof item.onClick === "function") { 85 item.onClick(e, ...rest); 86 return; 87 } 88 this.setState({ 89 [activeKey]: !this.state[activeKey] 90 }); 91 }; 92 } 93 94 preloadNavIconImage = (iconUrl) => new Promise( 95 (resolve, reject) => { 96 var image = new Image(); 97 image.onload = resolve; 98 image.onerror = reject; 99 image.src = iconUrl; 100 } 101 ) 102 103 componentDidUpdate() { 104 const { shipAppMetadata } = this.props; 105 const { imageLoaded } = this.state; 106 107 if (!imageLoaded && shipAppMetadata.loaded) { 108 this.preloadNavIconImage(shipAppMetadata.icon) 109 .then(() => { 110 this.setState({ 111 navDetails: { 112 name: shipAppMetadata.name, 113 icon: shipAppMetadata.icon, 114 }, 115 imageLoaded: true, 116 }) 117 }) 118 .catch(() => this.setState({ 119 navDetails: { 120 name: shipAppMetadata.name, 121 icon: shipLogo, 122 }, 123 imageLoaded: true, 124 })); 125 } 126 } 127 128 render() { 129 const { className, routes, basePath } = this.props; 130 const { navDetails, imageLoaded } = this.state; 131 const isPathActive = this.isActive( 132 typeof window === "object" 133 ? window.location.pathname 134 : "", 135 ); 136 137 const itemsArr = [this.getNavItems.bind(this)]; 138 // build items 139 const headerItems = this.combineItems(itemsArr) 140 .filter(item => item) 141 .map(item => (assign(item, { 142 isActive: isPathActive(item), 143 }))); 144 const renderItem = item => { 145 return ( 146 <NavItem 147 key={item.id} 148 {...item} 149 onClick={this.onClick(item)} 150 isDropDownActive={this.state[`${item.dropdownLabel || item.id || ""}Active`]} 151 /> 152 ); 153 }; 154 155 const rightItems = headerItems.filter(item => item.position === "right"); 156 const leftItems = headerItems.filter(item => item.position === "left"); 157 158 const [ firstRoute = {} ] = routes; 159 const { id: firstRouteId } = firstRoute; 160 161 const headerLogo = ( 162 <div className="HeaderLogo-wrapper flex-column flex1 flex-verticalCenter u-position--relative"> 163 <div className="HeaderLogo"> 164 <Link to={`/${firstRouteId}`} tabIndex="-1"> 165 <img src={navDetails.icon} className="logo" /> 166 </Link> 167 </div> 168 </div> 169 ); 170 171 const headerName = navDetails && navDetails.icon ? null : ( 172 <div className="flex-column flex-auto HeaderName-wrapper"> 173 {navDetails.name && navDetails.name.length ? 174 <div className="flex-column flex1 flex-verticalCenter u-position--relative"> 175 <p className="u-fontSize--larger u-fontWeight--bold u-color--tundora u-lineHeight--default u-marginRight--30">{upperFirst(navDetails.name)}</p> 176 </div> 177 : <div className="flex-column flex1 flex-verticalCenter u-position--relative"> 178 <p className="u-fontSize--larger u-fontWeight--bold u-color--tundora u-lineHeight--default u-marginRight--30">Replicated Ship</p> 179 </div> 180 } 181 </div> 182 ); 183 184 return ( 185 <div className={`NavBarWrapper flex flex-auto ${className || ""}`}> 186 <div className="container flex flex1"> 187 <div className="flex1 flex justifyContent--center alignItems--center"> 188 <div className="flex1 flex"> 189 <div className="flex flex-auto metadata-wrapper"> 190 { 191 imageLoaded ? 192 ( 193 <Fragment> 194 {headerLogo} 195 {headerName} 196 </Fragment> 197 ) : 198 null 199 } 200 {this.props.hideLinks ? null : 201 <div className="flex flex-auto alignItems--center left-items"> 202 {leftItems.map(renderItem)} 203 </div> 204 } 205 </div> 206 {this.props.hideSteps ? null : 207 <div className="flex flex1"> 208 <StepNumbers basePath={basePath} steps={routes} inNav={true} /> 209 </div> 210 } 211 {this.props.hideLinks ? null : 212 <div className="flex flex1 justifyContent--flexEnd right-items"> 213 <div className="flex flex-auto alignItems--center"> 214 {rightItems.map(renderItem)} 215 </div> 216 </div> 217 } 218 </div> 219 </div> 220 </div> 221 </div> 222 ); 223 } 224 } 225 226 227 export default withRouter(NavBar);