-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #91 from zetkin/issue-71-home-screen
Home page (minus Storybook)
- Loading branch information
Showing
3 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Cache } from '@/Cache'; | ||
import HomeDashboard from '@/components/HomeDashboard'; | ||
import MessageAdapterFactory from '@/utils/adapters/MessageAdapterFactory'; | ||
import { ProjectCardProps } from '@/components/ProjectCard'; | ||
import { RepoGit } from '@/RepoGit'; | ||
import { ServerConfig } from '@/utils/serverConfig'; | ||
|
||
// Force dynamic rendering for this page. By default Next.js attempts to render | ||
// this page statically. That means that it tries to render the page at build | ||
// time instead of at runtime. That doesn't work: this page needs to fetch | ||
// project-specific config files and perform git operations. So this little | ||
// one-liner forces it into dynamic rendering mode. | ||
// | ||
// More info on dynamic vs static rendering at: | ||
// https://nextjs.org/learn/dashboard-app/static-and-dynamic-rendering | ||
// | ||
// More info on `export const dynamic` at: | ||
// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config | ||
export const dynamic = 'force-dynamic'; | ||
|
||
export default async function Home() { | ||
const serverConfig = await ServerConfig.read(); | ||
const projects = await Promise.all( | ||
serverConfig.projects.map<Promise<ProjectCardProps>>(async (project) => { | ||
await RepoGit.cloneIfNotExist(project); | ||
const repoGit = await RepoGit.getRepoGit(project); | ||
const lyraConfig = await repoGit.getLyraConfig(); | ||
const projectConfig = lyraConfig.getProjectConfigByPath( | ||
project.projectPath, | ||
); | ||
const msgAdapter = MessageAdapterFactory.createAdapter(projectConfig); | ||
const messages = await msgAdapter.getMessages(); | ||
const store = await Cache.getProjectStore(projectConfig); | ||
const languages = await Promise.all( | ||
projectConfig.languages.map(async (lang) => { | ||
const translations = await store.getTranslations(lang); | ||
return { | ||
href: `/projects/${project.name}/${lang}`, | ||
language: lang, | ||
progress: translations | ||
? (Object.keys(translations).length / messages.length) * 100 | ||
: 0, | ||
}; | ||
}), | ||
); | ||
|
||
return { | ||
href: `/projects/${project.name}`, | ||
languages, | ||
messageCount: messages.length, | ||
name: project.name, | ||
}; | ||
}), | ||
); | ||
|
||
return <HomeDashboard projects={projects} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { FC } from 'react'; | ||
import { Box, List, Typography } from '@mui/joy'; | ||
import ProjectCard, { ProjectCardProps } from './ProjectCard'; | ||
|
||
type HomeDashboardProps = { | ||
projects: ProjectCardProps[]; | ||
}; | ||
|
||
const HomeDashboard: FC<HomeDashboardProps> = ({ projects }) => { | ||
return ( | ||
<Box | ||
alignItems="center" | ||
display="flex" | ||
flexDirection="column" | ||
justifyContent="center" | ||
minHeight="97vh" | ||
> | ||
<Typography alignSelf="flex-start" color="primary" component="h1"> | ||
Your Lyra Projects | ||
</Typography> | ||
<List | ||
sx={{ | ||
'@media (min-width: 600px)': { | ||
alignContent: 'center', | ||
columnGap: 2, | ||
display: 'grid', | ||
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 300px));', | ||
justifyContent: 'center', | ||
}, | ||
alignItems: 'center', | ||
display: 'flex', | ||
flex: 1, | ||
flexDirection: 'column', | ||
rowGap: 2, | ||
width: '100%', | ||
}} | ||
> | ||
{projects.map((project, i) => ( | ||
<ProjectCard key={i} {...project} /> | ||
))} | ||
</List> | ||
</Box> | ||
); | ||
}; | ||
|
||
export default HomeDashboard; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { FC } from 'react'; | ||
import { Box, LinearProgress, Link, Typography } from '@mui/joy'; | ||
|
||
export type ProjectCardProps = { | ||
/** | ||
* The URL of the project page. | ||
*/ | ||
href: string; | ||
|
||
/** | ||
* The project's languages and their translation progress. | ||
*/ | ||
languages: { | ||
/** | ||
* The URL of the page containing the project's messages in this language. | ||
*/ | ||
|
||
href: string; | ||
|
||
/** | ||
* The name of the language. | ||
*/ | ||
language: string; | ||
|
||
/** | ||
* The percentage of messages translated in this language. 0 means none, 100 | ||
* means all of them. | ||
*/ | ||
progress: number; | ||
}[]; | ||
|
||
/** | ||
* The number of messages in the project. | ||
*/ | ||
messageCount: number; | ||
|
||
/** | ||
* The name of the project. | ||
*/ | ||
name: string; | ||
}; | ||
|
||
/** | ||
* Project cards are the primary navigation element on the home screen. They | ||
* display information about the project, and clicking them takes the user to | ||
* the project page. | ||
* | ||
* Displaying a collection of structured information like this in a clickable | ||
* card introduces some accessibility challenges. The implementation here | ||
* employs the [Inclusive Components "pseudo-content trick"](https://inclusive-components.design/cards/). | ||
* | ||
*/ | ||
const ProjectCard: FC<ProjectCardProps> = ({ | ||
href, | ||
languages, | ||
name, | ||
messageCount, | ||
}) => { | ||
return ( | ||
<Box component="li" sx={{ listStyleType: 'none' }} width="100%"> | ||
<Box | ||
bgcolor="neutral.50" | ||
border={1} | ||
borderColor="transparent" | ||
borderRadius={8} | ||
display="flex" | ||
flexDirection="column" | ||
position="relative" | ||
px={3} | ||
py={2} | ||
rowGap={1} | ||
sx={{ | ||
':focus-within, :hover': { | ||
outlineColor: 'focusVisible', | ||
outlineStyle: 'solid', | ||
outlineWidth: 1, | ||
}, | ||
}} | ||
> | ||
<Typography component="h2"> | ||
<Link | ||
href={href} | ||
sx={{ | ||
'::after': { | ||
bottom: 0, | ||
content: '""', | ||
left: 0, | ||
position: 'absolute', | ||
right: 0, | ||
top: 0, | ||
width: '100%', | ||
}, | ||
':hover, :focus': { | ||
outline: 'none', | ||
textDecoration: 'none', | ||
}, | ||
color: 'inherit', | ||
position: 'inherit', | ||
}} | ||
> | ||
{name} | ||
</Link> | ||
</Typography> | ||
<Typography>{messageCount} messages</Typography> | ||
<Box | ||
columnGap={1} | ||
component="ul" | ||
display="grid" | ||
margin={0} | ||
padding={0} | ||
rowGap={1} | ||
sx={{ | ||
gridTemplateColumns: 'repeat(auto-fit, minmax(30px, 120px));', | ||
width: '100%', | ||
}} | ||
> | ||
{languages.map(({ href, language, progress }) => ( | ||
<Box | ||
key={language} | ||
bgcolor="primary.50" | ||
borderRadius={4} | ||
component="li" | ||
position="relative" | ||
sx={{ | ||
':focus-within, :hover': { | ||
outlineColor: 'focusVisible', | ||
outlineStyle: 'solid', | ||
outlineWidth: 1, | ||
}, | ||
listStyleType: 'none', | ||
}} | ||
> | ||
<Box display="flex" flexDirection="column" px={3} py={2}> | ||
<Link | ||
href={href} | ||
sx={{ | ||
'::after': { | ||
bottom: 0, | ||
content: '""', | ||
left: 0, | ||
position: 'absolute', | ||
right: 0, | ||
top: 0, | ||
width: '100%', | ||
}, | ||
':hover, :focus': { | ||
outline: 'none', | ||
textDecoration: 'none', | ||
}, | ||
position: 'inherit', | ||
}} | ||
> | ||
{language} | ||
</Link> | ||
<LinearProgress | ||
determinate | ||
size="lg" | ||
sx={{ backgroundColor: '#ffffff' }} | ||
thickness={8} | ||
value={Math.min(progress, 100)} | ||
variant="outlined" | ||
/> | ||
</Box> | ||
</Box> | ||
))} | ||
</Box> | ||
</Box> | ||
</Box> | ||
); | ||
}; | ||
|
||
export default ProjectCard; |