| skipped 3 lines |
4 | 4 | | import Chip from '../Chip/Chip'; |
5 | 5 | | import clsx from 'clsx'; |
6 | 6 | | import ChipGroup from '../ChipGroup/ChipGroup'; |
7 | | - | import SitesList from '../SitesList/SitesList'; |
8 | 7 | | import { CSSTransition } from 'react-transition-group'; |
9 | 8 | | import Button from '../Button/Button'; |
10 | | - | import { |
11 | | - | LicenceSkeleton, |
12 | | - | LinksSkeleton, |
13 | | - | RatingSkeleton, |
14 | | - | ScriptSkeleton, |
15 | | - | } from './PackagePreviewSkeleton'; |
| 9 | + | import { LicenceSkeleton, LinksSkeleton, ScriptSkeleton } from './PackagePreviewSkeleton'; |
16 | 10 | | import ProblemBadge from '../ProblemBadge/ProblemBadge'; |
17 | 11 | | import { ChipGroupSkeleton } from '../ChipGroup/ChipGroupSkeleton'; |
18 | | - | import { SitesListSkeleton } from '../SitesList/SitesListSkeleton'; |
19 | | - | import BarChart from '../BarChart/BarChart'; |
20 | | - | import BarChartSkeleton from '../BarChart/BarChartSkeleton'; |
21 | | - | import { formatNumber } from 'utils/helpers'; |
22 | | - | import Hint from '../Tooltip/Hint'; |
23 | 12 | | import { useNavigate } from 'react-router-dom'; |
24 | | - | |
25 | | - | type Problem = 'vulnerabilities' | 'duplicate' | 'outdated'; |
26 | | - | |
27 | | - | type ExternalLink = { |
28 | | - | href: string; |
29 | | - | kind: 'repository' | 'link' | 'npm'; |
30 | | - | linkText?: string; |
31 | | - | }; |
| 13 | + | import { IdentifiedPackage } from 'store/selectors/websiteResults'; |
32 | 14 | | |
33 | 15 | | type Props = { |
34 | | - | /* |
35 | | - | name: string; |
36 | | - | version: string; |
37 | | - | desc: string; |
38 | | - | problems?: Problem[]; |
39 | | - | keywords: string[]; |
40 | | - | author: { |
41 | | - | name: string; |
42 | | - | image: string; |
43 | | - | }; |
44 | | - | */ |
45 | 16 | | opened?: boolean; |
46 | 17 | | detailsLoading?: boolean; |
47 | | - | sites: Site[]; |
48 | | - | flags: { |
49 | | - | vulnerable: boolean; |
50 | | - | duplicate: boolean; |
51 | | - | outdated: boolean; |
52 | | - | }; |
53 | | - | pkg: { |
54 | | - | name: string; |
55 | | - | descriptionFull: string; |
56 | | - | containingScriptUrl: string; |
57 | | - | version: string; |
58 | | - | license: string; |
59 | | - | licenseDescription: string; |
60 | | - | rating: number; |
61 | | - | ratingDelta: number; |
62 | | - | deps: string[]; // TODO: probably not just string[] |
63 | | - | repositoryUrl?: string; |
64 | | - | homePageUrl?: string; |
65 | | - | npmUrl?: string; |
66 | | - | keywords: Array<{ |
67 | | - | name: string; |
68 | | - | }>; |
69 | | - | author: { |
70 | | - | name: string; |
71 | | - | avatar: string; |
72 | | - | }; |
73 | | - | }; |
74 | | - | totalRatedPackages: number; |
| 18 | + | // sites: Site[]; |
| 19 | + | pkg: IdentifiedPackage; |
75 | 20 | | }; |
76 | 21 | | |
| 22 | + | function makeNpmUrl(pkg: IdentifiedPackage) { |
| 23 | + | return `https://www.npmjs.com/package/${pkg.name}`; |
| 24 | + | } |
| 25 | + | |
77 | 26 | | // TODO: refactor this (decomposition, props, memoization, etc) |
78 | 27 | | export default function PackagePreview({ |
79 | | - | /* name, |
80 | | - | version, |
81 | | - | desc, |
82 | | - | problems, |
83 | | - | keywords, |
84 | | - | author, |
85 | | - | opened, |
86 | | - | detailsLoading = false, |
87 | | - | */ |
88 | 28 | | opened, |
89 | | - | sites, |
90 | | - | flags, |
| 29 | + | //sites, |
91 | 30 | | pkg, |
92 | | - | totalRatedPackages, |
93 | 31 | | detailsLoading = false, |
94 | 32 | | }: Props) { |
95 | 33 | | const [open, setOpen] = useState<boolean>(opened ?? false); |
| skipped 12 lines |
108 | 46 | | } |
109 | 47 | | }; |
110 | 48 | | |
111 | | - | // TODO: Mock API data, remove later |
112 | | - | const externalLinks: ExternalLink[] = [ |
113 | | - | { kind: 'repository', href: 'https://github.com/facebook/react/', linkText: 'Repository' }, |
114 | | - | { kind: 'link', href: 'https://reactjs.org/', linkText: 'Homepage' }, |
115 | | - | { kind: 'npm', href: 'https://www.npmjs.com/package/react' }, |
116 | | - | ]; |
117 | | - | |
118 | | - | // TODO: Mock API data, remove later |
119 | | - | const loadedData = { |
120 | | - | script: '/rsrc.php/v3id044/yu/l/en_US/yD2XaVkWQHO.js?_nc_x=Ij3Wp8lg5Kz', |
121 | | - | license: { |
122 | | - | title: 'MIT license', |
123 | | - | subtitle: 'freely distributable', |
124 | | - | }, |
125 | | - | rating: { |
126 | | - | place: 385, |
127 | | - | rankingDelta: -4, |
128 | | - | out: 12842, |
129 | | - | }, |
130 | | - | dependencies: ['art', 'create-react-class', 'loose-envify', 'scheduler'], |
131 | | - | packages: [ |
132 | | - | { |
133 | | - | fill: 1, |
134 | | - | uses: 89912, |
135 | | - | moduleVersion: '21.3.0', |
136 | | - | }, |
137 | | - | { |
138 | | - | fill: 0.8, |
139 | | - | uses: 67111, |
140 | | - | moduleVersion: '18.2.0', |
141 | | - | highlighted: true, |
142 | | - | }, |
143 | | - | { |
144 | | - | fill: 0.7, |
145 | | - | uses: 44212, |
146 | | - | moduleVersion: '20.1.0', |
147 | | - | }, |
148 | | - | { |
149 | | - | fill: 0.6, |
150 | | - | uses: 41129, |
151 | | - | moduleVersion: '18.0.0', |
152 | | - | }, |
153 | | - | { |
154 | | - | fill: 0.5, |
155 | | - | uses: 40465, |
156 | | - | moduleVersion: '19.11.2', |
157 | | - | }, |
158 | | - | { |
159 | | - | fill: 0.4, |
160 | | - | uses: 38907, |
161 | | - | moduleVersion: '8.1.2', |
162 | | - | vulnerabilities: true, |
163 | | - | }, |
164 | | - | ], |
165 | | - | sites: [ |
166 | | - | { |
167 | | - | id: '123', |
168 | | - | image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg', |
169 | | - | name: 'pinterest.com', |
170 | | - | packagesCount: 151, |
171 | | - | }, |
172 | | - | { |
173 | | - | id: '456', |
174 | | - | image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg', |
175 | | - | name: 'pinterest.com', |
176 | | - | packagesCount: 151, |
177 | | - | }, |
178 | | - | { |
179 | | - | id: '789', |
180 | | - | image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg', |
181 | | - | name: 'pinterest.com', |
182 | | - | packagesCount: 151, |
183 | | - | }, |
184 | | - | { |
185 | | - | id: '1231', |
186 | | - | image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg', |
187 | | - | name: 'pinterest.com', |
188 | | - | packagesCount: 151, |
189 | | - | }, |
190 | | - | { |
191 | | - | id: '12321', |
192 | | - | image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg', |
193 | | - | name: 'pinterest.com', |
194 | | - | packagesCount: 151, |
195 | | - | }, |
196 | | - | { |
197 | | - | id: '123123', |
198 | | - | image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg', |
199 | | - | name: 'pinterest.com', |
200 | | - | packagesCount: 151, |
201 | | - | }, |
202 | | - | { |
203 | | - | id: '12123132', |
204 | | - | image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg', |
205 | | - | name: 'pinterest.com', |
206 | | - | packagesCount: 151, |
207 | | - | }, |
208 | | - | ], |
209 | | - | links: externalLinks, |
210 | | - | }; |
211 | | - | |
212 | | - | //const { script, license, rating, dependencies, packages, sites, links } = loadedData; |
| 49 | + | const deps = Object.keys( |
| 50 | + | pkg.registryMetadata?.versionSpecificValues?.[pkg.versionSet[pkg.versionSet.length - 1]] |
| 51 | + | .dependencies ?? {} |
| 52 | + | ); |
213 | 53 | | |
214 | 54 | | return ( |
215 | 55 | | <div className={clsx(styles.package, open && styles.open)}> |
| skipped 4 lines |
220 | 60 | | {pkg.name} <span className={styles.version}>{pkg.version}</span> |
221 | 61 | | </span> |
222 | 62 | | |
223 | | - | {flags && ( // TODO deal with flags |
| 63 | + | {(pkg.vulnerable || pkg.outdated || pkg.duplicate) && ( |
224 | 64 | | <span className={styles.problems}> |
225 | | - | {problems.map((problem) => ( |
226 | | - | <ProblemBadge key={problem} problem={problem} /> |
227 | | - | ))} |
| 65 | + | {pkg.vulnerable && <ProblemBadge problem='vulnerabilities' />} |
| 66 | + | {pkg.outdated && <ProblemBadge problem='outdated' />} |
| 67 | + | {/*pkg.duplicate && <ProblemBadge problem='duplicate' />*/} |
228 | 68 | | </span> |
229 | 69 | | )} |
230 | 70 | | </div> |
| skipped 10 lines |
241 | 81 | | </button> |
242 | 82 | | </div> |
243 | 83 | | |
244 | | - | <div className={styles.desc}>{pkg.descriptionFull}</div> |
| 84 | + | <div className={styles.desc}>{pkg.registryMetadata?.fullDescription}</div> |
245 | 85 | | </div> |
246 | 86 | | |
247 | 87 | | <CSSTransition |
| skipped 5 lines |
253 | 93 | | > |
254 | 94 | | <div className={styles.content}> |
255 | 95 | | <div className={styles.contentInner}> |
| 96 | + | {/* |
256 | 97 | | <div className={styles.stat}> |
257 | 98 | | <div className={styles.statHeader}> |
258 | 99 | | <Icon kind='script' color='#8E8AA0' className={styles.statIcon} /> |
| skipped 12 lines |
271 | 112 | | </a> |
272 | 113 | | )} |
273 | 114 | | </div> |
| 115 | + | */} |
274 | 116 | | |
275 | 117 | | <div className={styles.statList}> |
276 | 118 | | <div className={clsx(styles.stat, styles.statListItemSmall)}> |
| skipped 5 lines |
282 | 124 | | <LicenceSkeleton /> |
283 | 125 | | ) : ( |
284 | 126 | | <> |
285 | | - | <div className={styles.statTitle}>{pkg.license}</div> |
286 | | - | <div className={styles.statSubtitle}>{pkg.licenseDescription}</div> |
| 127 | + | <div className={styles.statTitle}>{pkg.registryMetadata?.license}</div> |
| 128 | + | {/* TODO |
| 129 | + | <div className={styles.statSubtitle}> |
| 130 | + | {pkg.registryMetadata?.licenseDescription} |
| 131 | + | </div>*/} |
287 | 132 | | </> |
288 | 133 | | )} |
289 | 134 | | </div> |
290 | 135 | | |
| 136 | + | {/* |
291 | 137 | | <div className={clsx(styles.stat, styles.statListItemSmall)}> |
292 | 138 | | <div className={styles.statHeader}> |
293 | 139 | | <Icon kind='rating' color='#8E8AA0' className={styles.statIcon} /> |
| skipped 29 lines |
323 | 169 | | </> |
324 | 170 | | )} |
325 | 171 | | </div> |
| 172 | + | */} |
326 | 173 | | |
327 | 174 | | <div className={clsx(styles.stat, styles.statListItemLarge)}> |
328 | 175 | | <div className={styles.statHeader}> |
| skipped 3 lines |
332 | 179 | | <ChipGroupSkeleton /> |
333 | 180 | | ) : ( |
334 | 181 | | <ChipGroup> |
335 | | - | {pkg.deps.map((dependency) => ( |
| 182 | + | {deps.map((dependency) => ( |
336 | 183 | | <Chip size='medium' fontSize='small' font='monospace'> |
337 | 184 | | {dependency} |
338 | 185 | | </Chip> |
| skipped 18 lines |
357 | 204 | | |
358 | 205 | | {/* TODO: add Modules treemap here */} |
359 | 206 | | |
| 207 | + | {/* |
360 | 208 | | <div className={styles.stat}> |
361 | 209 | | <div className={styles.statHeader}>Used on</div> |
362 | 210 | | |
| skipped 3 lines |
366 | 214 | | <SitesList sites={sites} className={styles.usedOnList} /> |
367 | 215 | | )} |
368 | 216 | | </div> |
| 217 | + | */} |
369 | 218 | | |
370 | 219 | | <div className={styles.actions}> |
371 | 220 | | <div className={styles.links}> |
| skipped 1 lines |
373 | 222 | | <LinksSkeleton /> |
374 | 223 | | ) : ( |
375 | 224 | | <> |
376 | | - | {pkg.repositoryUrl && ( |
| 225 | + | {pkg.registryMetadata?.repositoryUrl && ( |
377 | 226 | | <a |
378 | | - | href={pkg.repositoryUrl} |
| 227 | + | href={pkg.registryMetadata?.repositoryUrl} |
379 | 228 | | className={styles.link} |
380 | 229 | | target='_blank' |
381 | 230 | | rel='noreferrer' |
| skipped 3 lines |
385 | 234 | | </a> |
386 | 235 | | )} |
387 | 236 | | |
388 | | - | {pkg.homePageUrl && ( |
| 237 | + | {pkg.registryMetadata?.homepageUrl && ( |
389 | 238 | | <a |
390 | | - | href={pkg.homePageUrl} |
| 239 | + | href={pkg.registryMetadata?.homepageUrl} |
391 | 240 | | className={styles.link} |
392 | 241 | | target='_blank' |
393 | 242 | | rel='noreferrer' |
| skipped 3 lines |
397 | 246 | | </a> |
398 | 247 | | )} |
399 | 248 | | |
400 | | - | {pkg.npmUrl && ( |
401 | | - | <a |
402 | | - | href={pkg.npmUrl} |
403 | | - | className={styles.link} |
404 | | - | target='_blank' |
405 | | - | rel='noreferrer' |
406 | | - | > |
407 | | - | <Icon |
408 | | - | kind='npm' |
409 | | - | width={32} |
410 | | - | height={32} |
411 | | - | color='#212121' |
412 | | - | className={styles.linkIcon} |
413 | | - | /> |
414 | | - | </a> |
415 | | - | )} |
| 249 | + | <a |
| 250 | + | href={makeNpmUrl(pkg)} |
| 251 | + | className={styles.link} |
| 252 | + | target='_blank' |
| 253 | + | rel='noreferrer' |
| 254 | + | > |
| 255 | + | <Icon |
| 256 | + | kind='npm' |
| 257 | + | width={32} |
| 258 | + | height={32} |
| 259 | + | color='#212121' |
| 260 | + | className={styles.linkIcon} |
| 261 | + | /> |
| 262 | + | </a> |
416 | 263 | | </> |
417 | 264 | | )} |
418 | 265 | | </div> |
419 | 266 | | |
420 | | - | {/* TODO: should be a <a> link */} |
421 | | - | <Button variant='arrow' onClick={() => navigate('/package/' + pkg.name)}> |
| 267 | + | {/* TODO: should be a <a> link w/ router support */} |
| 268 | + | <Button variant='arrow' onClick={() => navigate(`/package/${pkg.name}`)}> |
422 | 269 | | Details |
423 | 270 | | </Button> |
424 | 271 | | </div> |
| skipped 7 lines |
432 | 279 | | {/* TODO: not sure how to conditionally render maximum number of keywords (e.g. 5 for |
433 | 280 | | desktop, 3/4 for tablet, 2 for mobile) based on viewport and update rest number |
434 | 281 | | of keywords beyond current maximum in Chip */} |
435 | | - | {pkg.keywords.slice(6).map((tag) => ( |
| 282 | + | {/* ^ Nearly impossible thing for responsive markup rendered on server. |
| 283 | + | Maybe just avoid conditional render? // oklimenko */} |
| 284 | + | {pkg.registryMetadata?.keywords?.slice(6).map((tag) => ( |
436 | 285 | | <a href='#' className={styles.tag}> |
437 | | - | {tag.name} |
| 286 | + | {tag} |
438 | 287 | | </a> |
439 | 288 | | ))} |
440 | | - | {pkg.keywords.length > 6 && ( |
| 289 | + | {(pkg.registryMetadata?.keywords?.length ?? 0) > 6 && ( |
441 | 290 | | <Chip variant='info' size='medium' fontWeight='semiBold'> |
442 | | - | +{pkg.keywords.length - 6} |
| 291 | + | +{(pkg.registryMetadata?.keywords?.length ?? 0) - 6} |
443 | 292 | | </Chip> |
444 | 293 | | )} |
445 | 294 | | </div> |
446 | 295 | | |
447 | 296 | | <div className={styles.author}> |
448 | | - | <span className={styles.authorName}>{pkg.author.name}</span> |
449 | | - | <img className={styles.authorImage} src={pkg.author.avatar} alt='' /> |
| 297 | + | {/* TODO: print all maintainers? Author is not a single entity */} |
| 298 | + | <span className={styles.authorName}>{pkg.registryMetadata?.maintainers?.[0].name}</span> |
| 299 | + | <img |
| 300 | + | className={styles.authorImage} |
| 301 | + | src={pkg.registryMetadata?.maintainers?.[0].avatar} |
| 302 | + | alt='' |
| 303 | + | /> |
450 | 304 | | </div> |
451 | 305 | | </div> |
452 | 306 | | </div> |
| skipped 3 lines |