Merge pull request #12 from m-tartari/refactor/home/mobile
♻️ refactor: make mobile friendly homepage
@@ -16,3 +16,10 @@ updates:
|
|||||||
patterns:
|
patterns:
|
||||||
- 'vitest'
|
- 'vitest'
|
||||||
- '@vitest*'
|
- '@vitest*'
|
||||||
|
typescript-eslint:
|
||||||
|
patterns:
|
||||||
|
- '@typescript-eslint*'
|
||||||
|
mui:
|
||||||
|
patterns:
|
||||||
|
- '@mui/material'
|
||||||
|
- '@mui/icon-material'
|
||||||
|
|||||||
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 760 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 165 KiB |
@@ -13,7 +13,7 @@ const Hero = (props: { scrollTarget?: React.ForwardedRef<HTMLSelectElement> }) =
|
|||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
boxShadow: '0 0.25em 0.5em 0 rgba(0, 0, 0, 0.25)',
|
boxShadow: '0 0.25em 0.5em 0 rgba(0, 0, 0, 0.25)',
|
||||||
|
|
||||||
backgroundImage: 'url("images/banner-blur.jpg")',
|
backgroundImage: 'url("images/banner-blur.webp")',
|
||||||
backgroundPosition: 'center center',
|
backgroundPosition: 'center center',
|
||||||
backgroundAttachment: 'fixed',
|
backgroundAttachment: 'fixed',
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import { ParallaxBanner, ParallaxProvider } from 'react-scroll-parallax'
|
import { ParallaxBanner, ParallaxProvider } from 'react-scroll-parallax'
|
||||||
import { Box, Grid, IconButton, Typography, useTheme } from '@mui/material'
|
import { Box, Grid, IconButton, Typography, useTheme } from '@mui/material'
|
||||||
import { ExpandMore } from '@mui/icons-material'
|
import { ExpandMore } from '@mui/icons-material'
|
||||||
|
import { useViewport } from '../../utils/viewport'
|
||||||
|
|
||||||
type AnchorType = 'left' | 'top' | 'right' | 'bottom'
|
type AnchorType = 'left' | 'top' | 'right' | 'bottom'
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ type Props = {
|
|||||||
title: string
|
title: string
|
||||||
caption: React.ReactNode
|
caption: React.ReactNode
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
mobileImage?: string
|
||||||
ref?: React.ForwardedRef<HTMLSelectElement>
|
ref?: React.ForwardedRef<HTMLSelectElement>
|
||||||
anchor?: AnchorType
|
anchor?: AnchorType
|
||||||
scrollTarget?: React.ForwardedRef<HTMLSelectElement>
|
scrollTarget?: React.ForwardedRef<HTMLSelectElement>
|
||||||
@@ -124,7 +126,7 @@ const Description = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectSpotlight = React.forwardRef<HTMLSelectElement, Props>((props, ref) => {
|
const DesktopComponent = React.forwardRef<HTMLSelectElement, Props>((props, ref) => {
|
||||||
return (
|
return (
|
||||||
<section ref={ref}>
|
<section ref={ref}>
|
||||||
<ParallaxProvider>
|
<ParallaxProvider>
|
||||||
@@ -163,4 +165,51 @@ const ProjectSpotlight = React.forwardRef<HTMLSelectElement, Props>((props, ref)
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const MobileComponent = React.forwardRef<HTMLSelectElement, Props>((props, ref) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
ref={ref}
|
||||||
|
component="img"
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
maxHeight: '60vh',
|
||||||
|
}}
|
||||||
|
src={props.mobileImage ?? props.image}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
|
||||||
|
borderTopStyle: 'solid',
|
||||||
|
borderTopWidth: theme.spacing(0.5),
|
||||||
|
borderTopColor: theme.palette.primary.main,
|
||||||
|
p: `${theme.spacing(7)} ${theme.spacing(5)} ${theme.spacing(5.5)} ${theme.spacing(5)}`,
|
||||||
|
}}>
|
||||||
|
<Grid container spacing={2} maxWidth="xl">
|
||||||
|
<Grid item xs={12} md={4}>
|
||||||
|
<Typography variant="h4">{props.title}</Typography>
|
||||||
|
{props.caption}
|
||||||
|
</Grid>
|
||||||
|
<Grid item component={Typography} xs={12} md={8}>
|
||||||
|
{props.children}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const ProjectSpotlight = React.forwardRef<HTMLSelectElement, Props>((props, ref) => {
|
||||||
|
const { width } = useViewport()
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
return width < theme.breakpoints.values.md ? <MobileComponent {...props} ref={ref} /> : <DesktopComponent {...props} ref={ref} />
|
||||||
|
})
|
||||||
|
|
||||||
export default ProjectSpotlight
|
export default ProjectSpotlight
|
||||||
|
|||||||
@@ -5,9 +5,23 @@ import { renderWithRouter as render } from 'components/utils/tests'
|
|||||||
import MainPage from '.'
|
import MainPage from '.'
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
describe('Main page', async () => {
|
describe('Main page', () => {
|
||||||
it('Should render the page correctly', async () => {
|
it('Should render the page correctly on desktop', async () => {
|
||||||
// Setup
|
await render(<MainPage />)
|
||||||
|
|
||||||
|
// Make sure the page title is correct
|
||||||
|
expect(screen.getByText('Robotics Engineer')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Make sure the project spotlights are present
|
||||||
|
expect(screen.getByText('LINFA: An Intuitive Block-Programming Interface for Reconfigurable Robots')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText("Revamping HHCM's Modular: Swift Generation of Modular Robot Models")).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Automated Actuators and Robotic Modules Calibration')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Voice Controlled Mobile Robot for Indoor Applications')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
it('Should render the page correctly on mobile', async () => {
|
||||||
|
// Set the window size to mobile (Galaxy Note 20 Android 11)
|
||||||
|
window.innerWidth = 412
|
||||||
|
window.innerHeight = 915
|
||||||
await render(<MainPage />)
|
await render(<MainPage />)
|
||||||
|
|
||||||
// Make sure the page title is correct
|
// Make sure the page title is correct
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Box, Typography } from '@mui/material'
|
import { Box, Typography } from '@mui/material'
|
||||||
|
|
||||||
|
import { ViewportProvider } from 'components/utils/viewport'
|
||||||
import { AcademyIconLink, DescriptionIconLink, GitHubIconLink, VideoIconLink } from 'components/atoms'
|
import { AcademyIconLink, DescriptionIconLink, GitHubIconLink, VideoIconLink } from 'components/atoms'
|
||||||
import { Footer, Hero, Toobar, ProjectSpotlight } from 'components/molecules'
|
import { Footer, Hero, Toobar, ProjectSpotlight } from 'components/molecules'
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ const MainPage = () => {
|
|||||||
const refWUTThesis = React.useRef<HTMLSelectElement>(null)
|
const refWUTThesis = React.useRef<HTMLSelectElement>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ViewportProvider>
|
||||||
<Box aria-label="page" sx={{ flexGrow: 1 }}>
|
<Box aria-label="page" sx={{ flexGrow: 1 }}>
|
||||||
<Toobar position="absolute" sx={{ backgroundColor: 'transparent' }} />
|
<Toobar position="absolute" sx={{ backgroundColor: 'transparent' }} />
|
||||||
<Hero scrollTarget={refIITLinfa} />
|
<Hero scrollTarget={refIITLinfa} />
|
||||||
@@ -23,7 +25,7 @@ const MainPage = () => {
|
|||||||
ref={refIITLinfa}
|
ref={refIITLinfa}
|
||||||
scrollTarget={refIITModular}
|
scrollTarget={refIITModular}
|
||||||
anchor="bottom"
|
anchor="bottom"
|
||||||
image="images/IIT_Linfa.png"
|
image="images/IIT_Linfa.webp"
|
||||||
title="LINFA: An Intuitive Block-Programming Interface for Reconfigurable Robots"
|
title="LINFA: An Intuitive Block-Programming Interface for Reconfigurable Robots"
|
||||||
caption={
|
caption={
|
||||||
<>
|
<>
|
||||||
@@ -47,6 +49,7 @@ const MainPage = () => {
|
|||||||
anchor="right"
|
anchor="right"
|
||||||
// anchor="left"
|
// anchor="left"
|
||||||
image="images/IIT_Modular.png"
|
image="images/IIT_Modular.png"
|
||||||
|
mobileImage="images/IIT_Modular_Mobile.webp"
|
||||||
title="Revamping HHCM's Modular: Swift Generation of Modular Robot Models"
|
title="Revamping HHCM's Modular: Swift Generation of Modular Robot Models"
|
||||||
caption={
|
caption={
|
||||||
<>
|
<>
|
||||||
@@ -59,15 +62,16 @@ const MainPage = () => {
|
|||||||
This project focused on overhauling HHCM's Modular, enhancing its graphical interface, API, and deployment pipeline. Modular serves as a web
|
This project focused on overhauling HHCM's Modular, enhancing its graphical interface, API, and deployment pipeline. Modular serves as a web
|
||||||
application for the swift generation of modular robot models, with an offline approach (Building Mode) enabling users to set up robots in a
|
application for the swift generation of modular robot models, with an offline approach (Building Mode) enabling users to set up robots in a
|
||||||
virtual environment by selecting and assembling reconfigurable modules, immediately visualized in 3D. Additionally, an online approach
|
virtual environment by selecting and assembling reconfigurable modules, immediately visualized in 3D. Additionally, an online approach
|
||||||
(Discovery Mode) allows users to query the EtherCAT master for network topology, recognizing and displaying robots built from physical modules
|
(Discovery Mode) allows users to query the EtherCAT master for network topology, recognizing and displaying robots built from physical
|
||||||
in 3D. The system seamlessly produces URDF, SRDF, and a full ROS package that can then be utilized for simulating and controlling the robot.
|
modules in 3D. The system seamlessly produces URDF, SRDF, and a full ROS package that can then be utilized for simulating and controlling
|
||||||
|
the robot.
|
||||||
</ProjectSpotlight>
|
</ProjectSpotlight>
|
||||||
{/* 3 IIT - Motor Calibration */}
|
{/* 3 IIT - Motor Calibration */}
|
||||||
<ProjectSpotlight
|
<ProjectSpotlight
|
||||||
ref={refIITCalibration}
|
ref={refIITCalibration}
|
||||||
scrollTarget={refWUTThesis}
|
scrollTarget={refWUTThesis}
|
||||||
anchor="left"
|
anchor="left"
|
||||||
image="images/IIT_Modules.jpg"
|
image="images/IIT_Modules.webp"
|
||||||
title="Automated Actuators and Robotic Modules Calibration"
|
title="Automated Actuators and Robotic Modules Calibration"
|
||||||
caption={
|
caption={
|
||||||
<>
|
<>
|
||||||
@@ -77,8 +81,8 @@ const MainPage = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
This project focused on automating the calibration of EtherCAT actuators and robotic modules, aiming for a fully automated procedure for newly
|
This project focused on automating the calibration of EtherCAT actuators and robotic modules, aiming for a fully automated procedure for
|
||||||
assembled actuators. The process involved testing the PDO, conducting phase angle calibration, torque sensor calibration, ripple and
|
newly assembled actuators. The process involved testing the PDO, conducting phase angle calibration, torque sensor calibration, ripple and
|
||||||
position-dependent torque offset calibration, inertia and friction identification, and frequency response calibration. The goal was to
|
position-dependent torque offset calibration, inertia and friction identification, and frequency response calibration. The goal was to
|
||||||
optimize motor performance and collect necessary data for accurate characterization, with results stored centrally and presented in a
|
optimize motor performance and collect necessary data for accurate characterization, with results stored centrally and presented in a
|
||||||
comprehensive PDF report.
|
comprehensive PDF report.
|
||||||
@@ -88,6 +92,7 @@ const MainPage = () => {
|
|||||||
ref={refWUTThesis}
|
ref={refWUTThesis}
|
||||||
anchor="bottom"
|
anchor="bottom"
|
||||||
image="images/thesis.jpg"
|
image="images/thesis.jpg"
|
||||||
|
mobileImage="images/thesis.webp"
|
||||||
title="Voice Controlled Mobile Robot for Indoor Applications"
|
title="Voice Controlled Mobile Robot for Indoor Applications"
|
||||||
caption={
|
caption={
|
||||||
<>
|
<>
|
||||||
@@ -103,14 +108,15 @@ const MainPage = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
}>
|
}>
|
||||||
This thesis proposes a design for a small mobile robot controlled by voice. Firstly a preliminary analysis of the state of art of human-robot
|
This thesis proposes a design for a small mobile robot controlled by voice. Firstly a preliminary analysis of the state of art of
|
||||||
interaction has been conducted. Then, an analysis of the equipment selected took place, and, later, appropriate methodologies for voice
|
human-robot interaction has been conducted. Then, an analysis of the equipment selected took place, and, later, appropriate methodologies
|
||||||
recognition and user detection have been evaluated for this specific case. Finally, an overview of the complete software architecture of the
|
for voice recognition and user detection have been evaluated for this specific case. Finally, an overview of the complete software
|
||||||
system was given with three possible structures and their comparison. The results obtained show both the feasibility of using low power boards
|
architecture of the system was given with three possible structures and their comparison. The results obtained show both the feasibility of
|
||||||
and some of their limitations.
|
using low power boards and some of their limitations.
|
||||||
</ProjectSpotlight>
|
</ProjectSpotlight>
|
||||||
<Footer />
|
<Footer />
|
||||||
</Box>
|
</Box>
|
||||||
|
</ViewportProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import viewportContext from './viewportContext'
|
||||||
|
|
||||||
|
const ViewportProvider = (props: { children: React.ReactNode }) => {
|
||||||
|
const [width, setWidth] = React.useState(window.innerWidth)
|
||||||
|
const [height, setHeight] = React.useState(window.innerHeight)
|
||||||
|
const handleWindowResize = () => {
|
||||||
|
setWidth(window.innerWidth)
|
||||||
|
setHeight(window.innerHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
window.addEventListener('resize', handleWindowResize)
|
||||||
|
return () => window.removeEventListener('resize', handleWindowResize)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <viewportContext.Provider value={{ width, height }}>{props.children}</viewportContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewportProvider
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as viewportContext } from './viewportContext'
|
||||||
|
export { default as ViewportProvider } from './ViewportProvider'
|
||||||
|
export { default as useViewport } from './useViewport'
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import viewportContext from './viewportContext'
|
||||||
|
|
||||||
|
const useViewport = () => {
|
||||||
|
const { width, height } = React.useContext(viewportContext)
|
||||||
|
return { width, height }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useViewport
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Viewport = { width: number; height: number }
|
||||||
|
const viewportContext = React.createContext<Viewport>({ width: window.innerWidth, height: window.innerHeight })
|
||||||
|
|
||||||
|
export default viewportContext
|
||||||