forked from sombochea/verdaccio-ui
Refactor: Updated developers component structure (#360)
This commit is contained in:
parent
eef2913dd5
commit
3a9f66c023
@ -1,37 +0,0 @@
|
|||||||
import React, { FC } from 'react';
|
|
||||||
|
|
||||||
import { isEmail } from '../../utils/url';
|
|
||||||
import Tooltip from '../../muiComponents/Tooltip';
|
|
||||||
import Avatar from '../../muiComponents/Avatar';
|
|
||||||
|
|
||||||
export interface AvatarDeveloper {
|
|
||||||
name: string;
|
|
||||||
packageName: string;
|
|
||||||
version: string;
|
|
||||||
avatar: string;
|
|
||||||
email: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AvatarTooltip: FC<AvatarDeveloper> = ({ name, packageName, version, avatar, email }) => {
|
|
||||||
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
|
|
||||||
function renderLinkForMail(
|
|
||||||
email: string,
|
|
||||||
avatarComponent: JSX.Element,
|
|
||||||
packageName: string,
|
|
||||||
version: string
|
|
||||||
): JSX.Element {
|
|
||||||
if (!email || isEmail(email) === false) {
|
|
||||||
return avatarComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
|
|
||||||
{avatarComponent}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Tooltip title={name}>{renderLinkForMail(email, avatarComponent, packageName, version)}</Tooltip>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { AvatarTooltip };
|
|
@ -1,4 +0,0 @@
|
|||||||
import { AvatarTooltip } from './AvatarTooltip';
|
|
||||||
|
|
||||||
export { AvatarTooltip };
|
|
||||||
export default AvatarTooltip;
|
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react';
|
|||||||
|
|
||||||
import ActionBar from '../ActionBar';
|
import ActionBar from '../ActionBar';
|
||||||
import Author from '../Author';
|
import Author from '../Author';
|
||||||
import Developers from '../Developers';
|
import Developers, { DeveloperType } from '../Developers';
|
||||||
import Dist from '../Dist/Dist';
|
import Dist from '../Dist/Dist';
|
||||||
import Engine from '../Engines/Engines';
|
import Engine from '../Engines/Engines';
|
||||||
import Install from '../Install';
|
import Install from '../Install';
|
||||||
@ -28,8 +28,6 @@ const renderLatestDescription = (description, version, isLatest = true): JSX.Ele
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderCopyCLI = (): JSX.Element => <Install />;
|
const renderCopyCLI = (): JSX.Element => <Install />;
|
||||||
const renderMaintainers = (): JSX.Element => <Developers type="maintainers" />;
|
|
||||||
const renderContributors = (): JSX.Element => <Developers type="contributors" />;
|
|
||||||
const renderRepository = (): JSX.Element => <Repository />;
|
const renderRepository = (): JSX.Element => <Repository />;
|
||||||
const renderAuthor = (): JSX.Element => <Author />;
|
const renderAuthor = (): JSX.Element => <Author />;
|
||||||
const renderEngine = (): JSX.Element => <Engine />;
|
const renderEngine = (): JSX.Element => <Engine />;
|
||||||
@ -63,8 +61,8 @@ function renderSideBar(packageName, packageVersion, packageMeta): ReactElement<H
|
|||||||
{renderEngine()}
|
{renderEngine()}
|
||||||
{renderDist()}
|
{renderDist()}
|
||||||
{renderAuthor()}
|
{renderAuthor()}
|
||||||
{renderMaintainers()}
|
<Developers type={DeveloperType.MAINTAINERS} />
|
||||||
{renderContributors()}
|
<Developers type={DeveloperType.CONTRIBUTORS} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,8 +3,7 @@ import React from 'react';
|
|||||||
import { mount } from '../../utils/test-enzyme';
|
import { mount } from '../../utils/test-enzyme';
|
||||||
import { DetailContextProvider } from '../../pages/Version';
|
import { DetailContextProvider } from '../../pages/Version';
|
||||||
|
|
||||||
import Developers, { DevelopersType } from './Developers';
|
import Developers, { DeveloperType, Fab } from './Developers';
|
||||||
import { Fab } from './styles';
|
|
||||||
|
|
||||||
describe('test Developers', () => {
|
describe('test Developers', () => {
|
||||||
const packageMeta = {
|
const packageMeta = {
|
||||||
@ -35,14 +34,13 @@ describe('test Developers', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test('should render the component with no items', () => {
|
test('should render the component with no items', () => {
|
||||||
const type: DevelopersType = 'maintainers';
|
|
||||||
const packageMeta = {
|
const packageMeta = {
|
||||||
latest: {},
|
latest: {},
|
||||||
};
|
};
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<DetailContextProvider value={{ packageMeta }}>
|
<DetailContextProvider value={{ packageMeta }}>
|
||||||
<Developers type={type} />
|
<Developers type={DeveloperType.MAINTAINERS} />
|
||||||
</DetailContextProvider>
|
</DetailContextProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -50,11 +48,10 @@ describe('test Developers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render the component for maintainers with items', () => {
|
test('should render the component for maintainers with items', () => {
|
||||||
const type: DevelopersType = 'maintainers';
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<DetailContextProvider value={{ packageMeta }}>
|
<DetailContextProvider value={{ packageMeta }}>
|
||||||
<Developers type={type} />
|
<Developers type={DeveloperType.MAINTAINERS} />
|
||||||
</DetailContextProvider>
|
</DetailContextProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -62,11 +59,10 @@ describe('test Developers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render the component for contributors with items', () => {
|
test('should render the component for contributors with items', () => {
|
||||||
const type: DevelopersType = 'contributors';
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<DetailContextProvider value={{ packageMeta }}>
|
<DetailContextProvider value={{ packageMeta }}>
|
||||||
<Developers type={type} />
|
<Developers type={DeveloperType.CONTRIBUTORS} />
|
||||||
</DetailContextProvider>
|
</DetailContextProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -74,7 +70,6 @@ describe('test Developers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should test onClick the component avatar', () => {
|
test('should test onClick the component avatar', () => {
|
||||||
const type: DevelopersType = 'contributors';
|
|
||||||
const packageMeta = {
|
const packageMeta = {
|
||||||
latest: {
|
latest: {
|
||||||
packageName: 'foo',
|
packageName: 'foo',
|
||||||
@ -95,7 +90,7 @@ describe('test Developers', () => {
|
|||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<DetailContextProvider value={{ packageMeta }}>
|
<DetailContextProvider value={{ packageMeta }}>
|
||||||
<Developers type={type} visibleMax={1} />
|
<Developers type={DeveloperType.CONTRIBUTORS} visibleMax={1} />
|
||||||
</DetailContextProvider>
|
</DetailContextProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,60 +1,89 @@
|
|||||||
import React, { FC, Fragment } from 'react';
|
import React, { useState, useCallback, useContext, useEffect, useMemo } from 'react';
|
||||||
import Add from '@material-ui/icons/Add';
|
import Add from '@material-ui/icons/Add';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { DetailContext } from '../../pages/Version';
|
import { DetailContext } from '../../pages/Version';
|
||||||
import { AvatarTooltip } from '../AvatarTooltip';
|
import Tooltip from '../../muiComponents/Tooltip';
|
||||||
|
import Avatar from '../../muiComponents/Avatar';
|
||||||
|
import Box from '../../muiComponents/Box';
|
||||||
|
import Text from '../../muiComponents/Text';
|
||||||
|
import FloatingActionButton from '../../muiComponents/FloatingActionButton';
|
||||||
|
import { Theme } from '../../design-tokens/theme';
|
||||||
|
|
||||||
import { Details, StyledText, Content, Fab } from './styles';
|
import getUniqueDeveloperValues from './get-unique-developer-values';
|
||||||
|
|
||||||
export type DevelopersType = 'contributors' | 'maintainers';
|
export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
|
||||||
|
backgroundColor: props.theme && props.theme.palette.primary.main,
|
||||||
|
color: props.theme && props.theme.palette.white,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export enum DeveloperType {
|
||||||
|
CONTRIBUTORS = 'contributors',
|
||||||
|
MAINTAINERS = 'maintainers',
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: DevelopersType;
|
type: DeveloperType;
|
||||||
visibleMax?: number;
|
visibleMax?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const StyledText = styled(Text)<{ theme?: Theme }>(({ theme }) => ({
|
||||||
|
fontWeight: theme && theme.fontWeight.bold,
|
||||||
|
marginBottom: '10px',
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)({
|
||||||
|
'> *': {
|
||||||
|
margin: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const VISIBLE_MAX = 6;
|
export const VISIBLE_MAX = 6;
|
||||||
|
|
||||||
const Developers: FC<Props> = ({ type, visibleMax }) => {
|
const Developers: React.FC<Props> = ({ type, visibleMax = VISIBLE_MAX }) => {
|
||||||
const [visibleDevs, setVisibleDevs] = React.useState<number>(visibleMax || VISIBLE_MAX);
|
const detailContext = useContext(DetailContext);
|
||||||
const { packageMeta } = React.useContext(DetailContext);
|
|
||||||
|
|
||||||
const handleLoadMore = (): void => {
|
if (!detailContext) {
|
||||||
setVisibleDevs(visibleDevs + VISIBLE_MAX);
|
throw Error("The app's detail Context was not correct used");
|
||||||
};
|
|
||||||
|
|
||||||
const renderDeveloperDetails = ({ name, avatar, email }, packageMeta): JSX.Element => {
|
|
||||||
const { name: packageName, version } = packageMeta.latest;
|
|
||||||
|
|
||||||
return <AvatarTooltip avatar={avatar} email={email} name={name} packageName={packageName} version={version} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDevelopers = (developers, packageMeta): JSX.Element => {
|
|
||||||
const listVisibleDevelopers = developers.slice(0, visibleDevs);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<StyledText variant={'subtitle1'}>{type}</StyledText>
|
|
||||||
<Content>
|
|
||||||
{listVisibleDevelopers.map(developer => (
|
|
||||||
<Details key={developer.email}>{renderDeveloperDetails(developer, packageMeta)}</Details>
|
|
||||||
))}
|
|
||||||
{visibleDevs < developers.length && (
|
|
||||||
<Fab onClick={handleLoadMore} size="small">
|
|
||||||
<Add />
|
|
||||||
</Fab>
|
|
||||||
)}
|
|
||||||
</Content>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const developerList = packageMeta && packageMeta.latest[type];
|
|
||||||
if (!developerList || developerList.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderDevelopers(developerList, packageMeta);
|
const developers = useMemo(() => getUniqueDeveloperValues(detailContext.packageMeta?.latest[type]), [
|
||||||
|
detailContext.packageMeta,
|
||||||
|
type,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [visibleDevelopersMax, setVisibleDevelopersMax] = useState(visibleMax);
|
||||||
|
const [visibleDevelopers, setVisibleDevelopers] = useState(developers);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!developers) return;
|
||||||
|
setVisibleDevelopers(developers.slice(0, visibleDevelopersMax));
|
||||||
|
}, [developers, visibleDevelopersMax]);
|
||||||
|
|
||||||
|
const handleSetVisibleDevelopersMax = useCallback(() => {
|
||||||
|
setVisibleDevelopersMax(visibleDevelopersMax + VISIBLE_MAX);
|
||||||
|
}, [visibleDevelopersMax]);
|
||||||
|
|
||||||
|
if (!visibleDevelopers || !developers) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledText variant={'subtitle1'}>{type}</StyledText>
|
||||||
|
<StyledBox display="flex" flexWrap="wrap" margin="10px 0 10px 0">
|
||||||
|
{visibleDevelopers.map(visibleDeveloper => (
|
||||||
|
<Tooltip key={visibleDeveloper.email} title={visibleDeveloper.name}>
|
||||||
|
<Avatar alt={visibleDeveloper.name} src={visibleDeveloper.avatar} />
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
{visibleDevelopersMax < developers.length && (
|
||||||
|
<Fab onClick={handleSetVisibleDevelopersMax} size="small">
|
||||||
|
<Add />
|
||||||
|
</Fab>
|
||||||
|
)}
|
||||||
|
</StyledBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Developers;
|
export default Developers;
|
||||||
|
File diff suppressed because it is too large
Load Diff
12
src/components/Developers/get-unique-developer-values.ts
Normal file
12
src/components/Developers/get-unique-developer-values.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Developer } from '../../../types/packageMeta';
|
||||||
|
|
||||||
|
function getUniqueDeveloperValues(developers?: Array<Developer>): undefined | Array<Developer> {
|
||||||
|
if (!developers) return;
|
||||||
|
return developers.reduce(
|
||||||
|
(accumulator: Array<Developer>, current: Developer) =>
|
||||||
|
accumulator.some(developer => developer.email === current.email) ? accumulator : [...accumulator, current],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getUniqueDeveloperValues;
|
@ -1 +1 @@
|
|||||||
export { default } from './Developers';
|
export { default, DeveloperType } from './Developers';
|
||||||
|
@ -24,9 +24,16 @@ export interface PackageMetaInterface {
|
|||||||
type?: string;
|
type?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
};
|
};
|
||||||
|
maintainers?: Array<Developer>;
|
||||||
|
contributors?: Array<Developer>;
|
||||||
};
|
};
|
||||||
_uplinks: {};
|
_uplinks: {};
|
||||||
}
|
}
|
||||||
|
export interface Developer {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface LicenseInterface {
|
interface LicenseInterface {
|
||||||
type: string;
|
type: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user