Skip to content

Commit 20983da

Browse files
authored
Merge branch 'master' into learning-card
2 parents f50c664 + 935efaf commit 20983da

6 files changed

Lines changed: 821 additions & 33 deletions

File tree

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
2+
import { Avatar, styled } from '@mui/material';
3+
import React, { useEffect, useState } from 'react';
4+
import { Grid } from '../../base';
5+
import { CloneIcon, CommunityClassIcon, OfficialClassIcon, OpenIcon, ShareIcon } from '../../icons';
6+
import VerificationClassIcon from '../../icons/ContentClassIcons/VerificationClassIcon';
7+
import DeploymentsIcon from '../../icons/Deployments/DeploymentsIcon';
8+
import { DownloadIcon } from '../../icons/Download';
9+
import { CustomTooltip } from '../CustomTooltip';
10+
import {
11+
CardBack,
12+
CardFront,
13+
DateText,
14+
DateType,
15+
DesignAuthorName,
16+
DesignCard,
17+
DesignDetailsDiv,
18+
DesignInnerCard,
19+
DesignName,
20+
DesignType,
21+
MetricsContainerFront,
22+
MetricsCount,
23+
MetricsDiv,
24+
NoTechnologyText,
25+
ProfileSection,
26+
StyledClassWrapper,
27+
StyledInnerClassWrapper,
28+
TechnologiesSection,
29+
TechnologyText,
30+
VersionDiv,
31+
VersionText
32+
} from './style';
33+
34+
export const DesignCardUrl = styled('a')(() => ({
35+
textDecoration: 'none'
36+
}));
37+
38+
interface Pattern {
39+
name: string;
40+
download_count: number;
41+
clone_count: number;
42+
view_count: number;
43+
deployment_count: number;
44+
share_count: number;
45+
userData?: {
46+
version?: string;
47+
avatarUrl?: string;
48+
userName?: string;
49+
technologies?: string[];
50+
updatedAt?: string;
51+
};
52+
catalog_data?: {
53+
content_class?: string;
54+
imageURL?: string;
55+
compatibility?: string[];
56+
};
57+
visibility: string;
58+
updated_at: Date;
59+
}
60+
61+
type CatalogCardProps = {
62+
pattern: Pattern;
63+
patternType: string;
64+
cardLink: string;
65+
cardHeight: string;
66+
cardWidth: string;
67+
cardStyles: React.CSSProperties;
68+
type: string;
69+
version?: string;
70+
avatarUrl: string;
71+
compatibility: string[];
72+
userName: string;
73+
technologies: string[];
74+
updatedAt: string;
75+
shouldFlip?: boolean;
76+
cardTechnologies?: boolean;
77+
isDetailed?: boolean;
78+
cardAvatarUrl?: boolean;
79+
date?: boolean;
80+
cardVersion?: boolean;
81+
UserName?: string;
82+
children?: React.ReactNode; // catalogImage
83+
TechnologyComponent?: React.ReactNode;
84+
basePath?: string; // path of meshmodel img stored
85+
getHostUrl?: () => string;
86+
};
87+
88+
export const ClassToIconMap = {
89+
community: <CommunityClassIcon width="16px" height="12px" />,
90+
official: <OfficialClassIcon width="16px" height="12px" />,
91+
verified: <VerificationClassIcon width="16px" height="12px" />
92+
};
93+
94+
const ClassWrap = ({ catalogClassName }: { catalogClassName: string }) => {
95+
if (!catalogClassName) return <></>;
96+
97+
return (
98+
<StyledClassWrapper>
99+
<StyledInnerClassWrapper catalogClassName={catalogClassName}>
100+
{catalogClassName}
101+
</StyledInnerClassWrapper>
102+
</StyledClassWrapper>
103+
);
104+
};
105+
106+
const CustomCatalogCard: React.FC<CatalogCardProps> = ({
107+
pattern,
108+
patternType,
109+
cardHeight,
110+
cardWidth,
111+
cardStyles,
112+
cardLink,
113+
shouldFlip,
114+
isDetailed,
115+
cardTechnologies,
116+
cardVersion,
117+
avatarUrl,
118+
UserName,
119+
children,
120+
basePath,
121+
getHostUrl
122+
}) => {
123+
const outerStyles = {
124+
height: cardHeight,
125+
width: cardWidth,
126+
...cardStyles
127+
};
128+
129+
const technologies = pattern.catalog_data?.compatibility || []; // an array
130+
const techlimit = 5;
131+
const [availableTechnologies, setAvailableTechnologies] = useState<string[]>([]);
132+
const checkImageUrlValidity = async (url: string, appendHostUrl = true) => {
133+
return new Promise((resolve) => {
134+
const img = new Image();
135+
// Only append host if the URL does not start with "http" or "https"
136+
if (appendHostUrl && !url.startsWith('http')) {
137+
img.src = (getHostUrl ? getHostUrl() : '') + url;
138+
} else {
139+
img.src = url;
140+
}
141+
img.onload = () => {
142+
// Check if the image loaded successfully
143+
resolve(true);
144+
};
145+
146+
img.onerror = () => {
147+
// Handle the case where the image could not be loaded
148+
resolve(false);
149+
};
150+
});
151+
};
152+
153+
const handleImage = async () => {
154+
const validSvgPaths = [];
155+
for (const technology of technologies) {
156+
const svgIconPath = `${basePath}/${technology.toLowerCase()}/icons/color/${technology.toLowerCase()}-color.svg`;
157+
const isSvgPathValid = await checkImageUrlValidity(svgIconPath as string);
158+
if (isSvgPathValid) {
159+
validSvgPaths.push(technology);
160+
}
161+
}
162+
163+
setAvailableTechnologies(validSvgPaths);
164+
};
165+
useEffect(() => {
166+
handleImage();
167+
// eslint-disable-next-line react-hooks/exhaustive-deps
168+
}, []);
169+
170+
if (!shouldFlip) {
171+
return (
172+
<DesignCard shouldFlip={shouldFlip} isDetailed={isDetailed} outerStyles={outerStyles}>
173+
<DesignInnerCard shouldFlip={shouldFlip} className="innerCard">
174+
<CardFront shouldFlip={shouldFlip} isDetailed={isDetailed}>
175+
{children}
176+
</CardFront>
177+
</DesignInnerCard>
178+
</DesignCard>
179+
);
180+
}
181+
182+
return (
183+
<DesignCardUrl href={cardLink} target="_blank" rel="noreferrer">
184+
<DesignCard shouldFlip={shouldFlip} isDetailed={isDetailed} outerStyles={outerStyles}>
185+
<DesignInnerCard shouldFlip={shouldFlip} className="innerCard">
186+
<CardFront shouldFlip={shouldFlip} isDetailed={isDetailed}>
187+
{isDetailed && (
188+
<>
189+
<ClassWrap catalogClassName={pattern?.catalog_data?.content_class ?? ''} />
190+
<DesignType>{patternType}</DesignType>
191+
192+
<DesignName
193+
style={{
194+
color: '#000D12',
195+
margin: '3rem 0 1.59rem 0',
196+
textAlign: 'center'
197+
}}
198+
>
199+
{pattern.name}
200+
</DesignName>
201+
</>
202+
)}
203+
<DesignDetailsDiv>
204+
<div
205+
style={{
206+
background: 'rgba(231, 239, 243, 0.40)',
207+
display: 'flex',
208+
alignItems: 'center',
209+
justifyContent: 'center',
210+
padding: '0.5rem',
211+
width: '100%',
212+
borderRadius: '0.5rem'
213+
}}
214+
>
215+
{children}
216+
</div>
217+
</DesignDetailsDiv>
218+
{isDetailed && (
219+
<MetricsContainerFront isDetailed={isDetailed}>
220+
<MetricsDiv>
221+
<DownloadIcon width={18} height={18} />
222+
<MetricsCount>{pattern.download_count}</MetricsCount>
223+
</MetricsDiv>
224+
<MetricsDiv>
225+
<CloneIcon width={18} height={18} fill={'#51636B'} />
226+
<MetricsCount>{pattern.clone_count}</MetricsCount>
227+
</MetricsDiv>
228+
<MetricsDiv>
229+
<OpenIcon width={18} height={18} fill={'#51636B'} />
230+
<MetricsCount>{pattern.view_count}</MetricsCount>
231+
</MetricsDiv>
232+
<MetricsDiv>
233+
<DeploymentsIcon width={18} height={18} />
234+
<MetricsCount>{pattern.deployment_count}</MetricsCount>
235+
</MetricsDiv>
236+
<MetricsDiv>
237+
<ShareIcon width={18} height={18} fill={'#51636B'} />
238+
<MetricsCount>{pattern.share_count}</MetricsCount>
239+
</MetricsDiv>
240+
</MetricsContainerFront>
241+
)}
242+
</CardFront>
243+
{shouldFlip && (
244+
<CardBack isCatalog={true}>
245+
<ProfileSection>
246+
<Avatar
247+
alt="Design Author"
248+
src={avatarUrl}
249+
sx={{ width: '32px', height: '32px', color: '#293B43' }}
250+
/>
251+
<DesignAuthorName>{UserName}</DesignAuthorName>
252+
</ProfileSection>
253+
254+
<DesignDetailsDiv style={{ marginTop: '0.7rem', gap: '5px' }}>
255+
{cardTechnologies && (
256+
<TechnologiesSection>
257+
<TechnologyText>Technologies</TechnologyText>
258+
<Grid
259+
container
260+
style={{ gap: '4px', alignItems: 'flex-start', flexWrap: 'nowrap' }}
261+
>
262+
{technologies.length < 1 || availableTechnologies.length < 1 ? (
263+
<NoTechnologyText>No technologies</NoTechnologyText>
264+
) : (
265+
<>
266+
{availableTechnologies.slice(0, techlimit).map((technology, index) => {
267+
const svgPath =
268+
(getHostUrl ? getHostUrl() : '') +
269+
`${basePath}/${technology.toLowerCase()}/icons/color/${technology.toLowerCase()}-color.svg`;
270+
return (
271+
<Grid item key={index}>
272+
<CustomTooltip key={index} title={technology.toLowerCase()}>
273+
<img
274+
height="24px"
275+
width="24px"
276+
alt={technology.toLowerCase()}
277+
src={svgPath}
278+
/>
279+
</CustomTooltip>
280+
</Grid>
281+
);
282+
})}
283+
{availableTechnologies.length > techlimit && (
284+
<Grid
285+
item
286+
sx={{
287+
padding: '0 8px 0 4px',
288+
borderRadius: '16px',
289+
border: '1px solid #C9DBE3',
290+
background: '#E7EFF3',
291+
color: '#3C494E',
292+
fontSize: '14px',
293+
lineHeight: '1.5',
294+
fontWeight: '600'
295+
}}
296+
>
297+
+{availableTechnologies.length - techlimit}
298+
</Grid>
299+
)}
300+
</>
301+
)}
302+
</Grid>
303+
</TechnologiesSection>
304+
)}
305+
</DesignDetailsDiv>
306+
307+
{isDetailed && (
308+
<DesignDetailsDiv style={{ marginTop: '50px' }}>
309+
<Grid container style={{ justifyContent: 'space-between', alignItems: 'center' }}>
310+
<Grid
311+
item
312+
style={{
313+
width: '100%',
314+
display: 'flex',
315+
alignItems: 'center',
316+
justifyContent: 'space-between'
317+
}}
318+
>
319+
<div
320+
style={{
321+
display: 'flex',
322+
flexDirection: 'row',
323+
gap: '0.2rem',
324+
alignItems: 'center'
325+
}}
326+
>
327+
<CalendarMonthIcon width={18} height={18} />
328+
<DateType>Updated At</DateType>
329+
</div>
330+
<DateText>
331+
{' '}
332+
{new Date(pattern.updated_at.toString().slice(0, 10)).toLocaleDateString(
333+
'en-US',
334+
{
335+
day: 'numeric',
336+
month: 'long',
337+
year: 'numeric'
338+
}
339+
)}
340+
</DateText>
341+
</Grid>
342+
</Grid>
343+
</DesignDetailsDiv>
344+
)}
345+
{cardVersion && (
346+
<VersionDiv>
347+
<VersionText>v{cardVersion}</VersionText>
348+
</VersionDiv>
349+
)}
350+
</CardBack>
351+
)}
352+
</DesignInnerCard>
353+
</DesignCard>
354+
</DesignCardUrl>
355+
);
356+
};
357+
358+
export default CustomCatalogCard;

src/custom/CustomCatalog/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import CustomCatalogCard from './custom-card';
2+
3+
export { CustomCatalogCard };

0 commit comments

Comments
 (0)