From 83185436351574900c1bdc5107621a3ecc966155 Mon Sep 17 00:00:00 2001
From: esikkala <esko.ikkala@aalto.fi>
Date: Tue, 18 Jan 2022 08:58:14 +0200
Subject: [PATCH] Adapt main page and top bar for MUI v5

---
 src/client/components/App.js                  |  41 ++-
 src/client/components/main_layout/TextPage.js |  95 +++---
 src/client/components/main_layout/TopBar.js   | 275 +++++++++---------
 .../main_layout/TopBarSearchField.js          | 108 ++++---
 .../components/perspectives/sampo/Footer.js   |  82 +++---
 .../components/perspectives/sampo/Main.js     |  19 +-
 src/client/containers/SemanticPortal.js       |  51 ++--
 src/client/img/logos/logo.png                 | Bin 0 -> 3694 bytes
 src/configs/portalConfig.json                 |   5 +-
 9 files changed, 351 insertions(+), 325 deletions(-)
 create mode 100644 src/client/img/logos/logo.png

diff --git a/src/client/components/App.js b/src/client/components/App.js
index cf785afd..a4de9f0f 100644
--- a/src/client/components/App.js
+++ b/src/client/components/App.js
@@ -1,12 +1,22 @@
 import React from 'react'
-import { ThemeProvider, StyledEngineProvider, createTheme } from '@mui/material/styles'
+import { ThemeProvider, createTheme } from '@mui/material/styles'
 import AdapterMoment from '@mui/lab/AdapterDateFns'
 import LocalizationProvider from '@mui/lab/LocalizationProvider'
 // import 'moment/locale/fi'
 import SemanticPortal from '../containers/SemanticPortal'
 import portalConfig from '../../configs/portalConfig.json'
 
-const { colorPalette } = portalConfig.layoutConfig
+const { colorPalette, reducedHeightBreakpoint, hundredPercentHeightBreakPoint, topBar } = portalConfig.layoutConfig
+
+const muiDefaultBreakpoints = {
+  xs: 0,
+  sm: 600,
+  md: 900,
+  lg: 1200,
+  xl: 1536
+}
+
+const defaultTheme = createTheme()
 
 const theme = createTheme({
   palette: {
@@ -17,7 +27,26 @@ const theme = createTheme({
       main: colorPalette.secondary.main
     }
   },
+  breakpoints: {
+    values: {
+      ...muiDefaultBreakpoints,
+      reducedHeight: muiDefaultBreakpoints[reducedHeightBreakpoint],
+      hundredPercentHeight: muiDefaultBreakpoints[hundredPercentHeightBreakPoint]
+    }
+  },
   components: {
+    MuiToolbar: {
+      styleOverrides: {
+        regular: {
+          [defaultTheme.breakpoints.down(reducedHeightBreakpoint)]: {
+            minHeight: topBar.reducedHeight
+          },
+          [defaultTheme.breakpoints.up(reducedHeightBreakpoint)]: {
+            minHeight: topBar.defaultHeight
+          }
+        }
+      }
+    },
     MuiTooltip: {
       tooltip: {
         fontSize: '1 rem'
@@ -64,11 +93,9 @@ const theme = createTheme({
 
 const App = () => (
   <LocalizationProvider dateAdapter={AdapterMoment}>
-    <StyledEngineProvider injectFirst>
-      <ThemeProvider theme={theme}>
-        <SemanticPortal />
-      </ThemeProvider>
-    </StyledEngineProvider>
+    <ThemeProvider theme={theme}>
+      <SemanticPortal />
+    </ThemeProvider>
   </LocalizationProvider>
 )
 
diff --git a/src/client/components/main_layout/TextPage.js b/src/client/components/main_layout/TextPage.js
index 5f7f70cd..c7b99249 100644
--- a/src/client/components/main_layout/TextPage.js
+++ b/src/client/components/main_layout/TextPage.js
@@ -1,50 +1,77 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import makeStyles from '@mui/styles/makeStyles';
 import Paper from '@mui/material/Paper'
-
-const useStyles = makeStyles(theme => ({
-  root: {
-    width: '100%',
-    height: '100%',
-    display: 'flex',
-    justifyContent: 'center'
-  },
-  layout: {
-    width: 'auto',
-    padding: theme.spacing(3),
-    marginLeft: theme.spacing(3),
-    marginRight: theme.spacing(3),
-    [theme.breakpoints.down('md')]: {
-      padding: theme.spacing(1.5),
-      marginLeft: 0,
-      marginRight: 0
-    },
-    [theme.breakpoints.up(1100 + theme.spacing(6))]: {
-      width: 1100,
-      marginLeft: 'auto',
-      marginRight: 'auto'
-    },
-    overflow: 'auto'
-  }
-}))
+import Box from '@mui/material/Box'
+import { getSpacing } from '../../helpers/helpers'
 
 /**
  * A component for creating a responsive page with static content.
  */
 const TextPage = props => {
-  const classes = useStyles()
-
+  const { layoutConfig } = props
   return (
-    <div className={classes.root}>
-      <Paper className={classes.layout}>
-        {props.children}
-      </Paper>
-    </div>
+    <Box
+      sx={theme => ({
+        margin: theme.spacing(0.5),
+        width: `calc(100% - ${getSpacing(theme, 1)}px)`,
+        [theme.breakpoints.up(layoutConfig.hundredPercentHeightBreakPoint)]: {
+          height: `calc(100% - ${layoutConfig.topBar.reducedHeight + getSpacing(theme, 1)}px)`
+        },
+        [theme.breakpoints.up(layoutConfig.reducedHeightBreakpoint)]: {
+          height: `calc(100% - ${layoutConfig.topBar.defaultHeight + getSpacing(theme, 1)}px)`
+        }
+      })}
+    >
+      <Box
+        sx={{
+          width: '100%',
+          height: '100%',
+          display: 'flex',
+          justifyContent: 'center'
+        }}
+      >
+        <Paper
+          sx={theme => ({
+            width: 'auto',
+            padding: theme.spacing(3),
+            marginLeft: theme.spacing(3),
+            marginRight: theme.spacing(3),
+            [theme.breakpoints.down('md')]: {
+              padding: theme.spacing(1.5),
+              marginLeft: 0,
+              marginRight: 0
+            },
+            [theme.breakpoints.up(1100 + getSpacing(theme, 6))]: {
+              width: 1100,
+              marginLeft: 'auto',
+              marginRight: 'auto'
+            },
+            overflow: 'auto',
+            '& h1': {
+              ...theme.typography.h2,
+              marginTop: 0,
+              marginBottom: theme.spacing(1)
+            },
+            '& h2': {
+              ...theme.typography.h4
+            },
+            '& h3': {
+              ...theme.typography.h6
+            },
+            '& p, li': {
+              ...theme.typography.body1
+            }
+          })}
+        >
+          {props.children}
+        </Paper>
+      </Box>
+    </Box>
   )
 }
 
 TextPage.propTypes = {
+  layoutConfig: PropTypes.object.isRequired,
   /**
    * The content of the page.
    */
diff --git a/src/client/components/main_layout/TopBar.js b/src/client/components/main_layout/TopBar.js
index 312f6d57..862f9add 100644
--- a/src/client/components/main_layout/TopBar.js
+++ b/src/client/components/main_layout/TopBar.js
@@ -7,9 +7,10 @@ import IconButton from '@mui/material/IconButton'
 import Typography from '@mui/material/Typography'
 import MenuItem from '@mui/material/MenuItem'
 import Menu from '@mui/material/Menu'
-import makeStyles from '@mui/styles/makeStyles'
 import MoreIcon from '@mui/icons-material/MoreVert'
 import Button from '@mui/material/Button'
+import Box from '@mui/material/Box'
+import { useTheme } from '@mui/material/styles'
 import { Link, NavLink } from 'react-router-dom'
 import TopBarSearchField from './TopBarSearchField'
 import TopBarInfoButton from './TopBarInfoButton'
@@ -18,113 +19,22 @@ import Divider from '@mui/material/Divider'
 import { has } from 'lodash'
 import secoLogo from '../../img/logos/seco-logo-48x50.png'
 
-const useStyles = makeStyles(theme => ({
-  grow: {
-    flexGrow: 1
-  },
-  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)
-  }),
-  sectionDesktop: props => ({
-    display: 'none',
-    [theme.breakpoints.up(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
-      display: 'flex'
-    }
-  }),
-  link: {
-    textDecoration: 'none'
-  },
-  sectionMobile: props => ({
-    display: 'flex',
-    [theme.breakpoints.up(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
-      display: 'none'
-    }
-  }),
-  appBarButton: {
-    whiteSpace: 'nowrap',
-    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'
-  },
-  secoLogo: props => ({
-    marginLeft: theme.spacing(1),
-    [theme.breakpoints.down(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
-      display: 'none'
-    }
-  }),
-  secoLogoImage: props => ({
-    height: 32,
-    [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
-      height: 50
-    }
-  }),
-  mainLogo: props => ({
-    height: props.layoutConfig.topBar.logoImageReducedHeight || 23,
-    [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
-      height: props.layoutConfig.topBar.logoImageHeight || 40
-    },
-    marginRight: theme.spacing(1)
-  }),
-  mainLogoButtonRoot: {
-    paddingLeft: 0,
-    [theme.breakpoints.down('sm')]: {
-      minWidth: 48
-    }
-  },
-  mainLogoButtonLabel: {
-    justifyContent: 'left'
-  },
-  mainLogoTypography: props => ({
-    // set color and background explicitly to keep Google Lighthouse happy
-    color: '#fff',
-    background: theme.palette.primary.main,
-    whiteSpace: 'nowrap',
-    textTransform: props.layoutConfig.topBar.logoTextTransform,
-    [theme.breakpoints.down('md')]: {
-      fontSize: '1.5rem'
-    },
-    ...(props.layoutConfig.topBar.hideLogoTextOnMobile && {
-      [theme.breakpoints.down('sm')]: {
-        display: 'none'
-      }
-    })
-  }),
-  logoSecondary: props => ({
-    height: 26,
-    [theme.breakpoints.up('sm')]: {
-      height: 32
-    },
-    [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
-      height: 52
-    }
-  }),
-  mobileMenuButton: {
-    padding: 12
-  }
-}))
-
 /**
  * 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 theme = useTheme()
+  // custom style function for utilizing React Router's isActive prop
+  const createAppBarButtonStyle = isActive => ({
+    whiteSpace: 'nowrap',
+    color: 'white !important',
+    border: isActive ? '1px solid white' : `1px solid ${theme.palette.primary.main}`
+  })
   const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null)
   const isMobileMenuOpen = Boolean(mobileMoreAnchorEl)
   const { perspectives, currentLocale, availableLocales, rootUrl, layoutConfig } = props
   const { topBar } = layoutConfig
-  const classes = useStyles(props)
   const handleMobileMenuOpen = event => setMobileMoreAnchorEl(event.currentTarget)
   const handleMobileMenuClose = () => setMobileMoreAnchorEl(null)
   const federatedSearchMode = props.location.pathname.indexOf('federated-search') !== -1
@@ -133,9 +43,11 @@ const TopBar = props => {
     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} />)
+  // https://mui.com/guides/routing/#button
+  const AdapterLink = React.forwardRef((props, ref) =>
+    <Link ref={ref} {...props} role={undefined} />)
+  const AdapterNavLink = React.forwardRef((props, ref) =>
+    <NavLink ref={ref} {...props} role={undefined} />)
 
   const getInternalLink = perspective => {
     const searchMode = has(perspective, 'searchMode') ? perspective.searchMode : 'faceted-search'
@@ -152,19 +64,22 @@ const TopBar = props => {
   const renderMobileMenuItem = perspective => {
     if (has(perspective, 'externalUrl')) {
       return (
-        <a
-          className={classes.link}
+        <Box
+          component='a'
           key={perspective.id}
           href={perspective.externalUrl}
           target='_blank'
           rel='noopener noreferrer'
+          sx={{
+            textDecoration: 'none'
+          }}
         >
           <MenuItem>
             {perspective.label
               ? perspective.label.toUpperCase()
               : intl.get(`perspectives.${perspective.id}.label`).toUpperCase()}
           </MenuItem>
-        </a>
+        </Box>
       )
     } else {
       return (
@@ -183,31 +98,33 @@ const TopBar = props => {
   const renderDesktopTopMenuItem = perspective => {
     if (has(perspective, 'externalUrl')) {
       return (
-        <a
-          className={classes.link}
+        <Box
+          component='a'
           key={perspective.id}
           href={perspective.externalUrl}
           target='_blank'
           rel='noopener noreferrer'
+          sx={{
+            textDecoration: 'none'
+          }}
         >
           <Button
-            className={classes.appBarButton}
+            sx={createAppBarButtonStyle(false)}
           >
             {perspective.label
               ? perspective.label
               : intl.get(`perspectives.${perspective.id}.label`).toUpperCase()}
           </Button>
-        </a>
+        </Box>
       )
     } else {
       return (
         <Button
           key={perspective.id}
-          className={classes.appBarButton}
           component={AdapterNavLink}
           to={getInternalLink(perspective)}
           isActive={(match, location) => location.pathname.startsWith(`${props.rootUrl}/${perspective.id}`)}
-          activeClassName={classes.appBarButtonActive}
+          style={isActive => createAppBarButtonStyle(isActive)}
         >
           {intl.get(`perspectives.${perspective.id}.label`).toUpperCase()}
         </Button>
@@ -219,17 +136,19 @@ const TopBar = props => {
     let jsx
     if (item.externalLink) {
       jsx = (
-        <a
-          className={classes.link}
+        <Box
           key={item.id}
           href={intl.get(`topBar.info.${item.translatedUrl}`)}
           target='_blank'
           rel='noopener noreferrer'
+          sx={{
+            textDecoration: 'none'
+          }}
         >
           <MenuItem onClick={handleMobileMenuClose}>
             {intl.get(`topBar.info.${item.translatedText}`).toUpperCase()}
           </MenuItem>
-        </a>
+        </Box>
       )
     } else {
       jsx = (
@@ -284,28 +203,56 @@ const TopBar = props => {
   }
 
   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 */}
-      <Typography />
+    <>
       <AppBar position='static'>
-        <Toolbar className={classes.topBarToolbar}>
+        <Toolbar
+          disableGutters
+          sx={theme => ({
+            paddingLeft: theme.spacing(1.5),
+            paddingRight: theme.spacing(1.5)
+          })}
+        >
           <Button
+            sx={theme => ({
+              paddingLeft: 0,
+              [theme.breakpoints.down('sm')]: {
+                minWidth: 48
+              }
+            })}
             component={AdapterLink} to='/'
-            classes={{
-              root: classes.mainLogoButtonRoot,
-              label: classes.mainLogoButtonLabel
-            }}
             onClick={() => federatedSearchMode ? props.clientFSClearResults() : null}
           >
             {topBar.logoImage &&
-              <img
-                className={classes.mainLogo}
+              <Box
+                component='img'
                 src={topBar.logoImage}
                 alt={`${intl.get('appTitle.short')} logo`}
+                sx={theme => ({
+                  height: props.layoutConfig.topBar.logoImageReducedHeight || 23,
+                  [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
+                    height: props.layoutConfig.topBar.logoImageHeight || 40
+                  },
+                  marginRight: theme.spacing(1)
+                })}
               />}
             {!topBar.hideLogoText &&
-              <Typography className={classes.mainLogoTypography} variant='h5'>
+              <Typography
+                sx={theme => ({
+                  color: '#fff',
+                  background: theme.palette.primary.main,
+                  whiteSpace: 'nowrap',
+                  textTransform: props.layoutConfig.topBar.logoTextTransform,
+                  [theme.breakpoints.down('md')]: {
+                    fontSize: '1.5rem'
+                  },
+                  ...(props.layoutConfig.topBar.hideLogoTextOnMobile && {
+                    [theme.breakpoints.down('sm')]: {
+                      display: 'none'
+                    }
+                  })
+                })}
+                variant='h5'
+              >
                 {props.xsScreen ? intl.get('appTitle.mobile') : intl.get('appTitle.short')}
               </Typography>}
           </Button>
@@ -316,7 +263,20 @@ const TopBar = props => {
               rel='noopener noreferrer'
             >
               <Button>
-                <img className={classes.logoSecondary} src={topBar.logoImageSecondary} alt='logoSecondary' />
+                <Box
+                  component='img'
+                  src={topBar.logoImageSecondary}
+                  alt='logoSecondary'
+                  sx={theme => ({
+                    height: 26,
+                    [theme.breakpoints.up('sm')]: {
+                      height: 32
+                    },
+                    [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
+                      height: 52
+                    }
+                  })}
+                />
               </Button>
             </a>}
           {showSearchField &&
@@ -326,10 +286,23 @@ const TopBar = props => {
               xsScreen={props.xsScreen}
               rootUrl={rootUrl}
             />}
-          <div className={classes.grow} />
-          <div className={classes.sectionDesktop}>
+          <Box sx={{ flexGrow: 1 }} />
+          <Box
+            sx={theme => ({
+              display: 'none',
+              [theme.breakpoints.up(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
+                display: 'flex'
+              }
+            })}
+          >
             {perspectives.map((perspective, index) => perspective.hideTopPerspectiveButton ? null : renderDesktopTopMenuItem(perspective, index))}
-            <div className={classes.appBarDivider} />
+            <Box
+              sx={theme => ({
+                marginLeft: theme.spacing(1),
+                marginRight: theme.spacing(1),
+                borderLeft: '2px solid white'
+              })}
+            />
             {renderDesktopTopMenuItem({
               id: 'feedback',
               externalUrl: props.layoutConfig.topBar.feedbackLink,
@@ -343,11 +316,10 @@ const TopBar = props => {
             })}
             {!topBar.externalInstructions &&
               <Button
-                className={classes.appBarButton}
                 component={AdapterNavLink}
                 to={`${props.rootUrl}/instructions`}
                 isActive={(match, location) => location.pathname.startsWith(`${props.rootUrl}/instructions`)}
-                activeClassName={classes.appBarButtonActive}
+                style={isActive => createAppBarButtonStyle(isActive)}
               >
                 {intl.get('topBar.instructions')}
               </Button>}
@@ -358,22 +330,41 @@ const TopBar = props => {
                 loadLocales={props.loadLocales}
                 location={props.location}
               />}
-          </div>
-          <a
-            className={classes.secoLogo}
+          </Box>
+          <Box
+            component='a'
             href='https://seco.cs.aalto.fi'
             target='_blank'
             rel='noopener noreferrer'
+            sx={theme => ({
+              marginLeft: theme.spacing(1),
+              [theme.breakpoints.down(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
+                display: 'none'
+              }
+            })}
           >
             <Button aria-label='link to Semantic Computing research group homepage'>
-              <img
-                className={classes.secoLogoImage}
+              <Box
+                component='img'
                 src={secoLogo}
                 alt='Semantic Computing research group logo'
+                sx={theme => ({
+                  height: 32,
+                  [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
+                    height: 50
+                  }
+                })}
               />
             </Button>
-          </a>
-          <div className={classes.sectionMobile}>
+          </Box>
+          <Box
+            sx={theme => ({
+              display: 'flex',
+              [theme.breakpoints.up(props.layoutConfig.topBar.mobileMenuBreakpoint)]: {
+                display: 'none'
+              }
+            })}
+          >
             {props.layoutConfig.topBar.showLanguageButton &&
               <TopBarLanguageButton
                 currentLocale={currentLocale}
@@ -384,17 +375,17 @@ const TopBar = props => {
             <IconButton
               aria-label='display more actions'
               color='inherit'
-              className={classes.mobileMenuButton}
+              edge='end'
               onClick={handleMobileMenuOpen}
               size='large'
             >
               <MoreIcon />
             </IconButton>
-          </div>
+          </Box>
         </Toolbar>
       </AppBar>
       {renderMobileMenu(perspectives)}
-    </div>
+    </>
   )
 }
 
diff --git a/src/client/components/main_layout/TopBarSearchField.js b/src/client/components/main_layout/TopBarSearchField.js
index 0c89fb47..0b6bd6a7 100644
--- a/src/client/components/main_layout/TopBarSearchField.js
+++ b/src/client/components/main_layout/TopBarSearchField.js
@@ -1,56 +1,12 @@
 import React from 'react'
 import PropTypes from 'prop-types'
 import intl from 'react-intl-universal'
-import { alpha } from '@mui/material/styles';
-import withStyles from '@mui/styles/withStyles';
+import { alpha } from '@mui/material/styles'
+import Box from '@mui/material/Box'
 import SearchIcon from '@mui/icons-material/Search'
 import InputBase from '@mui/material/InputBase'
-// import CircularProgress from '@mui/material/CircularProgress';
 import history from '../../History'
 
-const styles = theme => ({
-  search: {
-    position: 'relative',
-    borderRadius: theme.shape.borderRadius,
-    backgroundColor: alpha(theme.palette.common.white, 0.15),
-    '&:hover': {
-      backgroundColor: alpha(theme.palette.common.white, 0.25)
-    },
-    // marginRight: theme.spacing(3),
-    // marginLeft: theme.spacing(2.5),
-    width: '100%',
-    maxWidth: 300,
-    [theme.breakpoints.up('sm')]: {
-      marginLeft: theme.spacing(3),
-      width: 'auto'
-    }
-  },
-  searchIcon: {
-    width: theme.spacing(9),
-    height: '100%',
-    position: 'absolute',
-    pointerEvents: 'none',
-    display: 'flex',
-    alignItems: 'center',
-    justifyContent: 'center'
-  },
-  inputRoot: {
-    color: 'inherit',
-    width: '100%'
-  },
-  inputInput: {
-    paddingTop: theme.spacing(1),
-    paddingRight: theme.spacing(1),
-    paddingBottom: theme.spacing(1),
-    paddingLeft: theme.spacing(10),
-    transition: theme.transitions.create('width'),
-    width: '100%',
-    [theme.breakpoints.up('md')]: {
-      width: 200
-    }
-  }
-})
-
 /**
  * A search field that can be embedded into the TopBar.
  */
@@ -93,30 +49,66 @@ class TopBarSearchField extends React.Component {
   }
 
   render () {
-    const { classes, xsScreen } = this.props
-    const placeholder = xsScreen ? intl.get('topBar.searchBarPlaceHolderShort') : intl.get('topBar.searchBarPlaceHolder')
+    const { xsScreen } = this.props
+    const placeholder = xsScreen
+      ? intl.get('topBar.searchBarPlaceHolderShort')
+      : intl.get('topBar.searchBarPlaceHolder')
     return (
-      <div className={classes.search}>
-        <div className={classes.searchIcon}>
+      <Box
+        sx={theme => ({
+          position: 'relative',
+          borderRadius: theme.shape.borderRadius,
+          backgroundColor: alpha(theme.palette.common.white, 0.15),
+          '&:hover': {
+            backgroundColor: alpha(theme.palette.common.white, 0.25)
+          },
+          marginRight: theme.spacing(2),
+          marginLeft: 0,
+          width: '100%',
+          [theme.breakpoints.up('sm')]: {
+            marginLeft: theme.spacing(3),
+            width: 'auto'
+          }
+        })}
+      >
+        <Box
+          sx={theme => ({
+            padding: theme.spacing(0, 2),
+            height: '100%',
+            position: 'absolute',
+            pointerEvents: 'none',
+            display: 'flex',
+            alignItems: 'center',
+            justifyContent: 'center'
+          })}
+        >
           <SearchIcon />
-        </div>
+        </Box>
         <InputBase
           placeholder={placeholder}
-          classes={{
-            root: classes.inputRoot,
-            input: classes.inputInput
-          }}
           inputProps={{ 'aria-label': 'search' }}
           onChange={this.handleChange}
           onKeyDown={this.handleOnKeyDown}
+          sx={theme => ({
+            color: 'inherit',
+            '& .MuiInputBase-input': {
+              padding: theme.spacing(1, 1, 1, 0),
+              // vertical padding + font size from searchIcon
+              paddingLeft: `calc(1em + ${theme.spacing(4)})`,
+              transition: theme.transitions.create('width'),
+              width: '100%',
+              [theme.breakpoints.up('md')]: {
+                width: '20ch'
+              }
+            }
+          })}
         />
-      </div>
+      </Box>
     )
   }
 }
 
 TopBarSearchField.propTypes = {
-  classes: PropTypes.object.isRequired,
   fetchFullTextResults: PropTypes.func,
   xsScreen: PropTypes.bool.isRequired,
   rootUrl: PropTypes.string.isRequired
@@ -124,4 +116,4 @@ TopBarSearchField.propTypes = {
 
 export const TopBarSearchFieldComponent = TopBarSearchField
 
-export default withStyles(styles)(TopBarSearchField)
+export default TopBarSearchField
diff --git a/src/client/components/perspectives/sampo/Footer.js b/src/client/components/perspectives/sampo/Footer.js
index 6a301866..c400c34f 100644
--- a/src/client/components/perspectives/sampo/Footer.js
+++ b/src/client/components/perspectives/sampo/Footer.js
@@ -2,33 +2,13 @@ import React from 'react'
 import Paper from '@mui/material/Paper'
 import PropTypes from 'prop-types'
 import Grid from '@mui/material/Grid'
-import makeStyles from '@mui/styles/makeStyles';
+import makeStyles from '@mui/styles/makeStyles'
+import { getSpacing } from '../../../helpers/helpers'
 import aaltoLogo from '../../../img/logos/Aalto_SCI_EN_13_BLACK_2_cropped.png'
 import hyLogo from '../../../img/logos/university-of-helsinki-logo-transparent-black.png'
 import heldigLogo from '../../../img/logos/heldig-logo-transparent-black.png'
 
 const useStyles = makeStyles(theme => ({
-  root: {
-    display: 'block',
-    boxShadow: '0 -20px 20px -20px #333',
-    borderRadius: 0
-  },
-  layout: props => ({
-    [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
-      height: props.layoutConfig.footer.reducedHeight
-      // height: 115, for two row footer
-    },
-    [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
-      height: props.layoutConfig.footer.defaultHeight
-    },
-    marginLeft: theme.spacing(2),
-    marginRight: theme.spacing(2),
-    [theme.breakpoints.up(1500 + theme.spacing(6))]: {
-      width: 1500,
-      marginLeft: 'auto',
-      marginRight: 'auto'
-    }
-  }),
   gridContainer: {
     height: '100%',
     marginTop: 0,
@@ -85,26 +65,46 @@ const useStyles = makeStyles(theme => ({
 const Footer = props => {
   const classes = useStyles(props)
   return (
-    <Paper className={classes.root}>
-      <div className={classes.layout}>
-        <Grid className={classes.gridContainer} container spacing={3}>
-          <Grid item xs className={classes.gridItem}>
-            <a className={classes.link} href='https://www.aalto.fi/en/school-of-science' target='_blank' rel='noopener noreferrer'>
-              <img className={classes.aaltoLogo} src={aaltoLogo} alt='Aalto University logo' />
-            </a>
-          </Grid>
-          <Grid item xs className={classes.gridItem}>
-            <a className={classes.link} href='https://www.helsinki.fi/en' target='_blank' rel='noopener noreferrer'>
-              <img className={classes.hyLogo} src={hyLogo} alt='University of Helsinki logo' />
-            </a>
-          </Grid>
-          <Grid item xs className={classes.gridItem}>
-            <a className={classes.link} href='https://www.helsinki.fi/en/helsinki-centre-for-digital-humanities' target='_blank' rel='noopener noreferrer'>
-              <img className={classes.heldigLogo} src={heldigLogo} alt='Helsinki Centre for Digital Humanities logo' />
-            </a>
-          </Grid>
+    <Paper
+      sx={theme => ({
+        boxShadow: '0 -20px 20px -20px #333',
+        borderRadius: 0,
+        height: {
+          hundredPercentHeight: props.layoutConfig.footer.reducedHeight,
+          reducedHeight: props.layoutConfig.footer.defaultHeight
+        }
+      })}
+    >
+      <Grid
+        className={classes.gridContainer}
+        container spacing={3}
+        sx={theme => ({
+          height: '100%',
+          marginTop: 0,
+          marginBottom: 0,
+          [theme.breakpoints.up(1500 + getSpacing(theme, 6))]: {
+            width: 1500,
+            marginLeft: 'auto',
+            marginRight: 'auto'
+          }
+        })}
+      >
+        <Grid item xs className={classes.gridItem}>
+          <a className={classes.link} href='https://www.aalto.fi/en/school-of-science' target='_blank' rel='noopener noreferrer'>
+            <img className={classes.aaltoLogo} src={aaltoLogo} alt='Aalto University logo' />
+          </a>
+        </Grid>
+        <Grid item xs className={classes.gridItem}>
+          <a className={classes.link} href='https://www.helsinki.fi/en' target='_blank' rel='noopener noreferrer'>
+            <img className={classes.hyLogo} src={hyLogo} alt='University of Helsinki logo' />
+          </a>
+        </Grid>
+        <Grid item xs className={classes.gridItem}>
+          <a className={classes.link} href='https://www.helsinki.fi/en/helsinki-centre-for-digital-humanities' target='_blank' rel='noopener noreferrer'>
+            <img className={classes.heldigLogo} src={heldigLogo} alt='Helsinki Centre for Digital Humanities logo' />
+          </a>
         </Grid>
-      </div>
+      </Grid>
     </Paper>
   )
 }
diff --git a/src/client/components/perspectives/sampo/Main.js b/src/client/components/perspectives/sampo/Main.js
index 62df9eea..56db65f0 100644
--- a/src/client/components/perspectives/sampo/Main.js
+++ b/src/client/components/perspectives/sampo/Main.js
@@ -49,19 +49,18 @@ const Main = props => {
       sx={theme => {
         const { layoutConfig } = props
         const defaultHeightReduction = layoutConfig.topBar.defaultHeight +
-          layoutConfig.footer.defaultHeight + getSpacing(theme, 1)
+          layoutConfig.footer.defaultHeight + getSpacing(theme, 2)
         const reducedHeightReduction = layoutConfig.topBar.reducedHeight +
-          layoutConfig.footer.reducedHeight + getSpacing(theme, 1)
+          layoutConfig.footer.reducedHeight + getSpacing(theme, 2)
         return {
-          [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
-            overflow: 'auto',
-            height: `calc(100% - ${reducedHeightReduction}px)`
+          paddingBottom: theme.spacing(2),
+          height: {
+            hundredPercentHeight: `calc(100% - ${reducedHeightReduction}px)`,
+            reducedHeight: `calc(100% - ${defaultHeightReduction}px)`
           },
-          [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
-            overflow: 'auto',
-            height: `calc(100% - ${defaultHeightReduction}px)`
-          },
-          marginBottom: theme.spacing(1)
+          overflow: {
+            hundredPercentHeight: 'auto'
+          }
         }
       }}
     >
diff --git a/src/client/containers/SemanticPortal.js b/src/client/containers/SemanticPortal.js
index 7b24a5d1..069ab294 100644
--- a/src/client/containers/SemanticPortal.js
+++ b/src/client/containers/SemanticPortal.js
@@ -5,7 +5,6 @@ import { has } from 'lodash'
 import { connect } from 'react-redux'
 import { makeStyles } from '@material-ui/core/styles'
 import { withRouter, Route, Redirect, Switch } from 'react-router-dom'
-import classNames from 'classnames'
 import { compose } from '@shakacode/recompose'
 import { useTheme } from '@mui/material/styles'
 import useMediaQuery from '@mui/material/useMediaQuery'
@@ -114,16 +113,6 @@ const useStyles = makeStyles(theme => ({
       height: `calc(100% - ${layoutConfig.topBar.defaultHeight + layoutConfig.footer.defaultHeight + theme.spacing(1)}px)`
     }
   },
-  textPageContainer: {
-    margin: theme.spacing(0.5),
-    width: `calc(100% - ${theme.spacing(1)}px)`,
-    [theme.breakpoints.up(layoutConfig.hundredPercentHeightBreakPoint)]: {
-      height: `calc(100% - ${layoutConfig.topBar.reducedHeight + theme.spacing(1.5)}px)`
-    },
-    [theme.breakpoints.up(layoutConfig.reducedHeightBreakpoint)]: {
-      height: `calc(100% - ${layoutConfig.topBar.defaultHeight + theme.spacing(1.5)}px)`
-    }
-  },
   perspectiveContainer: {
     margin: theme.spacing(0.5),
     width: `calc(100% - ${theme.spacing(1)}px)`,
@@ -244,7 +233,7 @@ const SemanticPortal = props => {
   const { error } = props
   const theme = useTheme()
   const classes = useStyles(props)
-  const xsScreen = useMediaQuery(theme.breakpoints.down('xs'))
+  const xsScreen = useMediaQuery(theme.breakpoints.down('sm'))
   const smScreen = useMediaQuery(theme.breakpoints.between('sm', 'md'))
   const mdScreen = useMediaQuery(theme.breakpoints.between('md', 'lg'))
   const lgScreen = useMediaQuery(theme.breakpoints.between('lg', 'xl'))
@@ -626,31 +615,29 @@ const SemanticPortal = props => {
           <Route
             path={`${rootUrlWithLang}/about`}
             render={() =>
-              <div className={classNames(classes.mainContainer, classes.textPageContainer)}>
-                <TextPage>
-                  {intl.getHTML('aboutThePortalPartOne')}
-                  {knowledgeGraphMetadataConfig.showTable &&
-                    <KnowledgeGraphMetadataTable
-                      portalConfig={portalConfig}
-                      layoutConfig={layoutConfig}
-                      perspectiveID={knowledgeGraphMetadataConfig.perspective}
-                      resultClass='knowledgeGraphMetadata'
-                      fetchKnowledgeGraphMetadata={props.fetchKnowledgeGraphMetadata}
-                      knowledgeGraphMetadata={props[knowledgeGraphMetadataConfig.perspective]
-                        ? props[knowledgeGraphMetadataConfig.perspective].knowledgeGraphMetadata
-                        : null}
-                    />}
-                  {intl.getHTML('aboutThePortalPartTwo')}
-                </TextPage>
-              </div>}
+              <TextPage layoutConfig={layoutConfig}>
+                {intl.getHTML('aboutThePortalPartOne')}
+                {knowledgeGraphMetadataConfig.showTable &&
+                  <KnowledgeGraphMetadataTable
+                    portalConfig={portalConfig}
+                    layoutConfig={layoutConfig}
+                    perspectiveID={knowledgeGraphMetadataConfig.perspective}
+                    resultClass='knowledgeGraphMetadata'
+                    fetchKnowledgeGraphMetadata={props.fetchKnowledgeGraphMetadata}
+                    knowledgeGraphMetadata={props[knowledgeGraphMetadataConfig.perspective]
+                      ? props[knowledgeGraphMetadataConfig.perspective].knowledgeGraphMetadata
+                      : null}
+                  />}
+                {intl.getHTML('aboutThePortalPartTwo')}
+              </TextPage>}
           />}
         {!layoutConfig.topBar.externalInstructions &&
           <Route
             path={`${rootUrlWithLang}/instructions`}
             render={() =>
-              <div className={classNames(classes.mainContainer, classes.textPageContainer)}>
-                <TextPage>{intl.getHTML('instructions')}</TextPage>
-              </div>}
+              <TextPage layoutConfig={layoutConfig}>
+                {intl.getHTML('instructions')}
+              </TextPage>}
           />}
       </>
     </div>
diff --git a/src/client/img/logos/logo.png b/src/client/img/logos/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..1738c87bdb9b30662d587927cc0e822d5bac06a7
GIT binary patch
literal 3694
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

literal 0
HcmV?d00001

diff --git a/src/configs/portalConfig.json b/src/configs/portalConfig.json
index fe3e88ce..bbc7c36f 100644
--- a/src/configs/portalConfig.json
+++ b/src/configs/portalConfig.json
@@ -62,11 +62,14 @@
         "paginationToolbarHeight": 37,
         "tableFontSize": "0.8rem",
         "topBar": {
+            "logoImage": "logos/logo.png",
+            "logoTextTransform": "none",
+            "hideLogoTextOnMobile": true,
             "showLanguageButton": true,
             "feedbackLink": "https://link.webropolsurveys.com/...",
             "externalInstructions": false,
             "externalAboutPage": false,
-            "reducedHeight": 44,
+            "reducedHeight": 48,
             "defaultHeight": 64,
             "mobileMenuBreakpoint": 1360,
             "infoDropdown": [
-- 
GitLab