patch: ux update for repo page

Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
Raul Kele 2023-04-20 15:09:09 +03:00
parent ecd584c4e2
commit c1a51afede
14 changed files with 288 additions and 276 deletions

View File

@ -248,7 +248,7 @@ describe('Repo details component', () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsWithMissingData } });
render(<RepoDetailsThemeWrapper />);
expect(await screen.findByText('test')).toBeInTheDocument();
expect(await screen.findByText(/timestamp n\/a/i)).toBeInTheDocument();
expect((await screen.findAllByText(/timestamp n\/a/i)).length).toBeGreaterThan(0);
});
it('renders vulnerability icons', async () => {
@ -288,15 +288,6 @@ describe('Repo details component', () => {
await waitFor(() => expect(mockUseNavigate).toBeCalledWith('/home'));
});
it('should switch between tabs', async () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetailsThemeWrapper />);
expect(await screen.findByTestId('overview-container')).toBeInTheDocument();
fireEvent.click(await screen.findByText(/tags/i));
expect(await screen.findByTestId('tags-container')).toBeInTheDocument();
expect(screen.queryByTestId('overview-container')).not.toBeInTheDocument();
});
it('should render platform chips and they should redirect to explore page', async () => {
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockRepoDetailsData } });
render(<RepoDetailsThemeWrapper />);

View File

@ -2,6 +2,15 @@ import { fireEvent, waitFor, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Tags from 'components/Repo/Tabs/Tags';
import React from 'react';
import MockThemeProvier from '__mocks__/MockThemeProvider';
const TagsThemeWrapper = () => {
return (
<MockThemeProvier>
<Tags tags={mockedTagsData} />
</MockThemeProvier>
);
};
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
@ -59,7 +68,7 @@ const mockedTagsData = [
describe('Tags component', () => {
it('should open and close details dropdown for tags', async () => {
render(<Tags tags={mockedTagsData} />);
render(<TagsThemeWrapper />);
const openBtn = screen.getAllByText(/digest/i);
fireEvent.click(openBtn[0]);
expect(screen.getByText(/OS\/ARCH/i)).toBeInTheDocument();
@ -68,7 +77,7 @@ describe('Tags component', () => {
});
it('should navigate to tag page details when tag is clicked', async () => {
render(<Tags tags={mockedTagsData} />);
render(<TagsThemeWrapper />);
const tagLink = await screen.findByText('latest');
fireEvent.click(tagLink);
await waitFor(() => {
@ -77,7 +86,7 @@ describe('Tags component', () => {
});
it('should navigate to specific manifest when clicking the digest', async () => {
render(<Tags tags={mockedTagsData} />);
render(<TagsThemeWrapper />);
const openBtn = screen.getAllByText(/digest/i);
await fireEvent.click(openBtn[0]);
const tagLink = await screen.findByText(/sha256:adca4/i);
@ -90,8 +99,8 @@ describe('Tags component', () => {
});
it('should filter tag list based on user input', async () => {
render(<Tags tags={mockedTagsData} />);
const tagFilterInput = await screen.findByPlaceholderText(/Search for Tags/i);
render(<TagsThemeWrapper />);
const tagFilterInput = await screen.findByPlaceholderText(/Search Tags/i);
expect(await screen.findByText(/latest/i)).toBeInTheDocument();
expect(await screen.findByText(/bullseye/i)).toBeInTheDocument();
userEvent.type(tagFilterInput, 'bull');
@ -100,7 +109,7 @@ describe('Tags component', () => {
});
it('should sort tags based on the picked sort criteria', async () => {
render(<Tags tags={mockedTagsData} />);
render(<TagsThemeWrapper />);
const selectFilter = await screen.findByText('Newest');
expect(selectFilter).toBeInTheDocument();
userEvent.click(selectFilter);

View File

@ -3,6 +3,7 @@ import { api } from 'api';
import DependsOn from 'components/Tag/Tabs/DependsOn';
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import MockThemeProvier from '__mocks__/MockThemeProvider';
const mockDependenciesList = {
data: {
@ -52,11 +53,13 @@ const mockDependenciesList = {
const RouterDependsWrapper = () => {
return (
<MockThemeProvier>
<BrowserRouter>
<Routes>
<Route path="*" element={<DependsOn name="alpine:latest" />} />
</Routes>
</BrowserRouter>
</MockThemeProvier>
);
};

View File

@ -3,6 +3,7 @@ import { api } from 'api';
import IsDependentOn from 'components/Tag/Tabs/IsDependentOn';
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import MockThemeProvier from '__mocks__/MockThemeProvider';
const mockDependentsList = {
data: {
@ -52,11 +53,13 @@ const mockDependentsList = {
const RouterDependsWrapper = () => {
return (
<MockThemeProvier>
<BrowserRouter>
<Routes>
<Route path="*" element={<IsDependentOn name="alpine:latest" />} />
</Routes>
</BrowserRouter>
</MockThemeProvier>
);
};

View File

@ -76,7 +76,7 @@ const endpoints = {
(pageNumber - 1) * pageSize
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned Documentation Vendor Labels} DownloadCount}}}`,
detailedRepoInfo: (name) =>
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {RepoName IsSigned Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Title Documentation DownloadCount Source Description Licenses}}}}`,
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors NewestImage {RepoName IsSigned Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
detailedImageInfo: (name, tag) =>
`/v2/_zot/ext/search?query={Image(image: "${name}:${tag}"){RepoName IsSigned Vulnerabilities {MaxSeverity Count} Referrers {MediaType ArtifactType Size Digest Annotations{Key Value}} Tag Manifests {History {Layer {Size Digest} HistoryDescription {CreatedBy EmptyLayer}} Digest ConfigDigest LastUpdated Size Platform {Os Arch}} Vendor Licenses }}`,
vulnerabilitiesForRepo: (name, { pageNumber = 1, pageSize = 15 }, searchTerm = '') => {

View File

@ -34,11 +34,6 @@ const useStyles = makeStyles((theme) => ({
justifyContent: 'center',
alignItems: 'center'
},
exploreText: {
color: '#C0C0C0',
display: 'flex',
alignItems: 'left'
},
resultsRow: {
justifyContent: 'space-between',
alignItems: 'center'

View File

@ -13,26 +13,31 @@ import React from 'react';
const useStyles = makeStyles((theme) => {
return {
exploreHeader: {
backgroundColor: '#FFFFFF',
backgroundColor: 'transparent',
minHeight: 50,
paddingLeft: '3rem',
padding: '2.75rem 0 1.25rem 0',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
padding: '2rem',
[theme.breakpoints.down('md')]: {
padding: '1rem'
}
},
explore: {
color: '#52637A',
color: theme.palette.secondary.dark,
fontSize: '0.813rem',
fontWeight: '600',
letterSpacing: '0.009375rem',
[theme.breakpoints.down('md')]: {
fontSize: '0.8rem'
}
},
arrowIcon: {
color: theme.palette.secondary.dark,
marginRight: '1.75rem',
fontSize: { xs: '1.5rem', md: '2rem' },
cursor: 'pointer'
}
};
});
@ -49,10 +54,7 @@ function ExploreHeader() {
return (
<div className={classes.exploreHeader}>
<ArrowBackIcon
sx={{ color: '#52637A', marginRight: '1.75rem', fontSize: { xs: '1.5rem', md: '2rem' }, cursor: 'pointer' }}
onClick={() => navigate(-1)}
/>
<ArrowBackIcon className={classes.arrowIcon} onClick={() => navigate(-1)} />
<Breadcrumbs separator="/" aria-label="breadcrumb">
<Link to="/">
<Typography variant="body1" className={classes.explore}>

View File

@ -283,6 +283,7 @@ function SearchSuggestion({ setSearchCurrentValue = () => {} }) {
<InputBase
placeholder={'Search for content...'}
className={`${classes.input} ${isComponentFocused && classes.inputFocused}`}
sx={{ input: { '&::placeholder': { opacity: 1 } } }}
onKeyUp={handleSearch}
onFocus={() => openMenu()}
{...getInputProps()}

View File

@ -1,13 +1,16 @@
// react global
import React, { useEffect, useMemo, useRef, useState } from 'react';
// external
import { DateTime } from 'luxon';
// utility
import { api, endpoints } from '../../api';
import { useParams, useNavigate, createSearchParams } from 'react-router-dom';
// components
import Tags from './Tabs/Tags.jsx';
import { Box, Card, CardContent, CardMedia, Chip, Grid, Stack, Tab, Typography } from '@mui/material';
import { Card, CardContent, CardMedia, Chip, Grid, Stack, Tooltip, Typography } from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import { host } from '../../host';
@ -16,19 +19,17 @@ import repocube1 from '../../assets/repocube-1.png';
import repocube2 from '../../assets/repocube-2.png';
import repocube3 from '../../assets/repocube-3.png';
import repocube4 from '../../assets/repocube-4.png';
import { TabContext, TabList, TabPanel } from '@mui/lab';
import RepoDetailsMetadata from './RepoDetailsMetadata';
import Loading from '../Shared/Loading';
import { Markdown } from 'utilities/MarkdowntojsxWrapper';
import { isEmpty, uniq } from 'lodash';
import { VulnerabilityIconCheck, SignatureIconCheck } from 'utilities/vulnerabilityAndSignatureCheck';
import { mapToRepoFromRepoInfo } from 'utilities/objectModels';
const useStyles = makeStyles((theme) => ({
pageWrapper: {
backgroundColor: '#FFFFFF',
display: 'flex',
flexFlow: 'column',
backgroundColor: 'transparent',
height: '100%'
},
container: {
@ -38,13 +39,14 @@ const useStyles = makeStyles((theme) => ({
backgroundColor: '#FFFFFF'
},
repoName: {
fontWeight: '700',
fontWeight: '600',
fontSize: '1.5rem',
color: '#0F2139',
textAlign: 'left'
},
avatar: {
height: '3rem',
width: '3rem',
height: '1.438rem',
width: '1.438rem',
objectFit: 'fill'
},
cardBtn: {
@ -54,31 +56,16 @@ const useStyles = makeStyles((theme) => ({
media: {
borderRadius: '3.125em'
},
tabs: {
marginTop: '3rem',
padding: '0.5rem',
tags: {
marginTop: '1.5rem',
height: '100%',
[theme.breakpoints.down('md')]: {
padding: '0'
}
},
tabContent: {
height: '100%'
},
selectedTab: {
background: '#D83C0E',
borderRadius: '1.5rem'
},
tabPanel: {
height: '100%',
paddingLeft: '0rem!important',
[theme.breakpoints.down('md')]: {
padding: '1.5rem 0'
}
},
metadata: {
marginTop: '8rem',
paddingLeft: '1.5rem',
marginTop: '1.5rem',
paddingLeft: '1.25rem',
[theme.breakpoints.down('md')]: {
marginTop: '1rem',
paddingLeft: '0'
@ -88,11 +75,10 @@ const useStyles = makeStyles((theme) => ({
marginBottom: 2,
display: 'flex',
flexDirection: 'row',
alignItems: 'start',
alignItems: 'flex-start',
background: '#FFFFFF',
border: '0.0625rem solid #E0E5EB',
borderRadius: '2rem',
flex: 'none',
borderRadius: '0.75rem',
alignSelf: 'stretch',
flexGrow: 0,
order: 0,
@ -117,7 +103,6 @@ const useStyles = makeStyles((theme) => ({
boxShadow: 'none!important'
},
header: {
paddingLeft: '2rem',
[theme.breakpoints.down('md')]: {
padding: '0'
}
@ -127,17 +112,41 @@ const useStyles = makeStyles((theme) => ({
fontSize: '1rem',
lineHeight: '1.5rem',
color: 'rgba(0, 0, 0, 0.6)',
padding: '0.5rem 0 0 4rem',
padding: '1rem 0 0 0',
[theme.breakpoints.down('md')]: {
padding: '0.5rem 0 0 0'
}
},
platformChipsContainer: {
alignItems: 'center',
padding: '0.5rem 0 0 1rem',
padding: '0.15rem 0 0 0',
[theme.breakpoints.down('md')]: {
padding: '0.5rem 0 0 0'
}
},
platformChips: {
backgroundColor: '#E0E5EB',
color: '#52637A',
fontSize: '0.813rem',
lineHeight: '0.813rem',
borderRadius: '0.375rem',
padding: '0.313rem 0.625rem'
},
chipLabel: {
padding: '0'
},
vendor: {
color: theme.palette.primary,
fontSize: '0.75rem',
maxWidth: '50%',
textOverflow: 'ellipsis',
lineHeight: '1.125rem'
},
versionLast: {
color: theme.palette.secondary.dark,
fontSize: '0.75rem',
lineHeight: '1.125rem',
textOverflow: 'ellipsis'
}
}));
@ -156,7 +165,6 @@ function RepoDetails() {
const [tags, setTags] = useState([]);
const placeholderImage = useRef(randomImage());
const [isLoading, setIsLoading] = useState(true);
const [selectedTab, setSelectedTab] = useState('Overview');
// get url param from <Route here (i.e. image name)
const { name } = useParams();
const navigate = useNavigate();
@ -205,38 +213,25 @@ function RepoDetails() {
key={`${name}${platform}${index}`}
label={platform}
onClick={handlePlatformChipClick}
sx={{
backgroundColor: '#E0E5EB',
color: '#52637A',
fontSize: '0.625rem'
className={classes.platformChips}
classes={{
label: classes.chipLabel
}}
/>
));
};
const handleTabChange = (event, newValue) => {
setSelectedTab(newValue);
const getVendor = () => {
return `${repoDetailData.newestTag?.Vendor || 'Vendor not available'}`;
};
const renderOverview = () => {
return (
<Card className={classes.card} data-testid="overview-container">
<CardContent>
<Typography
variant="body1"
sx={{
color: 'rgba(0, 0, 0, 0.6)',
fontSize: '1rem',
lineHeight: '150%',
marginTop: '1.3rem',
alignSelf: 'stretch'
}}
>
{repoDetailData.description || 'Description not available'}
</Typography>
</CardContent>
</Card>
);
const getVersion = () => {
return `published ${repoDetailData.newestTag?.Tag}`;
};
const getLast = () => {
const lastDate = repoDetailData.lastUpdated
? DateTime.fromISO(repoDetailData.lastUpdated).toRelative({ unit: ['weeks', 'days', 'hours', 'minutes'] })
: `Timestamp N/A`;
return lastDate;
};
return (
@ -244,7 +239,8 @@ function RepoDetails() {
{isLoading ? (
<Loading />
) : (
<div className={classes.pageWrapper}>
<Grid container className={classes.pageWrapper}>
<Grid item xs={12} md={12}>
<Card className={classes.cardRoot}>
<CardContent>
<Grid container className={classes.header}>
@ -275,7 +271,6 @@ function RepoDetails() {
count={repoDetailData?.vulnerabilityCount}
/>
<SignatureIconCheck isSigned={repoDetailData.isSigned} />
{/* <BookmarkIcon sx={{color:"#52637A"}}/> */}
</Stack>
</Stack>
<Typography gutterBottom className={classes.repoTitle}>
@ -284,32 +279,34 @@ function RepoDetails() {
<Stack direction="row" spacing={1} className={classes.platformChipsContainer}>
{platformChips()}
</Stack>
<Stack alignItems="center" direction="row" spacing={1} pt={'0.5rem'}>
<Tooltip title={getVendor()} placement="top" className="hide-on-mobile">
<Typography className={classes.vendor} variant="body2" noWrap>
{<Markdown options={{ forceInline: true }}>{getVendor()}</Markdown>}
</Typography>
</Tooltip>
<Tooltip title={getVersion()} placement="top" className="hide-on-mobile">
<Typography className={classes.versionLast} variant="body2" noWrap>
{getVersion()}
</Typography>
</Tooltip>
<Tooltip title={repoDetailData.lastUpdated?.slice(0, 16) || ' '} placement="top">
<Typography className={classes.versionLast} variant="body2" noWrap>
{getLast()}
</Typography>
</Tooltip>
</Stack>
</Grid>
</Grid>
<Grid container>
<Grid item xs={12} md={8} className={classes.tabs}>
<TabContext value={selectedTab}>
<Box>
<TabList
onChange={handleTabChange}
TabIndicatorProps={{ className: classes.selectedTab }}
sx={{ '& button.Mui-selected': { color: '#14191F', fontWeight: '600' } }}
>
<Tab value="Overview" label="Overview" className={classes.tabContent} />
<Tab value="Tags" label="Tags" className={classes.tabContent} />
</TabList>
<Grid container>
<Grid item xs={12}>
<TabPanel value="Overview" className={classes.tabPanel}>
{renderOverview()}
</TabPanel>
<TabPanel value="Tags" className={classes.tabPanel}>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={8} className={classes.tags}>
<Card className={classes.cardRoot}>
<CardContent>
<Tags tags={tags} />
</TabPanel>
</Grid>
</Grid>
</Box>
</TabContext>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={4} className={classes.metadata}>
<RepoDetailsMetadata
@ -319,12 +316,10 @@ function RepoDetails() {
size={repoDetailData?.size}
latestTag={repoDetailData?.newestTag}
license={repoDetailData?.license}
description={repoDetailData?.description}
/>
</Grid>
</Grid>
</CardContent>
</Card>
</div>
)}
</>
);

View File

@ -5,26 +5,33 @@ import { Markdown } from 'utilities/MarkdowntojsxWrapper';
import React from 'react';
import transform from '../../utilities/transform';
const useStyles = makeStyles(() => ({
const useStyles = makeStyles((theme) => ({
card: {
marginBottom: 2,
display: 'flex',
flexDirection: 'row',
alignItems: 'start',
background: '#FFFFFF',
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
borderRadius: '1.5rem',
border: '0',
borderRadius: '0.5rem',
flex: 'none',
alignSelf: 'stretch',
flexGrow: 0,
order: 0,
width: '100%'
},
cardContent: {
'&:last-child': {
padding: '0.5rem 1rem'
}
},
metadataHeader: {
color: 'rgba(0, 0, 0, 0.6)'
color: theme.palette.secondary.dark,
fontSize: '0.75rem',
lineHeight: '1.125rem'
},
metadataBody: {
color: 'rgba(0, 0, 0, 0.87)',
color: theme.palette.primary,
fontFamily: 'Roboto',
fontStyle: 'normal',
fontWeight: 400,
@ -36,7 +43,7 @@ const useStyles = makeStyles(() => ({
function RepoDetailsMetadata(props) {
const classes = useStyles();
const { repoURL, totalDownloads, lastUpdated, size, license } = props;
const { repoURL, totalDownloads, lastUpdated, size, license, description } = props;
const lastDate = lastUpdated
? DateTime.fromISO(lastUpdated).toRelative({ unit: ['weeks', 'days', 'hours', 'minutes'] })
@ -45,7 +52,7 @@ function RepoDetailsMetadata(props) {
<Grid container spacing={1}>
<Grid container item xs={12}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<CardContent className={classes.cardContent}>
<Typography variant="body2" align="left" className={classes.metadataHeader}>
Repository
</Typography>
@ -57,7 +64,7 @@ function RepoDetailsMetadata(props) {
</Grid>
<Grid container item xs={12}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<CardContent className={classes.cardContent}>
<Typography variant="body2" align="left" className={classes.metadataHeader}>
Total downloads
</Typography>
@ -70,7 +77,7 @@ function RepoDetailsMetadata(props) {
<Grid container item xs={12} spacing={2}>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<CardContent className={classes.cardContent}>
<Typography variant="body2" align="left" className={classes.metadataHeader}>
Last publish
</Typography>
@ -84,7 +91,7 @@ function RepoDetailsMetadata(props) {
</Grid>
<Grid item xs={6}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<CardContent className={classes.cardContent}>
<Typography variant="body2" align="left" className={classes.metadataHeader}>
Total size
</Typography>
@ -98,7 +105,7 @@ function RepoDetailsMetadata(props) {
<Grid container item xs={12} spacing={2}>
<Grid item xs={12}>
<Card variant="outlined" className={classes.card}>
<CardContent>
<CardContent className={classes.cardContent}>
<Typography variant="body2" align="left" className={classes.metadataHeader}>
License
</Typography>
@ -111,6 +118,20 @@ function RepoDetailsMetadata(props) {
</Card>
</Grid>
</Grid>
<Grid container item xs={12} spacing={2}>
<Grid item xs={12}>
<Card variant="outlined" className={classes.card}>
<CardContent className={classes.cardContent}>
<Typography variant="body2" align="left" className={classes.metadataHeader}>
Description
</Typography>
<Typography variant="body1" align="left" className={classes.metadataBody}>
{description ? <Markdown>{description}</Markdown> : `Description not available`}
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</Grid>
);
}

View File

@ -3,14 +3,14 @@ import React, { useState } from 'react';
// components
import Typography from '@mui/material/Typography';
import { Card, CardContent, Divider, Stack, InputBase, FormControl, Select, InputLabel, MenuItem } from '@mui/material';
import { Card, CardContent, Stack, InputBase, FormControl, Select, InputLabel, MenuItem } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import { makeStyles } from '@mui/styles';
import TagCard from '../../Shared/TagCard';
import { tagsSortByCriteria } from 'utilities/sortCriteria';
const useStyles = makeStyles(() => ({
tagCard: {
tagContainer: {
marginBottom: 2,
display: 'flex',
flexDirection: 'row',
@ -24,20 +24,6 @@ const useStyles = makeStyles(() => ({
order: 0,
width: '100%'
},
card: {
marginBottom: '2rem',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
background: '#FFFFFF',
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
borderRadius: '1.875rem',
flex: 'none',
alignSelf: 'stretch',
flexGrow: 0,
order: 0,
width: '100%'
},
content: {
textAlign: 'left',
color: '#606060',
@ -49,13 +35,15 @@ const useStyles = makeStyles(() => ({
},
search: {
position: 'relative',
minWidth: '100%',
maxWidth: '100%',
flexDirection: 'row',
marginBottom: '1.7rem',
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
border: '0.125rem solid #E7E7E7',
borderRadius: '1rem',
zIndex: 1155
alignItems: 'center',
justifyContent: 'space-between',
marginTop: '1rem',
marginBottom: '1rem',
boxShadow: 'none',
border: '0.063rem solid #E7E7E7',
borderRadius: '0.625rem'
},
searchIcon: {
color: '#52637A',
@ -63,8 +51,12 @@ const useStyles = makeStyles(() => ({
},
input: {
color: '#464141',
marginLeft: 1,
width: '90%'
fontSize: '1rem',
paddingLeft: '1rem',
width: '90%',
'&::placeholder': {
opacity: '1'
}
}
}));
@ -73,6 +65,7 @@ export default function Tags(props) {
const { tags } = props;
const [tagsFilter, setTagsFilter] = useState('');
const [sortFilter, setSortFilter] = useState(tagsSortByCriteria.updateTimeDesc.value);
const renderTags = (tags) => {
const selectedSort = Object.values(tagsSortByCriteria).find((sc) => sc.value === sortFilter);
const filteredTags = tags.filter((t) => t.tag?.includes(tagsFilter));
@ -106,7 +99,7 @@ export default function Tags(props) {
};
return (
<Card className={classes.tagCard} data-testid="tags-container">
<Card className={classes.tagContainer} data-testid="tags-container">
<CardContent className={classes.content}>
<Stack direction="row" justifyContent="space-between">
<Typography
@ -136,26 +129,12 @@ export default function Tags(props) {
</FormControl>
</div>
</Stack>
<Divider
variant="fullWidth"
sx={{
margin: '5% 0% 5% 0%',
background: 'rgba(0, 0, 0, 0.38)',
height: '0.00625rem',
width: '100%'
}}
/>
<Stack
className={classes.search}
direction="row"
alignItems="center"
justifyContent="space-between"
spacing={2}
>
<Stack className={classes.search}>
<InputBase
style={{ paddingLeft: 10, height: 46, color: 'rgba(0, 0, 0, 0.6)' }}
placeholder={'Search for Tags...'}
className={classes.input}
style={{ paddingLeft: 10, height: 40, color: 'rgba(0, 0, 0, 0.6)' }}
placeholder={'Search tags...'}
// className={classes.input}
classes={{ input: classes.input }}
value={tagsFilter}
onChange={handleTagsFilterChange}
/>

View File

@ -40,7 +40,7 @@ const randomImage = () => {
return imageArray[randomIntFromInterval(0, 3)];
};
const useStyles = makeStyles(() => ({
const useStyles = makeStyles((theme) => ({
card: {
marginBottom: '1rem',
display: 'flex',
@ -112,7 +112,7 @@ const useStyles = makeStyles(() => ({
marginLeft: 10
},
vendor: {
color: '#14191F',
color: theme.palette.primary,
fontSize: '0.75rem',
maxWidth: '50%',
textOverflow: 'ellipsis',
@ -127,7 +127,7 @@ const useStyles = makeStyles(() => ({
paddingTop: '1rem'
},
versionLast: {
color: '#52637A',
color: theme.palette.secondary.dark,
fontSize: '0.75rem',
lineHeight: '1.125rem',
textOverflow: 'ellipsis'

View File

@ -1,35 +1,22 @@
import React, { useState } from 'react';
import { makeStyles } from '@mui/styles';
import { useNavigate } from 'react-router-dom';
import { Box, Card, CardContent, Collapse, Grid, Stack, Tooltip, Typography } from '@mui/material';
import { Box, Card, CardContent, Collapse, Grid, Stack, Tooltip, Typography, Divider } from '@mui/material';
import { Markdown } from 'utilities/MarkdowntojsxWrapper';
import transform from 'utilities/transform';
import { DateTime } from 'luxon';
import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
const useStyles = makeStyles(() => ({
tagCard: {
marginBottom: 2,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
background: '#FFFFFF',
boxShadow: 'none!important',
borderRadius: '1.875rem',
flex: 'none',
alignSelf: 'stretch',
flexGrow: 0,
order: 0,
width: '100%'
},
const useStyles = makeStyles((theme) => ({
card: {
marginBottom: '2rem',
marginBottom: '1rem',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
background: '#FFFFFF',
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
borderRadius: '1.875rem',
boxShadow: 'none',
border: '1px solid #E0E5EB',
borderRadius: '0.75rem',
flex: 'none',
alignSelf: 'stretch',
flexGrow: 0,
@ -56,6 +43,30 @@ const useStyles = makeStyles(() => ({
fontWeight: '600',
cursor: 'pointer',
textAlign: 'center'
},
tagHeading: {
color: '#828282',
fontSize: '1rem',
marginBottom: '0.5rem'
},
tagName: {
color: '#1479FF',
fontSize: '1rem',
marginBottom: '0.5rem',
textDecorationLine: 'underline',
cursor: 'pointer'
},
cardDivider: {
marginTop: '1rem',
marginBottom: '1rem',
border: '1px solid #E0E5EB'
},
manifsetsTable: {
marginTop: '1rem'
},
tableHeaderText: {
color: theme.palette.secondary.dark,
fontSize: '1rem'
}
}));
@ -81,22 +92,17 @@ export default function TagCard(props) {
return (
<Card className={classes.card} raised>
<CardContent className={classes.content}>
<Typography variant="body1" align="left" sx={{ color: '#828282', fontSize: '1rem', paddingBottom: '0.5rem' }}>
<Typography variant="body1" align="left" className={classes.tagHeading}>
Tag
</Typography>
<Typography
variant="body1"
align="left"
sx={{ color: '#1479FF', fontSize: '1rem', textDecorationLine: 'underline', cursor: 'pointer' }}
onClick={() => goToTags()}
>
<Typography variant="body1" align="left" className={classes.tagName} onClick={() => goToTags()}>
{repoName && `${repoName}:`}
{tag}
</Typography>
<Stack sx={{ display: 'inline' }} direction="row" spacing={0.5}>
<Typography variant="caption" sx={{ fontWeight: '400', fontSize: '0.8125rem' }}>
Pushed
Created
</Typography>
<Tooltip title={lastUpdated?.slice(0, 16) || ' '} placement="top">
<Typography variant="caption" sx={{ fontWeight: '600', fontSize: '0.8125rem' }}>
@ -104,6 +110,7 @@ export default function TagCard(props) {
</Typography>
</Tooltip>
</Stack>
<Divider variant="fullWidth" className={classes.cardDivider} />
<Stack direction="row" onClick={() => setOpen(!open)}>
{!open ? (
<KeyboardArrowRight className={classes.dropdownText} />
@ -123,22 +130,30 @@ export default function TagCard(props) {
</Typography>
</Stack>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box>
<Box className={classes.manifsetsTable}>
<Grid container item xs={12} direction={'row'}>
<Grid item xs={6} md={4}>
<Typography variant="body1">DIGEST</Typography>
<Grid item xs={6} md={6}>
<Typography variant="body1" className={classes.tableHeaderText}>
DIGEST
</Typography>
</Grid>
<Grid item xs={6} md={4} sx={{ display: 'flex', justifyContent: 'center' }}>
<Grid item xs={6} md={3} className={classes.tableHeaderText}>
<Typography variant="body1">OS/Arch</Typography>
</Grid>
<Grid item xs={0} md={4} className="hide-on-mobile" sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Typography variant="body1"> Size </Typography>
<Grid
item
xs={0}
md={3}
className={`${classes.tableHeaderText} hide-on-mobile`}
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
<Typography variant="body1"> COMPRESSED SIZE </Typography>
</Grid>
</Grid>
{manifests.map((el) => (
<Grid container item xs={12} key={el.digest} direction={'row'}>
<Grid item xs={6} md={4}>
<Grid item xs={6} md={6}>
<Tooltip title={el.digest || ''} placement="top">
<Typography
variant="body1"
@ -149,19 +164,19 @@ export default function TagCard(props) {
</Typography>
</Tooltip>
</Grid>
<Grid item xs={6} md={4} sx={{ display: 'flex', justifyContent: 'center' }}>
<Typography variant="body1">
<Grid item xs={6} md={3}>
<Typography variant="body1" color="primary">
{el.platform?.Os}/{el.platform?.Arch}
</Typography>
</Grid>
<Grid
item
xs={0}
md={4}
md={3}
className="hide-on-mobile"
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
<Typography sx={{ textAlign: 'right' }} variant="body1">
<Typography sx={{ textAlign: 'right' }} variant="body1" color="primary">
{transform.formatBytes(el.size)}
</Typography>
</Grid>

View File

@ -26,9 +26,7 @@ const useStyles = makeStyles(() => ({
height: '100vh'
},
gridWrapper: {
paddingTop: 10,
paddingBottom: 10,
backgroundColor: '#fff',
backgroundColor: 'transparent',
width: '100%',
display: 'flex',
flexFlow: 'column',