Merge pull request #476 from m-tartari/fix/external_links

🐛 fix: update dead links to external resources
This commit is contained in:
2025-05-23 16:27:01 +02:00
committed by GitHub
19 changed files with 266 additions and 298 deletions
@@ -1,9 +0,0 @@
import { IconButton as MuiIconButton, styled } from '@mui/material'
const IconButton = styled(MuiIconButton)(({ theme }) => ({
':hover': {
backgroundColor: 'transparent',
color: theme.palette.primary.main,
},
}))
export default IconButton
-1
View File
@@ -1 +0,0 @@
export { default } from './IconButton'
@@ -1,9 +1,8 @@
// IconLinks.test.tsx // IconLinks.test.tsx
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as IconLinks from '.' import * as IconLinks from './index.js'
const labels: { [key: string]: string } = { const labels: { [key: string]: string } = {
DescriptionIconLink: 'Doc-button', DescriptionIconLink: 'Doc-button',
@@ -18,8 +17,6 @@ const labels: { [key: string]: string } = {
vi.stubGlobal('open', vi.fn()) vi.stubGlobal('open', vi.fn())
describe('IconLinks', () => { describe('IconLinks', () => {
const open = vi.spyOn(window, 'open')
const defaultProps = { const defaultProps = {
href: 'https://test.com', href: 'https://test.com',
fontSize: 'large' as const, fontSize: 'large' as const,
@@ -28,20 +25,10 @@ describe('IconLinks', () => {
describe.each(Object.entries(IconLinks))('%s', (name, Icon) => { describe.each(Object.entries(IconLinks))('%s', (name, Icon) => {
test('renders correctly and without crashing', async () => { test('renders correctly and without crashing', async () => {
render(<Icon {...defaultProps} />) render(<Icon {...defaultProps} />)
await userEvent.click(screen.getByLabelText(labels[name])) const link = screen.getByLabelText(labels[name])
expect(open).toHaveBeenCalledTimes(1) expect(link).toHaveAttribute('href', defaultProps.href)
expect(open).toHaveBeenLastCalledWith(defaultProps.href, '_blank') expect(link).toHaveAttribute('target', '_blank')
}) expect(link).toHaveAttribute('rel', 'noopener noreferrer')
test('stops propagation when the icon button is clicked', async () => {
const onClick = vi.fn()
render(
<div onClick={onClick}>
<Icon {...defaultProps} />
</div>
)
await userEvent.click(screen.getByLabelText(labels[name]))
expect(onClick).not.toHaveBeenCalled()
}) })
test("renders the badge when 'index' is provided", () => { test("renders the badge when 'index' is provided", () => {
-1
View File
@@ -1 +0,0 @@
export * from './IconLinks'
@@ -1,10 +1,7 @@
import { Email, LinkedIn, GitHub, Description, OndemandVideo, School, OpenInNew, OpenInBrowser } from '@mui/icons-material' import { Email, LinkedIn, GitHub, Description, OndemandVideo, School, OpenInNew, OpenInBrowser } from '@mui/icons-material'
import { Badge, IconButtonProps, SvgIcon } from '@mui/material' import { Badge, Link, LinkProps, SvgIcon } from '@mui/material'
import IconButton from '../IconButton' interface IconLinkProps extends LinkProps {
interface IconLinkProps extends IconButtonProps {
href: string
fontSize?: 'small' | 'large' | 'medium' | 'inherit' fontSize?: 'small' | 'large' | 'medium' | 'inherit'
index?: number index?: number
} }
@@ -13,25 +10,46 @@ interface IconWrapperProps extends IconLinkProps {
Icon: typeof SvgIcon Icon: typeof SvgIcon
} }
const IconWrapper = ({ index, href: to, fontSize, Icon, ...props }: IconWrapperProps) => { const IconWrapper: React.FC<IconWrapperProps> = ({ index, fontSize, Icon, sx, ...props }) => {
if (typeof index === 'undefined') { if (typeof index === 'undefined') {
return ( return (
<IconButton <Link
onClick={e => { color="inherit"
e.stopPropagation() rel="noopener noreferrer"
window.open(to, '_blank') target="_blank"
}} underline="hover"
sx={theme => ({
color: 'inherit',
':hover': {
backgroundColor: 'transparent',
color: theme.palette.primary.main,
},
...sx,
})}
{...props}> {...props}>
<Icon fontSize={fontSize} /> <Icon fontSize={fontSize} />
</IconButton> </Link>
) )
} }
return ( return (
<IconButton onClick={() => window.open(to, '_blank')} {...props}> <Link
color="inherit"
rel="noopener noreferrer"
target="_blank"
underline="hover"
sx={theme => ({
color: 'inherit',
':hover': {
backgroundColor: 'transparent',
color: theme.palette.primary.main,
},
...sx,
})}
{...props}>
<Badge badgeContent={index}> <Badge badgeContent={index}>
<Icon fontSize={fontSize} /> <Icon fontSize={fontSize} />
</Badge> </Badge>
</IconButton> </Link>
) )
} }
@@ -1,11 +1,9 @@
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Box } from '@mui/material' import { Box } from '@mui/material'
import Publication from '.' import Publication from './index.js'
vi.stubGlobal('open', vi.fn()) vi.stubGlobal('open', vi.fn())
const open = vi.spyOn(window, 'open')
describe('Publication component', () => { describe('Publication component', () => {
const defaultProps = { const defaultProps = {
@@ -13,10 +11,10 @@ describe('Publication component', () => {
authors: 'Test Author', authors: 'Test Author',
title: 'Test Title', title: 'Test Title',
location: 'Test Location', location: 'Test Location',
link: 'https://test.com', href: 'https://test.com',
} }
test('renders without crashing and displays the correct year, authors, title, and location', () => { test('renders without crashing and displays the correct year, authors, title, and location', () => {
render(<Publication {...defaultProps} link={undefined} />) render(<Publication {...defaultProps} href={undefined} />)
expect(screen.getByText(defaultProps.year)).toBeInTheDocument() expect(screen.getByText(defaultProps.year)).toBeInTheDocument()
expect(screen.getByText(defaultProps.title)).toBeInTheDocument() expect(screen.getByText(defaultProps.title)).toBeInTheDocument()
expect(screen.getByText(defaultProps.authors, { exact: false })).toBeInTheDocument() expect(screen.getByText(defaultProps.authors, { exact: false })).toBeInTheDocument()
@@ -26,8 +24,10 @@ describe('Publication component', () => {
test('opens the correct link when the link button is clicked', async () => { test('opens the correct link when the link button is clicked', async () => {
render(<Publication {...defaultProps} />) render(<Publication {...defaultProps} />)
await userEvent.click(screen.getByRole('button')) const link = screen.getByRole('link')
expect(open).toHaveBeenCalledWith(defaultProps.link, '_blank') expect(link).toHaveAttribute('href', defaultProps.href)
expect(link).toHaveAttribute('target', '_blank')
expect(link).toHaveAttribute('rel', 'noopener noreferrer')
}) })
test('renders correctly with a custom base component', () => { test('renders correctly with a custom base component', () => {
@@ -1,36 +0,0 @@
import React from 'react'
import { Box, SxProps, Theme, Typography, TypographyProps } from '@mui/material'
import { ExternalIconLink } from '..'
type AwardEntryProps = {
year: string | number
title: string
authors: React.ReactNode
location: string
link?: string
titleProps?: TypographyProps
Component?: React.ElementType
sx?: SxProps<Theme>
}
const Publication = ({ Component = Box, ...props }: AwardEntryProps) => {
return (
<Component sx={{ pl: 2, ...props.sx }}>
<Box display="flex" gap={1} alignItems="flex-start">
<Typography variant="subtitle1" color="primary.main">
{props.year}
</Typography>
<Typography color="text.secondary">
{props.authors},{' '}
<Typography component="span" color="text.primary">
<b>{props.title}</b>
</Typography>
, In: {props.location}
{typeof props.link !== 'undefined' && <ExternalIconLink size="small" fontSize="small" href={props.link} sx={{ color: 'text.secondary' }} />}
</Typography>
</Box>
</Component>
)
}
export default Publication
+37 -1
View File
@@ -1 +1,37 @@
export { default } from './Publication' import React from 'react'
import { Box, SxProps, Theme, Typography, TypographyProps } from '@mui/material'
import { ExternalIconLink } from 'components/atoms/IconLinks/index.js'
type AwardEntryProps = {
year: string | number
title: string
authors: React.ReactNode
location: string
href?: string
titleProps?: TypographyProps
Component?: React.ElementType
sx?: SxProps<Theme>
}
const Publication = ({ Component = Box, ...props }: AwardEntryProps) => {
return (
<Component sx={{ pl: 2, ...props.sx }}>
<Box display="flex" gap={1} alignItems="flex-start">
<Typography variant="subtitle1" color="primary.main">
{props.year}
</Typography>
<Typography color="text.secondary">
{props.authors},{' '}
<Typography component="span" color="text.primary">
<b>{props.title}</b>
</Typography>
, In: {props.location}
{typeof props.href !== 'undefined' && <ExternalIconLink fontSize="inherit" href={props.href} sx={{ color: 'text.secondary' }} />}
</Typography>
</Box>
</Component>
)
}
export default Publication
-1
View File
@@ -2,5 +2,4 @@ export { default as Award } from './Award'
export { default as Extracurricular } from './Extracurricular' export { default as Extracurricular } from './Extracurricular'
export { default as Publication } from './Publication' export { default as Publication } from './Publication'
export { default as IconLinks } from './IconButton'
export * from './IconLinks' export * from './IconLinks'
+4 -4
View File
@@ -6,10 +6,10 @@ const Footer = () => {
const year = new Date().getFullYear() const year = new Date().getFullYear()
return ( return (
<Box sx={{ bgcolor: 'background.paper', p: 6, display: 'flex', alignItems: 'center', flexDirection: 'column' }} component="footer"> <Box sx={{ bgcolor: 'background.paper', p: 6, display: 'flex', alignItems: 'center', flexDirection: 'column' }} component="footer">
<Stack direction="row"> <Stack direction="row" gap={2}>
<LinkedInIconLink size="large" fontSize="large" href="https://www.linkedin.com/in/m-tartari/" /> <LinkedInIconLink fontSize="large" href="https://www.linkedin.com/in/m-tartari/" />
<GitHubIconLink size="large" fontSize="large" href="https://www.github.com/m-tartari/" /> <GitHubIconLink fontSize="large" href="https://www.github.com/m-tartari/" />
<EmailIconLink size="large" fontSize="large" href="mailto:info@m-tartari.eu" /> <EmailIconLink fontSize="large" href="mailto:info@m-tartari.eu" />
</Stack> </Stack>
<Typography variant="subtitle2" align="center" color="text.secondary" component="em" mt={3}> <Typography variant="subtitle2" align="center" color="text.secondary" component="em" mt={3}>
&copy; {year} Michele Tartari. All rights reserved. &copy; {year} Michele Tartari. All rights reserved.
+8 -22
View File
@@ -44,28 +44,14 @@ const Hero = (props: { scrollTarget?: React.ForwardedRef<HTMLSelectElement> }) =
}, },
})}> })}>
<Box> <Box>
{/* <Typography variant="h3">Robotics Engineer</Typography>
<h2>Michele Tartari</h2> <Typography color="text.secondary" sx={{ margin: '0.5em 0 0 0', top: 0 }}>
<h3>Robotics Engineer</h3>
*/}
<Typography
// sx={{ fontSize: '2.5em',margin: 0 }}
variant="h3">
Robotics Engineer
</Typography>
<Typography
color="text.secondary"
sx={{
// margin: { xs: '0.5em 1em', md: '0.5em 0 0 0' },
margin: '0.5em 0 0 0',
top: 0,
}}>
See some of my works below. Send a message to get in touch. See some of my works below. Send a message to get in touch.
</Typography> </Typography>
<Stack direction="row" sx={{ display: 'inline-block' }}> <Stack direction="row" gap={2} mt={2} justifyContent={{ xs: 'center', sm: 'end' }}>
<LinkedInIconLink size="large" sx={{ color: 'text.secondary' }} fontSize="large" href="https://www.linkedin.com/in/m-tartari/" /> <LinkedInIconLink sx={{ color: 'text.secondary' }} fontSize="large" href="https://www.linkedin.com/in/m-tartari/" />
<GitHubIconLink size="large" sx={{ color: 'text.secondary' }} fontSize="large" href="https://www.github.com/m-tartari/" /> <GitHubIconLink sx={{ color: 'text.secondary' }} fontSize="large" href="https://www.github.com/m-tartari/" />
<EmailIconLink size="large" sx={{ color: 'text.secondary' }} fontSize="large" href="mailto:info@m-tartari.eu" /> <EmailIconLink sx={{ color: 'text.secondary' }} fontSize="large" href="mailto:info@m-tartari.eu" />
</Stack> </Stack>
</Box> </Box>
<Avatar <Avatar
@@ -83,8 +69,8 @@ const Hero = (props: { scrollTarget?: React.ForwardedRef<HTMLSelectElement> }) =
{props.scrollTarget && ( {props.scrollTarget && (
<IconButton <IconButton
size="large" size="large"
onClick={() => (props.scrollTarget as React.MutableRefObject<HTMLSelectElement | null>).current?.scrollIntoView()} onClick={() => (props.scrollTarget as React.RefObject<HTMLElement | null>).current?.scrollIntoView()}
sx={{ display: { xs: 'none', md: 'block' }, position: 'absolute', p: 1, bottom: 3 }}> sx={{ position: 'absolute', p: 1, bottom: 2, left: '50%', transform: 'translate(-50%, 0)' }}>
<ExpandMore fontSize="large" /> <ExpandMore fontSize="large" />
</IconButton> </IconButton>
)} )}
+4 -2
View File
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material' import { Accordion, AccordionDetails, AccordionSummary, Stack, Typography } from '@mui/material'
import { ExpandMore as ExpandIcon } from '@mui/icons-material' import { ExpandMore as ExpandIcon } from '@mui/icons-material'
interface ProjectProps { interface ProjectProps {
@@ -54,7 +54,9 @@ const Project = (props: ProjectProps) => {
<Typography variant="h6"> <Typography variant="h6">
<b>{props.title}</b> <b>{props.title}</b>
</Typography> </Typography>
{props.subtitle} <Stack direction="row" gap={1} alignItems="center">
{props.subtitle}
</Stack>
</div> </div>
</AccordionSummary> </AccordionSummary>
<AccordionDetails>{props.children}</AccordionDetails> <AccordionDetails>{props.children}</AccordionDetails>
@@ -153,7 +153,7 @@ const DesktopComponent = React.forwardRef<HTMLSelectElement, Props>((props, ref)
{props.scrollTarget && ( {props.scrollTarget && (
<IconButton <IconButton
size="large" size="large"
onClick={() => (props.scrollTarget as React.MutableRefObject<HTMLSelectElement | null>).current?.scrollIntoView()} onClick={() => (props.scrollTarget as React.RefObject<HTMLSelectElement | null>).current?.scrollIntoView()}
sx={{ position: 'absolute', p: 1, bottom: 2, left: '50%', transform: 'translate(-50%, 0)' }}> sx={{ position: 'absolute', p: 1, bottom: 2, left: '50%', transform: 'translate(-50%, 0)' }}>
<ExpandMore fontSize="large" /> <ExpandMore fontSize="large" />
</IconButton> </IconButton>
@@ -1,41 +1,37 @@
import { screen } from '@testing-library/react' import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { renderWithRouter as render } from 'components/utils/tests' import { renderWithRouter as render } from 'components/utils/tests/index.js'
import ContactPage from './index.js'
import ContactPage from '.'
vi.stubGlobal('open', vi.fn()) vi.stubGlobal('open', vi.fn())
describe('ContactPage', () => { describe('ContactPage', () => {
const open = vi.spyOn(window, 'open')
test('renders without crashing and displays the correct contact methods', () => { test('renders without crashing and displays the correct contact methods', () => {
render(<ContactPage />) render(<ContactPage />)
expect(screen.getAllByText('Contacts')).toHaveLength(2) // Page title and the Appbar drawer link expect(screen.getAllByText('Contacts')).toHaveLength(2) // Page title and the Appbar drawer link
expect(screen.getByLabelText('Email button')).toBeInTheDocument() expect(screen.getByLabelText('Email link')).toBeInTheDocument()
expect(screen.getByLabelText('LinkedIn button')).toBeInTheDocument() expect(screen.getByLabelText('LinkedIn link')).toBeInTheDocument()
expect(screen.getByLabelText('GitHub button')).toBeInTheDocument() expect(screen.getByLabelText('GitHub link')).toBeInTheDocument()
}) })
test('opens the correct link when the email button is clicked', async () => { test.each([
{
label: 'Email link',
href: 'mailto:info@m-tartari.eu',
},
// {
// label: 'LinkedIn link',
// href: 'https://www.linkedin.com/in/m-tartari/',
// },
// {
// label: 'GitHub link',
// href: 'https://www.github.com/m-tartari/',
// },
])('leads to the right place when the $label is clicked', ({ label, href }) => {
render(<ContactPage />) render(<ContactPage />)
const link = screen.getByLabelText(label)
await userEvent.click(screen.getByLabelText('Email button')) expect(link).toHaveAttribute('href', href)
expect(open).toHaveBeenCalledWith('mailto:info@m-tartari.eu', '_blank') expect(link).toHaveAttribute('target', '_blank')
}) expect(link).toHaveAttribute('rel', 'noopener noreferrer')
test('opens the correct link when the LinkedIn button is clicked', async () => {
render(<ContactPage />)
await userEvent.click(screen.getByLabelText('LinkedIn button'))
expect(open).toHaveBeenCalledWith('https://www.linkedin.com/in/m-tartari/', '_blank')
})
test('opens the correct link when the GitHub button is clicked', async () => {
render(<ContactPage />)
await userEvent.click(screen.getByLabelText('GitHub button'))
expect(open).toHaveBeenCalledWith('https://www.github.com/m-tartari/', '_blank')
}) })
}) })
@@ -1,20 +1,41 @@
import { List, ListItem, ListItemButton as MuiListItemButton, Typography, styled } from '@mui/material' import { Link, List, ListItem, Stack, Typography } from '@mui/material'
import { Drafts as DraftsIcon, LinkedIn as LinkedInIcon, GitHub as GitHubIcon } from '@mui/icons-material' import DraftsIcon from '@mui/icons-material/Drafts'
import LinkedInIcon from '@mui/icons-material/LinkedIn'
import GitHubIcon from '@mui/icons-material/GitHub'
import { Page } from 'components/templates' import { Page } from 'components/templates'
const ListItemButton = styled(MuiListItemButton)(({ theme }) => ({ interface ListItemProps {
h6: { children?: React.ReactNode
color: theme.palette.text.secondary, href?: string
}, }
':hover': { const ListItemLink: React.FC<ListItemProps> = ({ children, ...props }) => (
backgroundColor: 'transparent', <ListItem>
color: theme.palette.primary.main, <Link
h6: { {...props}
color: theme.palette.primary.dark, rel="noopener noreferrer"
}, target="_blank"
}, underline="hover"
})) sx={theme => ({
color: 'inherit',
h6: {
color: theme.palette.text.secondary,
},
':hover': {
backgroundColor: 'transparent',
color: theme.palette.primary.main,
h6: {
color: theme.palette.primary.dark,
},
},
})}>
<Stack direction="row" alignItems="center">
{children}
</Stack>
</Link>
</ListItem>
)
const ContactPage = () => { const ContactPage = () => {
return ( return (
@@ -23,32 +44,26 @@ const ContactPage = () => {
Feel free to contact me using any of the following: Feel free to contact me using any of the following:
</Typography> </Typography>
<List> <List>
<ListItem disablePadding> <ListItemLink href="mailto:info@m-tartari.eu" aria-label="Email link">
<ListItemButton aria-label="Email button" onClick={() => window.open('mailto:info@m-tartari.eu', '_blank')}> <DraftsIcon fontSize="large" />
<DraftsIcon fontSize="large" /> <Typography variant="h6" ml={2}>
<Typography variant="h6" ml={2}> info
info <wbr />
<wbr /> @m-tartari.eu
@m-tartari.eu </Typography>
</Typography> </ListItemLink>
</ListItemButton> <ListItemLink aria-label="LinkedIn link" href="https://www.linkedin.com/in/m-tartari/">
</ListItem> <LinkedInIcon fontSize="large" />
<ListItem disablePadding> <Typography variant="h6" ml={2}>
<ListItemButton aria-label="LinkedIn button" onClick={() => window.open('https://www.linkedin.com/in/m-tartari/', '_blank')}> m-tartari
<LinkedInIcon fontSize="large" /> </Typography>
<Typography variant="h6" ml={2}> </ListItemLink>
m-tartari <ListItemLink aria-label="GitHub link" href="https://www.github.com/m-tartari/">
</Typography> <GitHubIcon fontSize="large" />
</ListItemButton> <Typography variant="h6" ml={2}>
</ListItem> m-tartari
<ListItem disablePadding> </Typography>
<ListItemButton aria-label="GitHub button" onClick={() => window.open('https://www.github.com/m-tartari/', '_blank')}> </ListItemLink>
<GitHubIcon fontSize="large" />
<Typography variant="h6" ml={2}>
m-tartari
</Typography>
</ListItemButton>
</ListItem>
</List> </List>
</Page> </Page>
) )
@@ -29,7 +29,12 @@ const Curriculum = () => {
<CurriculumItem title="Business Partner & Technical Lead" dates="03/23 - 12/24" company="MindHub Ferrara" location="Ferrara, Italy"> <CurriculumItem title="Business Partner & Technical Lead" dates="03/23 - 12/24" company="MindHub Ferrara" location="Ferrara, Italy">
<Typography> <Typography>
Co-founded{' '} Co-founded{' '}
<Link target="_blank" rel="noopener noreferrer" href="https://www.facebook.com/profile.php?id=61550278936416"> <Link
target="_blank"
rel="noopener noreferrer"
// href="https://www.facebook.com/profile.php?id=61550278936416"
// href="https://mind-hub.it/contact-hall/mindhub-ferrara/"
href="https://www.linkedin.com/company/mindhub-ferrara/">
MindHub Ferrara MindHub Ferrara
</Link> </Link>
, the first programming school for children in Ferrara, under the{' '} , the first programming school for children in Ferrara, under the{' '}
@@ -132,9 +137,9 @@ const Curriculum = () => {
</Typography> </Typography>
{/* id="WUT" */} {/* id="WUT" */}
<CurriculumItem title="IBM Full Stack Software Developer Professional Certificate" dates="03/24"> <CurriculumItem title="IBM Full Stack Software Developer Professional Certificate" dates="03/24">
<Stack direction="row" alignItems="flex-end"> <Stack direction="row" alignItems="center" gap={0.5}>
<Typography>Credential ID: EZ34GWQLRHEG</Typography> <Typography>Credential ID: EZ34GWQLRHEG</Typography>
<ExternalIconLink size="small" fontSize="small" href="https://www.coursera.org/account/accomplishments/professional-cert/EZ34GWQLRHEG" /> <ExternalIconLink fontSize="inherit" href="https://www.coursera.org/account/accomplishments/professional-cert/EZ34GWQLRHEG" />
</Stack> </Stack>
</CurriculumItem> </CurriculumItem>
@@ -414,7 +419,7 @@ const Curriculum = () => {
} }
location="ROMANSY 23 - Robot Design, Dynamics and Control. CISM International Centre for Mechanical Sciences (Courses and Lectures), vol location="ROMANSY 23 - Robot Design, Dynamics and Control. CISM International Centre for Mechanical Sciences (Courses and Lectures), vol
601. Springer, Cham." 601. Springer, Cham."
link="https://doi.org/10.1007/978-3-030-58380-4_7" href="https://doi.org/10.1007/978-3-030-58380-4_7"
/> />
</Box> </Box>
+19 -32
View File
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { Box, Typography } from '@mui/material' import { Box, Stack, Typography } from '@mui/material'
import { ViewportProvider } from 'components/utils/viewport' import { ViewportProvider } from 'components/utils/viewport'
import { AcademyIconLink, DescriptionIconLink, ExternalAppIconLink, GitHubIconLink, VideoIconLink } from 'components/atoms' import { AcademyIconLink, DescriptionIconLink, ExternalAppIconLink, GitHubIconLink, VideoIconLink } from 'components/atoms'
@@ -28,12 +28,10 @@ const MainPage = () => {
image="images/IIT_Linfa.webp" 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={
<> <Stack direction="row" gap={1} alignItems="center">
<VideoIconLink size="small" fontSize="small" href="https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/" /> <VideoIconLink fontSize="small" pt={0.5} href="https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/" />
<Typography component="span" color="text.secondary" ml={1}> <Typography color="text.secondary">Software Engineer @ IIT</Typography>
Software Engineer @ IIT </Stack>
</Typography>
</>
}> }>
This project aims to simplify the control, monitoring, and programming of reconfigurable robots through an intuitive block-programming This project aims to simplify the control, monitoring, and programming of reconfigurable robots through an intuitive block-programming
interface. It includes a front-end web app, built with TypeScript and React, allowing users to effortlessly change robot operating modes and interface. It includes a front-end web app, built with TypeScript and React, allowing users to effortlessly change robot operating modes and
@@ -52,13 +50,11 @@ const MainPage = () => {
mobileImage="images/IIT_Modular_Mobile.webp" 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={
<> <Stack direction="row" gap={1} alignItems="center">
<GitHubIconLink size="small" fontSize="small" href="https://github.com/ADVRHumanoids/motor_calibration" /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/ADVRHumanoids/modular_hhcm" />
<ExternalAppIconLink size="small" fontSize="small" href="https://modular.alberobotics.it" /> <ExternalAppIconLink pt={0.5} fontSize="small" href="https://modular.alberobotics.it" />
<Typography component="span" color="text.secondary" ml={1}> <Typography color="text.secondary">Software Engineer @ IIT</Typography>
Software Engineer @ IIT </Stack>
</Typography>
</>
}> }>
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
@@ -75,12 +71,10 @@ const MainPage = () => {
image="images/IIT_Modules.webp" image="images/IIT_Modules.webp"
title="Automated Actuators and Robotic Modules Calibration" title="Automated Actuators and Robotic Modules Calibration"
caption={ caption={
<> <Stack direction="row" gap={1} alignItems="center">
<GitHubIconLink size="small" fontSize="small" href="https://github.com/ADVRHumanoids/motor_calibration" /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/ADVRHumanoids/motor_calibration" />
<Typography component="span" color="text.secondary" ml={1}> <Typography color="text.secondary">Embedded Engineer @ IIT</Typography>
Embedded Engineer @ IIT </Stack>
</Typography>
</>
}> }>
This project focused on automating the calibration of EtherCAT actuators and robotic modules, aiming for a fully automated procedure for 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 newly assembled actuators. The process involved testing the PDO, conducting phase angle calibration, torque sensor calibration, ripple and
@@ -96,18 +90,11 @@ const MainPage = () => {
mobileImage="images/thesis.webp" mobileImage="images/thesis.webp"
title="Voice Controlled Mobile Robot for Indoor Applications" title="Voice Controlled Mobile Robot for Indoor Applications"
caption={ caption={
<> <Stack direction="row" gap={1} alignItems="center">
<AcademyIconLink size="small" fontSize="small" href="https://doi.org/10.1007/978-3-030-58380-4_7" /> <AcademyIconLink pt={0.5} fontSize="small" href="https://doi.org/10.1007/978-3-030-58380-4_7" />
<DescriptionIconLink pt={0.5} href="https://drive.google.com/drive/folders/1Y7TJJN8z1llCkSTnpVHMELD3PqsX8VF9?usp=sharing" />
<DescriptionIconLink <Typography color="text.secondary">Master Thesis @ WUT</Typography>
size="small" </Stack>
fontSize="small"
href="https://drive.google.com/file/d/1wBfvAzy9N8uChNxNj9Kd_hcvI0Qq3uQj/view?usp=sharing"
/>
<Typography component="span" color="text.secondary" ml={1}>
Master Thesis @ WUT
</Typography>
</>
}> }>
This thesis proposes a design for a small mobile robot controlled by voice. Firstly a preliminary analysis of the state of art of 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 human-robot interaction has been conducted. Then, an analysis of the equipment selected took place, and, later, appropriate methodologies
@@ -1,12 +1,9 @@
import { screen } from '@testing-library/react' import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { renderWithRouter as render } from 'components/utils/tests'
import { renderWithRouter as render } from 'components/utils/tests/index.js'
import ProjectsPage from '.' import ProjectsPage from '.'
vi.stubGlobal('open', vi.fn()) vi.stubGlobal('open', vi.fn())
const open = vi.spyOn(window, 'open')
describe('ProjectsPage component', () => { describe('ProjectsPage component', () => {
test('renders without crashing and displays the correct project titles', () => { test('renders without crashing and displays the correct project titles', () => {
render(<ProjectsPage />) render(<ProjectsPage />)
@@ -25,8 +22,10 @@ describe('ProjectsPage component', () => {
test('opens the correct links when the link buttons are clicked', async () => { test('opens the correct links when the link buttons are clicked', async () => {
render(<ProjectsPage />) render(<ProjectsPage />)
// test one of the many links // inspect one of the many links
await userEvent.click(screen.getByLabelText('Video-button')) const link = screen.getByLabelText('Video-button')
expect(open).toHaveBeenLastCalledWith('https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/', '_blank') expect(link).toHaveAttribute('href', 'https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/')
expect(link).toHaveAttribute('target', '_blank')
expect(link).toHaveAttribute('rel', 'noopener noreferrer')
}) })
}) })
@@ -22,13 +22,13 @@ const ProjectsPage = () => {
Software Engineer @ IIT Software Engineer @ IIT
</Typography> </Typography>
<VideoIconLink <VideoIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/" href="https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
{/* <Typography color="text.secondary" display="inline-block"> */} {/* <Typography color="text.secondary" display="inline-block"> */}
<Typography color="text.secondary" ml={1} display={{ xs: 'none', sm: 'inline' }}> <Typography color="text.secondary" display={{ xs: 'none', sm: 'inline' }}>
Software Engineer @ IIT Software Engineer @ IIT
</Typography> </Typography>
</> </>
@@ -49,9 +49,9 @@ const ProjectsPage = () => {
<Typography color="text.secondary" display={{ sm: 'none' }}> <Typography color="text.secondary" display={{ sm: 'none' }}>
Software Engineer @ IIT Software Engineer @ IIT
</Typography> </Typography>
<GitHubIconLink size="small" fontSize="small" href="https://github.com/ADVRHumanoids/modular_hhcm/" sx={{ color: 'text.secondary' }} /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/ADVRHumanoids/modular_hhcm/" sx={{ color: 'text.secondary' }} />
<ExternalAppIconLink size="small" fontSize="small" href="https://modular.alberobotics.it" sx={{ color: 'text.secondary' }} /> <ExternalAppIconLink pt={0.5} fontSize="small" href="https://modular.alberobotics.it" sx={{ color: 'text.secondary' }} />
<Typography color="text.secondary" ml={1} display={{ xs: 'none', sm: 'inline' }}> <Typography color="text.secondary" display={{ xs: 'none', sm: 'inline' }}>
Software Engineer @ IIT Software Engineer @ IIT
</Typography> </Typography>
</> </>
@@ -70,14 +70,14 @@ const ProjectsPage = () => {
<Typography color="text.secondary" display={{ sm: 'none' }}> <Typography color="text.secondary" display={{ sm: 'none' }}>
Master Thesis @ WUT Master Thesis @ WUT
</Typography> </Typography>
<AcademyIconLink size="small" fontSize="small" href="https://doi.org/10.1007/978-3-030-58380-4_7" sx={{ color: 'text.secondary' }} /> <AcademyIconLink pt={0.5} fontSize="small" href="https://doi.org/10.1007/978-3-030-58380-4_7" sx={{ color: 'text.secondary' }} />
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/file/d/1wBfvAzy9N8uChNxNj9Kd_hcvI0Qq3uQj/view?usp=sharing" href="https://drive.google.com/drive/folders/1Y7TJJN8z1llCkSTnpVHMELD3PqsX8VF9?usp=sharing"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
<Typography color="text.secondary" ml={1} display={{ xs: 'none', sm: 'inline' }}> <Typography color="text.secondary" display={{ xs: 'none', sm: 'inline' }}>
Master Thesis @ WUT Master Thesis @ WUT
</Typography> </Typography>
</> </>
@@ -93,15 +93,12 @@ const ProjectsPage = () => {
<Project <Project
title="Recharging Electric Cars with a Mobile Robot" title="Recharging Electric Cars with a Mobile Robot"
subtitle={ subtitle={
<> <DescriptionIconLink
<GitHubIconLink size="small" fontSize="small" href="https://github.com/m-tartari/ekf_matlab" sx={{ color: 'text.secondary' }} /> pt={0.5}
<DescriptionIconLink fontSize="small"
size="small" href="https://drive.google.com/file/d/1wBfvAzy9N8uChNxNj9Kd_hcvI0Qq3uQj/view?usp=sharing"
fontSize="small" sx={{ color: 'text.secondary' }}
href="https://drive.google.com/file/d/1BgPj-fgtYDDEsFHmDk2fGVm7DCPFWEeN/view?usp=sharing" />
sx={{ color: 'text.secondary' }}
/>
</>
}> }>
<Typography> <Typography>
The aim of this project was to prove the possibility to design an autonomous mobile robot whose task was to recharge electric cars in a The aim of this project was to prove the possibility to design an autonomous mobile robot whose task was to recharge electric cars in a
@@ -112,11 +109,11 @@ const ProjectsPage = () => {
title="Mobile Robot Self-Localization" title="Mobile Robot Self-Localization"
subtitle={ subtitle={
<> <>
<GitHubIconLink size="small" fontSize="small" href="https://github.com/m-tartari/firebot" sx={{ color: 'text.secondary' }} /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/ekf_matlab" sx={{ color: 'text.secondary' }} />
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/open?id=1a_ZvgaQjzvedeo296zrDpdvNL_VJDWTX" href="https://drive.google.com/file/d/1BgPj-fgtYDDEsFHmDk2fGVm7DCPFWEeN/view?usp=sharing"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
</> </>
@@ -130,16 +127,11 @@ const ProjectsPage = () => {
title="Onychophora-Inspired Firefighting Robot" title="Onychophora-Inspired Firefighting Robot"
subtitle={ subtitle={
<> <>
<GitHubIconLink <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/firebot" sx={{ color: 'text.secondary' }} />
size="small"
fontSize="small"
href="https://github.com/m-tartari/3RRR_planar_parallel_manipulator"
sx={{ color: 'text.secondary' }}
/>
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/open?id=11lCJM3EjVsua379BKD5NcEDHQLL4jdQn" href="https://drive.google.com/open?id=1a_ZvgaQjzvedeo296zrDpdvNL_VJDWTX"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
</> </>
@@ -155,13 +147,13 @@ const ProjectsPage = () => {
<> <>
{/* */} {/* */}
<GitHubIconLink <GitHubIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://github.com/m-tartari/simple_trajectory_optimization" href="https://github.com/m-tartari/simple_trajectory_optimization"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/open?id=11m7r7fLda_jpDEZ-9qAWvnfRN2kLkhlA" href="https://drive.google.com/open?id=11m7r7fLda_jpDEZ-9qAWvnfRN2kLkhlA"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
@@ -176,12 +168,20 @@ const ProjectsPage = () => {
<Project <Project
title="3RRR Planar Parallel Manipulator" title="3RRR Planar Parallel Manipulator"
subtitle={ subtitle={
<GitHubIconLink <>
size="small" <GitHubIconLink
fontSize="small" pt={0.5}
href="https://github.com/m-tartari/3RRR_planar_parallel_manipulator" fontSize="small"
sx={{ color: 'text.secondary' }} href="https://github.com/m-tartari/3RRR_planar_parallel_manipulator"
/> sx={{ color: 'text.secondary' }}
/>
<DescriptionIconLink
pt={0.5}
fontSize="small"
href="https://drive.google.com/open?id=11lCJM3EjVsua379BKD5NcEDHQLL4jdQn"
sx={{ color: 'text.secondary' }}
/>
</>
}> }>
<Typography>This project concerns the design and optimization of a 3RRR planar parallel manipulator using MATLAB and CATIA</Typography> <Typography>This project concerns the design and optimization of a 3RRR planar parallel manipulator using MATLAB and CATIA</Typography>
</Project> </Project>
@@ -189,7 +189,7 @@ const ProjectsPage = () => {
title="Simple Swarm" title="Simple Swarm"
subtitle={ subtitle={
<> <>
<GitHubIconLink size="small" fontSize="small" href="https://github.com/m-tartari/simple_swarm" sx={{ color: 'text.secondary' }} /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/simple_swarm" sx={{ color: 'text.secondary' }} />
</> </>
}> }>
<Typography> <Typography>
@@ -201,7 +201,7 @@ const ProjectsPage = () => {
subtitle={ subtitle={
<> <>
<GitHubIconLink <GitHubIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://github.com/m-tartari/industrial_robot_programming" href="https://github.com/m-tartari/industrial_robot_programming"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
@@ -215,7 +215,7 @@ const ProjectsPage = () => {
subtitle={ subtitle={
<> <>
<GitHubIconLink <GitHubIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://github.com/m-tartari/modelling_and_control_of_manipulators" href="https://github.com/m-tartari/modelling_and_control_of_manipulators"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
@@ -240,13 +240,8 @@ const ProjectsPage = () => {
<Typography color="text.secondary" display={{ sm: 'none' }}> <Typography color="text.secondary" display={{ sm: 'none' }}>
Embedded Engineer @ IIT Embedded Engineer @ IIT
</Typography> </Typography>
<GitHubIconLink <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/ADVRHumanoids/motor_calibration" sx={{ color: 'text.secondary' }} />
size="small" <Typography color="text.secondary" display={{ xs: 'none', sm: 'inline' }}>
fontSize="small"
href="https://github.com/ADVRHumanoids/motor_calibration"
sx={{ color: 'text.secondary' }}
/>
<Typography color="text.secondary" ml={1} display={{ xs: 'none', sm: 'inline' }}>
Embedded Engineer @ IIT Embedded Engineer @ IIT
</Typography> </Typography>
</> </>
@@ -268,12 +263,12 @@ const ProjectsPage = () => {
Bachelor Thesis @ TONGJI Bachelor Thesis @ TONGJI
</Typography> </Typography>
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/file/d/0B1z42QMR-0UtYzBYSVhRV3ZPWk0/view?usp=sharing" href="https://drive.google.com/file/d/0B1z42QMR-0UtYzBYSVhRV3ZPWk0/view?resourcekey=0-3O0frJlyho7TRKwNcy8f4g"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
<Typography color="text.secondary" ml={1} display={{ xs: 'none', sm: 'inline' }}> <Typography color="text.secondary" display={{ xs: 'none', sm: 'inline' }}>
Bachelor Thesis @ TONGJI Bachelor Thesis @ TONGJI
</Typography> </Typography>
</> </>
@@ -290,12 +285,12 @@ const ProjectsPage = () => {
Bachelor Thesis @ UNIBO Bachelor Thesis @ UNIBO
</Typography> </Typography>
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/file/d/0B1z42QMR-0UtSkxRVzJPTk1IX0k/view?usp=sharing" href="https://drive.google.com/file/d/0B1z42QMR-0UtSkxRVzJPTk1IX0k/view?resourcekey=0-LN4KALZlLWc_lKekqqQbIw"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
/> />
<Typography color="text.secondary" ml={1} display={{ xs: 'none', sm: 'inline' }}> <Typography color="text.secondary" display={{ xs: 'none', sm: 'inline' }}>
Bachelor Thesis @ UNIBO Bachelor Thesis @ UNIBO
</Typography> </Typography>
</> </>
@@ -308,14 +303,9 @@ const ProjectsPage = () => {
title="Control of a Nonlinear System" title="Control of a Nonlinear System"
subtitle={ subtitle={
<> <>
<GitHubIconLink <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/nonlinear_control" sx={{ color: 'text.secondary' }} />
size="small"
fontSize="small"
href="https://github.com/m-tartari/modelling_and_control_of_manipulators"
sx={{ color: 'text.secondary' }}
/>
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/file/d/1TE3TWkXZjVq8OuBkLl497c_MQSs3MiLQ/view?usp=sharing" href="https://drive.google.com/file/d/1TE3TWkXZjVq8OuBkLl497c_MQSs3MiLQ/view?usp=sharing"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
@@ -330,14 +320,9 @@ const ProjectsPage = () => {
title="Multi-Body System's Kinematic Analysis" title="Multi-Body System's Kinematic Analysis"
subtitle={ subtitle={
<> <>
<GitHubIconLink <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/multi-body-kinematics" sx={{ color: 'text.secondary' }} />
size="small"
fontSize="small"
href="https://github.com/m-tartari/multi-body-kinematics"
sx={{ color: 'text.secondary' }}
/>
<DescriptionIconLink <DescriptionIconLink
size="small" pt={0.5}
fontSize="small" fontSize="small"
href="https://drive.google.com/file/d/15yrDU2blkb34Jy2GXkCIe4qNmDRmp31w/view?usp=sharing" href="https://drive.google.com/file/d/15yrDU2blkb34Jy2GXkCIe4qNmDRmp31w/view?usp=sharing"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
@@ -352,14 +337,14 @@ const ProjectsPage = () => {
<Project <Project
title="Signal Processing" title="Signal Processing"
subtitle={ subtitle={
<GitHubIconLink size="small" fontSize="small" href="https://github.com/m-tartari/signal_processing" sx={{ color: 'text.secondary' }} /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/signal_processing" sx={{ color: 'text.secondary' }} />
}> }>
<Typography>Several Signal processing projects developed using MATLAB.</Typography> <Typography>Several Signal processing projects developed using MATLAB.</Typography>
</Project> </Project>
<Project <Project
title="Real Time Systems" title="Real Time Systems"
subtitle={ subtitle={
<GitHubIconLink size="small" fontSize="small" href="https://github.com/m-tartari/real_time_systems" sx={{ color: 'text.secondary' }} /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/real_time_systems" sx={{ color: 'text.secondary' }} />
}> }>
<Typography>Several real-time systems' projects developed using C++ and OIL, and run using Trampoline RTOS on a Teensy board.</Typography> <Typography>Several real-time systems' projects developed using C++ and OIL, and run using Trampoline RTOS on a Teensy board.</Typography>
</Project> </Project>
@@ -373,7 +358,7 @@ const ProjectsPage = () => {
<Project <Project
title="Medieval Furniture" title="Medieval Furniture"
subtitle={ subtitle={
<GitHubIconLink size="small" fontSize="small" href="https://github.com/m-tartari/medieval_furniture" sx={{ color: 'text.secondary' }} /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/medieval_furniture" sx={{ color: 'text.secondary' }} />
}> }>
<Typography> <Typography>
A collection of pdf and CAD models to create late XIV century furniture and medieval camp equipment. These objects are being realized by{' '} A collection of pdf and CAD models to create late XIV century furniture and medieval camp equipment. These objects are being realized by{' '}
@@ -386,7 +371,7 @@ const ProjectsPage = () => {
<Project <Project
title="Pinterest Pinner" title="Pinterest Pinner"
subtitle={ subtitle={
<GitHubIconLink size="small" fontSize="small" href="https://github.com/m-tartari/pinterest_pinner" sx={{ color: 'text.secondary' }} /> <GitHubIconLink pt={0.5} fontSize="small" href="https://github.com/m-tartari/pinterest_pinner" sx={{ color: 'text.secondary' }} />
}> }>
<Typography> <Typography>
Publishes all photos from a{' '} Publishes all photos from a{' '}