Newer
Older
import React from 'react'
import PropTypes from 'prop-types'
import intl from 'react-intl-universal'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import IconButton from '@material-ui/core/IconButton'
import Typography from '@material-ui/core/Typography'
import MenuItem from '@material-ui/core/MenuItem'
import Menu from '@material-ui/core/Menu'
import MoreIcon from '@material-ui/icons/MoreVert'
import Button from '@material-ui/core/Button'
import { Link, NavLink } from 'react-router-dom'
import TopBarSearchField from './TopBarSearchField'
import TopBarInfoButton from './TopBarInfoButton'
import TopBarLanguageButton from './TopBarLanguageButton'
import Divider from '@material-ui/core/Divider'
import { has } from 'lodash'
import secoLogo from '../../img/logos/seco-logo-48x50.png'
topBarToolbar: props => ({
minHeight: props.layoutConfig.topBar.reducedHeight,
[theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
minHeight: props.layoutConfig.topBar.defaultHeight
},
paddingLeft: theme.spacing(1.5),
paddingRight: theme.spacing(1.5)
display: 'none',
[theme.breakpoints.up(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
display: 'flex',
[theme.breakpoints.up(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
appBarButton: {
color: 'white !important',
border: `1px solid ${theme.palette.primary.main}`
appBarButtonActive: {
border: '1px solid white'
},
appBarDivider: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
borderLeft: '2px solid white'
[theme.breakpoints.down(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
}),
secoLogoImage: props => ({
height: 32,
[theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
height: 50
}
}),
mainLogo: props => ({
height: 23,
[theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
height: 40
},
marginRight: theme.spacing(1)
}),
mainLogoButtonRoot: {
paddingLeft: 0,
[theme.breakpoints.down('xs')]: {
minWidth: 48
}
},
mainLogoButtonLabel: {
justifyContent: 'left'
},
// set color and background explicitly to keep Google Lighthouse happy
color: '#fff',
background: theme.palette.primary.main,
fontSize: '1.5rem'
},
...(props.layoutConfig.topBar.hideLogoTextOnMobile && {
[theme.breakpoints.down('xs')]: {
display: 'none'
}
})
}),
/**
* Responsive app bar with a search field, perspective links, info links and a language
* selector. Based on Material-UI's App Bar component.
*/
const TopBar = props => {
const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null)
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl)
const { perspectives, currentLocale, availableLocales, rootUrl, layoutConfig } = props
const { topBar } = layoutConfig
const handleMobileMenuOpen = event => setMobileMoreAnchorEl(event.currentTarget)
const handleMobileMenuClose = () => setMobileMoreAnchorEl(null)
const clientFSMode = props.location.pathname.indexOf('clientFS') !== -1
let showSearchField = true
if (has(layoutConfig.topBar, 'showSearchField')) {
showSearchField = layoutConfig.topBar.showSearchField
}
// https://material-ui.com/components/buttons/#third-party-routing-library
const AdapterLink = React.forwardRef((props, ref) => <Link innerRef={ref} {...props} />)
const AdapterNavLink = React.forwardRef((props, ref) => <NavLink innerRef={ref} {...props} />)
const getInternalLink = perspective => {
const searchMode = has(perspective, 'searchMode') ? perspective.searchMode : 'faceted-search'
let link = null
if (searchMode === 'dummy-internal') {
link = `${props.rootUrl}${perspective.internalLink}`
}
if (searchMode !== 'dummy-internal') {
link = `${props.rootUrl}/${perspective.id}/${searchMode}`
}
return link
}
key={perspective.id}
href={perspective.externalUrl}
target='_blank'
rel='noopener noreferrer'
>
<MenuItem>
{perspective.label
? perspective.label.toUpperCase()
: intl.get(`perspectives.${perspective.id}.label`).toUpperCase()}
{intl.get(`perspectives.${perspective.id}.label`).toUpperCase()}
key={perspective.id}
href={perspective.externalUrl}
target='_blank'
rel='noopener noreferrer'
>
<Button
{perspective.label
? perspective.label
: intl.get(`perspectives.${perspective.id}.label`).toUpperCase()}
className={classes.appBarButton}
component={AdapterNavLink}
isActive={(match, location) => location.pathname.startsWith(`${props.rootUrl}/${perspective.id}`)}
activeClassName={classes.appBarButtonActive}
{intl.get(`perspectives.${perspective.id}.label`).toUpperCase()}
const renderInfoItem = item => {
let jsx
if (item.externalLink) {
jsx = (
<a
className={classes.link}
key={item.id}
href={intl.get(`topBar.info.${item.translatedUrl}`)}
target='_blank'
rel='noopener noreferrer'
>
<MenuItem onClick={handleMobileMenuClose}>
{intl.get(`topBar.info.${item.translatedText}`).toUpperCase()}
</MenuItem>
</a>
)
} else {
jsx = (
<MenuItem
key={item.id}
component={AdapterLink}
to={`${props.rootUrl}${item.internalLink}`}
onClick={handleMobileMenuClose}
>
{intl.get(`topBar.info.${item.translatedText}`).toUpperCase()}
)
}
return jsx
}
const renderMobileMenu = perspectives => {
const { topBar } = props.layoutConfig
const { infoDropdown } = topBar
return (
<Menu
anchorEl={mobileMoreAnchorEl}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={isMobileMenuOpen}
onClose={handleMobileMenuClose}
{perspectives.map(perspective => perspective.hideTopPerspectiveButton ? null : renderMobileMenuItem(perspective))}
<Divider />
{renderMobileMenuItem({
id: 'feedback',
externalUrl: props.layoutConfig.topBar.feedbackLink,
label: intl.get('topBar.feedback')
})}
{infoDropdown.map(item => renderInfoItem(item))}
{topBar.externalInstructions && renderMobileMenuItem({
id: 'instructions',
externalUrl: intl.get('topBar.instructionsUrl'),
label: intl.get('topBar.instructions')
})}
{!topBar.externalInstructions &&
<MenuItem
key='instructions'
component={AdapterLink}
{intl.get('topBar.instructions').toUpperCase()}
</MenuItem>}
return (
<div className={classes.root}>
{/* Add an empty Typography element to ensure that that the MuiTypography class is loaded for
any lower level components that use MuiTypography class only in translation files */}
<AppBar position='static'>
<Toolbar className={classes.topBarToolbar}>
<Button
component={AdapterLink} to='/'
classes={{
root: classes.mainLogoButtonRoot,
label: classes.mainLogoButtonLabel
}}
onClick={() => clientFSMode ? props.clientFSClearResults() : null}
{topBar.logoImage &&
<img
className={classes.mainLogo}
src={topBar.logoImage}
alt={`${intl.get('appTitle.short')} logo`}
/>}
<Typography className={classes.mainLogoTypography} variant='h5'>
{props.xsScreen ? intl.get('appTitle.mobile') : intl.get('appTitle.short')}
</Typography>
<TopBarSearchField
fetchFullTextResults={props.fetchFullTextResults}
clearResults={props.clearResults}
xsScreen={props.xsScreen}
rootUrl={rootUrl}
/>}
<div className={classes.grow} />
<div className={classes.sectionDesktop}>
{perspectives.map((perspective, index) => perspective.hideTopPerspectiveButton ? null : renderDesktopTopMenuItem(perspective, index))}
{renderDesktopTopMenuItem({
id: 'feedback',
<TopBarInfoButton rootUrl={props.rootUrl} layoutConfig={layoutConfig} />
{topBar.externalInstructions && renderDesktopTopMenuItem({
id: 'instructions',
externalUrl: intl.get('topBar.instructionsUrl'),
label: intl.get('topBar.instructions')
})}
{!topBar.externalInstructions &&
<Button
className={classes.appBarButton}
component={AdapterNavLink}
to={`${props.rootUrl}/instructions`}
isActive={(match, location) => location.pathname.startsWith(`${props.rootUrl}/instructions`)}
activeClassName={classes.appBarButtonActive}
>
{intl.get('topBar.instructions')}
</Button>}
<TopBarLanguageButton
currentLocale={currentLocale}
availableLocales={availableLocales}
loadLocales={props.loadLocales}
location={props.location}
/>}
</div>
<a
className={classes.secoLogo}
href='https://seco.cs.aalto.fi'
target='_blank'
rel='noopener noreferrer'
>
<Button aria-label='link to Semantic Computing research group homepage'>
<img
className={classes.secoLogoImage}
src={secoLogo}
alt='Semantic Computing research group logo'
/>
</Button>
<TopBarLanguageButton
currentLocale={currentLocale}
availableLocales={availableLocales}
loadLocales={props.loadLocales}
location={props.location}
/>}
<IconButton
aria-label='display more actions' color='inherit'
className={classes.mobileMenuButton}
onClick={handleMobileMenuOpen}
>
<MoreIcon />
</IconButton>
</div>
</Toolbar>
</AppBar>
{renderMobileMenu(perspectives)}
</div>
)
/**
* Redux action for full text search results using the search field.
*/
fetchFullTextResults: PropTypes.func.isRequired,
/**
* Redux action for clearing the full text results.
*/
loadLocales: PropTypes.func.isRequired,
currentLocale: PropTypes.string.isRequired,
/**
* Available locales as an array of objects with two-letter codes as keys.
*/
availableLocales: PropTypes.array.isRequired,
/**
* Perspective config as an array of objects.
*/
perspectives: PropTypes.array.isRequired,
/**
* Flag for checking if the screen is extra small.
*/
/**
* React Router's location object. The perspective links are highlighted based on this.
*/
rootUrl: PropTypes.string.isRequired,
layoutConfig: PropTypes.object.isRequired