Merge pull request #12 from m-tartari/refactor/home/mobile
♻️ refactor: make mobile friendly homepage
@@ -16,3 +16,10 @@ updates:
|
||||
patterns:
|
||||
- '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',
|
||||
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',
|
||||
backgroundAttachment: 'fixed',
|
||||
backgroundSize: 'cover',
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react'
|
||||
import { ParallaxBanner, ParallaxProvider } from 'react-scroll-parallax'
|
||||
import { Box, Grid, IconButton, Typography, useTheme } from '@mui/material'
|
||||
import { ExpandMore } from '@mui/icons-material'
|
||||
import { useViewport } from '../../utils/viewport'
|
||||
|
||||
type AnchorType = 'left' | 'top' | 'right' | 'bottom'
|
||||
|
||||
@@ -10,6 +11,7 @@ type Props = {
|
||||
title: string
|
||||
caption: React.ReactNode
|
||||
children: React.ReactNode
|
||||
mobileImage?: string
|
||||
ref?: React.ForwardedRef<HTMLSelectElement>
|
||||
anchor?: AnchorType
|
||||
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 (
|
||||
<section ref={ref}>
|
||||
<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
|
||||
|
||||
@@ -5,9 +5,23 @@ import { renderWithRouter as render } from 'components/utils/tests'
|
||||
import MainPage from '.'
|
||||
|
||||
// Tests
|
||||
describe('Main page', async () => {
|
||||
it('Should render the page correctly', async () => {
|
||||
// Setup
|
||||
describe('Main page', () => {
|
||||
it('Should render the page correctly on desktop', async () => {
|
||||
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 />)
|
||||
|
||||
// Make sure the page title is correct
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
|
||||
import { ViewportProvider } from 'components/utils/viewport'
|
||||
import { AcademyIconLink, DescriptionIconLink, GitHubIconLink, VideoIconLink } from 'components/atoms'
|
||||
import { Footer, Hero, Toobar, ProjectSpotlight } from 'components/molecules'
|
||||
|
||||
@@ -15,6 +16,7 @@ const MainPage = () => {
|
||||
const refWUTThesis = React.useRef<HTMLSelectElement>(null)
|
||||
|
||||
return (
|
||||
<ViewportProvider>
|
||||
<Box aria-label="page" sx={{ flexGrow: 1 }}>
|
||||
<Toobar position="absolute" sx={{ backgroundColor: 'transparent' }} />
|
||||
<Hero scrollTarget={refIITLinfa} />
|
||||
@@ -23,7 +25,7 @@ const MainPage = () => {
|
||||
ref={refIITLinfa}
|
||||
scrollTarget={refIITModular}
|
||||
anchor="bottom"
|
||||
image="images/IIT_Linfa.png"
|
||||
image="images/IIT_Linfa.webp"
|
||||
title="LINFA: An Intuitive Block-Programming Interface for Reconfigurable Robots"
|
||||
caption={
|
||||
<>
|
||||
@@ -47,6 +49,7 @@ const MainPage = () => {
|
||||
anchor="right"
|
||||
// anchor="left"
|
||||
image="images/IIT_Modular.png"
|
||||
mobileImage="images/IIT_Modular_Mobile.webp"
|
||||
title="Revamping HHCM's Modular: Swift Generation of Modular Robot Models"
|
||||
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
|
||||
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
|
||||
(Discovery Mode) allows users to query the EtherCAT master for network topology, recognizing and displaying robots built from physical 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.
|
||||
(Discovery Mode) allows users to query the EtherCAT master for network topology, recognizing and displaying robots built from physical
|
||||
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>
|
||||
{/* 3 IIT - Motor Calibration */}
|
||||
<ProjectSpotlight
|
||||
ref={refIITCalibration}
|
||||
scrollTarget={refWUTThesis}
|
||||
anchor="left"
|
||||
image="images/IIT_Modules.jpg"
|
||||
image="images/IIT_Modules.webp"
|
||||
title="Automated Actuators and Robotic Modules Calibration"
|
||||
caption={
|
||||
<>
|
||||
@@ -77,8 +81,8 @@ const MainPage = () => {
|
||||
</Typography>
|
||||
</>
|
||||
}>
|
||||
This project focused on automating the calibration of EtherCAT actuators and robotic modules, aiming for a fully automated procedure for newly
|
||||
assembled actuators. The process involved testing the PDO, conducting phase angle calibration, torque sensor calibration, ripple and
|
||||
This project focused on automating the calibration of EtherCAT actuators and robotic modules, aiming for a fully automated procedure for
|
||||
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
|
||||
optimize motor performance and collect necessary data for accurate characterization, with results stored centrally and presented in a
|
||||
comprehensive PDF report.
|
||||
@@ -88,6 +92,7 @@ const MainPage = () => {
|
||||
ref={refWUTThesis}
|
||||
anchor="bottom"
|
||||
image="images/thesis.jpg"
|
||||
mobileImage="images/thesis.webp"
|
||||
title="Voice Controlled Mobile Robot for Indoor Applications"
|
||||
caption={
|
||||
<>
|
||||
@@ -103,14 +108,15 @@ const MainPage = () => {
|
||||
</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
|
||||
interaction has been conducted. Then, an analysis of the equipment selected took place, and, later, appropriate methodologies for voice
|
||||
recognition and user detection have been evaluated for this specific case. Finally, an overview of the complete software architecture of the
|
||||
system was given with three possible structures and their comparison. The results obtained show both the feasibility of using low power boards
|
||||
and some of their limitations.
|
||||
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 interaction has been conducted. Then, an analysis of the equipment selected took place, and, later, appropriate methodologies
|
||||
for voice recognition and user detection have been evaluated for this specific case. Finally, an overview of the complete software
|
||||
architecture of the system was given with three possible structures and their comparison. The results obtained show both the feasibility of
|
||||
using low power boards and some of their limitations.
|
||||
</ProjectSpotlight>
|
||||
<Footer />
|
||||
</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
|
||||