chore: sync with 4.x webui
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
|
61
src/app.js
@ -1,13 +1,17 @@
|
|||||||
import React, {Component, Fragment} from 'react';
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
import isNil from 'lodash/isNil';
|
import isNil from 'lodash/isNil';
|
||||||
|
|
||||||
import storage from './utils/storage';
|
import storage from './utils/storage';
|
||||||
import {makeLogin, isTokenExpire} from './utils/login';
|
import { makeLogin, isTokenExpire } from './utils/login';
|
||||||
|
|
||||||
import Loading from './components/Loading';
|
import Loading from './components/Loading';
|
||||||
import LoginModal from './components/Login';
|
import LoginModal from './components/Login';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import {Container, Content} from './components/Layout';
|
import { Container, Content } from './components/Layout';
|
||||||
import RouterApp from './router';
|
import RouterApp from './router';
|
||||||
import API from './utils/api';
|
import API from './utils/api';
|
||||||
import './styles/typeface-roboto.scss';
|
import './styles/typeface-roboto.scss';
|
||||||
@ -16,7 +20,6 @@ import 'normalize.css';
|
|||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
|
|
||||||
export const AppContext = React.createContext();
|
export const AppContext = React.createContext();
|
||||||
|
|
||||||
export const AppContextProvider = AppContext.Provider;
|
export const AppContextProvider = AppContext.Provider;
|
||||||
export const AppContextConsumer = AppContext.Consumer;
|
export const AppContextConsumer = AppContext.Consumer;
|
||||||
|
|
||||||
@ -39,12 +42,29 @@ export default class App extends Component {
|
|||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
componentDidUpdate(_, prevState) {
|
componentDidUpdate(_, prevState) {
|
||||||
const {isUserLoggedIn} = this.state;
|
const { isUserLoggedIn } = this.state;
|
||||||
if (prevState.isUserLoggedIn !== isUserLoggedIn) {
|
if (prevState.isUserLoggedIn !== isUserLoggedIn) {
|
||||||
this.loadOnHandler();
|
this.loadOnHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isLoading, isUserLoggedIn, packages, logoUrl, user, scope } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container isLoading={isLoading}>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loading />
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
<AppContextProvider value={{ isUserLoggedIn, packages, logoUrl, user, scope }}>{this.renderContent()}</AppContextProvider>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
{this.renderLoginModal()}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
isUserAlreadyLoggedIn = () => {
|
isUserAlreadyLoggedIn = () => {
|
||||||
// checks for token validity
|
// checks for token validity
|
||||||
const token = storage.getItem('token');
|
const token = storage.getItem('token');
|
||||||
@ -53,7 +73,7 @@ export default class App extends Component {
|
|||||||
this.handleLogout();
|
this.handleLogout();
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
user: {username, token},
|
user: { username, token },
|
||||||
isUserLoggedIn: true,
|
isUserLoggedIn: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -76,7 +96,7 @@ export default class App extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setLoading = (isLoading) =>
|
setLoading = isLoading =>
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading,
|
isLoading,
|
||||||
});
|
});
|
||||||
@ -86,7 +106,7 @@ export default class App extends Component {
|
|||||||
* Required by: <LoginModal /> <Header />
|
* Required by: <LoginModal /> <Header />
|
||||||
*/
|
*/
|
||||||
handleToggleLoginModal = () => {
|
handleToggleLoginModal = () => {
|
||||||
this.setState((prevState) => ({
|
this.setState(prevState => ({
|
||||||
showLoginModal: !prevState.showLoginModal,
|
showLoginModal: !prevState.showLoginModal,
|
||||||
error: {},
|
error: {},
|
||||||
}));
|
}));
|
||||||
@ -97,7 +117,7 @@ export default class App extends Component {
|
|||||||
* Required by: <Header />
|
* Required by: <Header />
|
||||||
*/
|
*/
|
||||||
handleDoLogin = async (usernameValue, passwordValue) => {
|
handleDoLogin = async (usernameValue, passwordValue) => {
|
||||||
const {username, token, error} = await makeLogin(usernameValue, passwordValue);
|
const { username, token, error } = await makeLogin(usernameValue, passwordValue);
|
||||||
|
|
||||||
if (username && token) {
|
if (username && token) {
|
||||||
this.setLoggedUser(username, token);
|
this.setLoggedUser(username, token);
|
||||||
@ -123,6 +143,7 @@ export default class App extends Component {
|
|||||||
showLoginModal: false, // set isUserLoggedIn to true
|
showLoginModal: false, // set isUserLoggedIn to true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logouts user
|
* Logouts user
|
||||||
* Required by: <Header />
|
* Required by: <Header />
|
||||||
@ -136,24 +157,8 @@ export default class App extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const {isLoading, isUserLoggedIn, packages, logoUrl, user, scope} = this.state;
|
|
||||||
return (
|
|
||||||
<Container isLoading={isLoading}>
|
|
||||||
{isLoading ? (
|
|
||||||
<Loading />
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
<AppContextProvider value={{isUserLoggedIn, packages, logoUrl, user, scope}}>{this.renderContent()}</AppContextProvider>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
{this.renderLoginModal()}
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLoginModal = () => {
|
renderLoginModal = () => {
|
||||||
const {error, showLoginModal} = this.state;
|
const { error, showLoginModal } = this.state;
|
||||||
return (
|
return (
|
||||||
<LoginModal
|
<LoginModal
|
||||||
error={error}
|
error={error}
|
||||||
@ -179,8 +184,8 @@ export default class App extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderHeader = () => {
|
renderHeader = () => {
|
||||||
const {logoUrl, user, scope} = this.state;
|
const { logoUrl, user: { username } = {}, scope } = this.state;
|
||||||
|
|
||||||
return <Header logo={logoUrl} onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal} scope={scope} username={user.username} />;
|
return <Header logo={logoUrl} onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal} scope={scope} username={username} />;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
16
src/app.scss
@ -1,16 +1,16 @@
|
|||||||
@import "./styles/variables";
|
@import './styles/variables';
|
||||||
|
|
||||||
.alertError {
|
.alertError {
|
||||||
background-color: $red !important;
|
background-color: $red !important;
|
||||||
min-width: inherit !important;
|
min-width: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alertErrorMsg {
|
.alertErrorMsg {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alertIcon {
|
.alertIcon {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
109
src/components/ActionBar/index.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/* eslint-disable react/jsx-max-depth */
|
||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
|
import List from '@material-ui/core/List/index';
|
||||||
|
|
||||||
|
import DownloadIcon from '@material-ui/icons/CloudDownload';
|
||||||
|
import BugReportIcon from '@material-ui/icons/BugReport';
|
||||||
|
import HomeIcon from '@material-ui/icons/Home';
|
||||||
|
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||||
|
|
||||||
|
import { Fab, ActionListItem } from './styles';
|
||||||
|
|
||||||
|
const ACTIONS = {
|
||||||
|
homepage: {
|
||||||
|
icon: <HomeIcon />,
|
||||||
|
title: 'Visit homepage',
|
||||||
|
},
|
||||||
|
issue: {
|
||||||
|
icon: <BugReportIcon />,
|
||||||
|
title: 'Open an issue',
|
||||||
|
},
|
||||||
|
tarball: {
|
||||||
|
icon: <DownloadIcon />,
|
||||||
|
title: 'Download tarball',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class ActionBar extends Component<any, any> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<DetailContextConsumer>
|
||||||
|
{context => {
|
||||||
|
return this.renderActionBar(context);
|
||||||
|
}}
|
||||||
|
</DetailContextConsumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderIconsWithLink(link, component) {
|
||||||
|
if (!link) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a href={link} target={"_blank"}>
|
||||||
|
{component}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActionBarListItems = (packageMeta) => {
|
||||||
|
const {
|
||||||
|
latest: {
|
||||||
|
bugs: {
|
||||||
|
url: issue,
|
||||||
|
} = {},
|
||||||
|
homepage,
|
||||||
|
dist: {
|
||||||
|
tarball,
|
||||||
|
} = {},
|
||||||
|
} = {},
|
||||||
|
} = packageMeta;
|
||||||
|
|
||||||
|
const actionsMap = {
|
||||||
|
homepage,
|
||||||
|
issue,
|
||||||
|
tarball,
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderList = Object.keys(actionsMap).reduce((component, value, key) => {
|
||||||
|
const link = actionsMap[value];
|
||||||
|
if (link) {
|
||||||
|
const fab = (
|
||||||
|
<Fab size={'small'}>
|
||||||
|
{ACTIONS[value]['icon']}
|
||||||
|
</Fab>
|
||||||
|
);
|
||||||
|
component.push(
|
||||||
|
<Tooltip key={key} title={ACTIONS[value]['title']}>
|
||||||
|
{this.renderIconsWithLink(link, fab)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ActionListItem alignItems={'flex-start'}>
|
||||||
|
{renderList}
|
||||||
|
</ActionListItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderActionBar = ({ packageMeta = {} }) => {
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
{this.renderActionBarListItems(packageMeta)}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionBar;
|
24
src/components/ActionBar/styles.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
import styled from 'react-emotion';
|
||||||
|
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
|
||||||
|
import colors from '../../utils/styles/colors';
|
||||||
|
|
||||||
|
export const ActionListItem = styled(ListItem)`
|
||||||
|
&& {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Fab = styled(MuiFab)`
|
||||||
|
&& {
|
||||||
|
background-color: ${colors.primary};
|
||||||
|
color: ${colors.white};
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`;
|
@ -1,49 +1,53 @@
|
|||||||
/* eslint no-unused-vars: 0 */
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
import React, {Component, Fragment} from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import {DetailContextConsumer} from '../../pages/version/index';
|
import Avatar from '@material-ui/core/Avatar/index';
|
||||||
import Card from '@material-ui/core/Card/index';
|
import List from '@material-ui/core/List/index';
|
||||||
import CardContent from '@material-ui/core/CardContent/index';
|
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||||
import CopyToClipBoard from '../CopyToClipBoard';
|
|
||||||
import CardHeader from '@material-ui/core/CardHeader/index';
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import { Heading, AuthorListItem } from './styles';
|
||||||
import CardActions from '@material-ui/core/CardActions';
|
|
||||||
import Typography from '@material-ui/core/Typography/index';
|
|
||||||
|
|
||||||
class Authors extends Component<any, any> {
|
class Authors extends Component<any, any> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<DetailContextConsumer>
|
<DetailContextConsumer>
|
||||||
{(context) => {
|
{context => {
|
||||||
return this.renderAuthor(context);
|
return this.renderAuthor(context);
|
||||||
}}
|
}}
|
||||||
</DetailContextConsumer>
|
</DetailContextConsumer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAuthor = ({packageMeta}) => {
|
renderLinkForMail(email, avatarComponent, packageName, version) {
|
||||||
const {author} = packageMeta.latest;
|
if (!email) {
|
||||||
|
return avatarComponent;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
|
||||||
|
{avatarComponent}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAuthor = ({ packageMeta }) => {
|
||||||
|
const { author, name: packageName, version } = packageMeta.latest;
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const avatarComponent = <Avatar alt={author.name} src={author.avatar} />;
|
||||||
return (
|
return (
|
||||||
<Card>
|
<List subheader={<Heading variant={'subheading'}>{'Author'}</Heading>}>
|
||||||
<CardContent>{this.renderAvatar(author)}</CardContent>
|
<AuthorListItem>
|
||||||
</Card>
|
{this.renderLinkForMail(author.email, avatarComponent, packageName, version)}
|
||||||
);
|
<ListItemText primary={author.name} />
|
||||||
};
|
</AuthorListItem>
|
||||||
|
</List>
|
||||||
renderAvatar = ({name, email, url, avatar}) => {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Avatar aria-label={name} src={avatar} />
|
|
||||||
<Typography color={'textPrimary'} gutterBottom={true} variant={'caption'}>
|
|
||||||
{name}
|
|
||||||
</Typography>
|
|
||||||
</Fragment>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
21
src/components/Author/styles.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styled from 'react-emotion';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
|
|
||||||
|
export const Heading = styled(Typography)`
|
||||||
|
&& {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const AuthorListItem = styled(ListItem)`
|
||||||
|
&& {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
`;
|
@ -7,14 +7,14 @@ import React from 'react';
|
|||||||
import FileCopy from '@material-ui/icons/FileCopy';
|
import FileCopy from '@material-ui/icons/FileCopy';
|
||||||
import Tooltip from '@material-ui/core/Tooltip/index';
|
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||||
|
|
||||||
import type {Node} from 'react';
|
import type { Node } from 'react';
|
||||||
import {IProps} from './types';
|
import { IProps } from './types';
|
||||||
|
|
||||||
import {ClipBoardCopy, ClipBoardCopyText, CopyIcon} from './styles';
|
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
|
||||||
import {copyToClipBoardUtility} from '../../utils/cli-utils';
|
import { copyToClipBoardUtility } from '../../utils/cli-utils';
|
||||||
import {TEXT} from '../../utils/constants';
|
import { TEXT } from '../../utils/constants';
|
||||||
|
|
||||||
const CopyToClipBoard = ({text}: IProps): Node => {
|
const CopyToClipBoard = ({ text, children }: IProps): Node => {
|
||||||
const renderToolTipFileCopy = () => (
|
const renderToolTipFileCopy = () => (
|
||||||
<Tooltip disableFocusListener={true} title={TEXT.CLIPBOARD_COPY}>
|
<Tooltip disableFocusListener={true} title={TEXT.CLIPBOARD_COPY}>
|
||||||
<CopyIcon onClick={copyToClipBoardUtility(text)}>
|
<CopyIcon onClick={copyToClipBoardUtility(text)}>
|
||||||
@ -22,9 +22,17 @@ const CopyToClipBoard = ({text}: IProps): Node => {
|
|||||||
</CopyIcon>
|
</CopyIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderText = children => {
|
||||||
|
if (children) {
|
||||||
|
return <ClipBoardCopyText>{children}</ClipBoardCopyText>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ClipBoardCopyText>{text}</ClipBoardCopyText>;
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<ClipBoardCopy>
|
<ClipBoardCopy>
|
||||||
<ClipBoardCopyText>{text}</ClipBoardCopyText>
|
{renderText(children)}
|
||||||
{renderToolTipFileCopy()}
|
{renderToolTipFileCopy()}
|
||||||
</ClipBoardCopy>
|
</ClipBoardCopy>
|
||||||
);
|
);
|
||||||
|
@ -5,4 +5,5 @@
|
|||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
text: string;
|
text: string;
|
||||||
|
children?: any;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,9 @@ import { withRouter } from 'react-router-dom';
|
|||||||
import CardContent from '@material-ui/core/CardContent/index';
|
import CardContent from '@material-ui/core/CardContent/index';
|
||||||
|
|
||||||
import { DetailContextConsumer } from '../../pages/version';
|
import { DetailContextConsumer } from '../../pages/version';
|
||||||
import { Content, CardWrap, Heading, Tags, Tag } from './styles';
|
|
||||||
|
import { CardWrap, Heading, Tags, Tag } from './styles';
|
||||||
|
import NoItems from '../NoItems';
|
||||||
|
|
||||||
class DepDetail extends Component<any, any> {
|
class DepDetail extends Component<any, any> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
@ -26,7 +28,7 @@ class DepDetail extends Component<any, any> {
|
|||||||
render() {
|
render() {
|
||||||
const { name, version } = this.state;
|
const { name, version } = this.state;
|
||||||
const tagText = `${name}@${version}`;
|
const tagText = `${name}@${version}`;
|
||||||
return <Tag clickable={true} component={'div'} label={tagText} onClick={this.handleOnClick} />;
|
return <Tag className={'dep-tag'} clickable={true} component={'div'} label={tagText} onClick={this.handleOnClick} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnClick = () => {
|
handleOnClick = () => {
|
||||||
@ -34,20 +36,13 @@ class DepDetail extends Component<any, any> {
|
|||||||
const { onLoading, history } = this.props;
|
const { onLoading, history } = this.props;
|
||||||
|
|
||||||
onLoading();
|
onLoading();
|
||||||
history.push(`/-/web/version/${name}`);
|
history.push(`/-/web/detail/${name}`);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const WrappDepDetail = withRouter(DepDetail);
|
const WrappDepDetail = withRouter(DepDetail);
|
||||||
|
|
||||||
class DependencyBlock extends Component<any, any> {
|
class DependencyBlock extends Component<any, any> {
|
||||||
renderTags = (deps: any, enableLoading: any) =>
|
|
||||||
deps.map(dep => {
|
|
||||||
const [name, version] = dep;
|
|
||||||
|
|
||||||
return <WrappDepDetail key={name} name={name} onLoading={enableLoading} version={version} />;
|
|
||||||
});
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dependencies, title } = this.props;
|
const { dependencies, title } = this.props;
|
||||||
const deps = Object.entries(dependencies);
|
const deps = Object.entries(dependencies);
|
||||||
@ -59,7 +54,7 @@ class DependencyBlock extends Component<any, any> {
|
|||||||
return (
|
return (
|
||||||
<CardWrap>
|
<CardWrap>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Heading variant={'subheading'}>{title}</Heading>
|
<Heading variant={'subheading'}>{`${title} (${deps.length})`}</Heading>
|
||||||
<Tags>{this.renderTags(deps, enableLoading)}</Tags>
|
<Tags>{this.renderTags(deps, enableLoading)}</Tags>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardWrap>
|
</CardWrap>
|
||||||
@ -68,6 +63,13 @@ class DependencyBlock extends Component<any, any> {
|
|||||||
</DetailContextConsumer>
|
</DetailContextConsumer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderTags = (deps: any, enableLoading: any) =>
|
||||||
|
deps.map(dep => {
|
||||||
|
const [name, version] = dep;
|
||||||
|
|
||||||
|
return <WrappDepDetail key={name} name={name} onLoading={enableLoading} version={version} />;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class Dependencies extends Component<any, any> {
|
class Dependencies extends Component<any, any> {
|
||||||
@ -85,23 +87,29 @@ class Dependencies extends Component<any, any> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkDependencyLength(dependency: Object = {}) {
|
||||||
|
return Object.keys(dependency).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
renderDependencies({ packageMeta }) {
|
renderDependencies({ packageMeta }) {
|
||||||
const { latest } = packageMeta;
|
const { latest } = packageMeta;
|
||||||
// console.log('renderDependencies', latest);
|
const { dependencies, devDependencies, peerDependencies, name } = latest;
|
||||||
const { dependencies, devDependencies, peerDependencies } = latest;
|
|
||||||
// console.log('dependencies', dependencies);
|
|
||||||
// console.log('devDependencies', devDependencies);
|
|
||||||
|
|
||||||
return (
|
const dependencyMap = { dependencies, devDependencies, peerDependencies };
|
||||||
<Content>
|
|
||||||
<Fragment>
|
const dependencyList = Object.keys(dependencyMap).reduce((result, value, key) => {
|
||||||
{dependencies && <DependencyBlock dependencies={dependencies} title={'Dependencies'} />}
|
const selectedDepndency = dependencyMap[value];
|
||||||
{devDependencies && <DependencyBlock dependencies={devDependencies} title={'DevDependencies'} />}
|
if (selectedDepndency && this.checkDependencyLength(selectedDepndency)) {
|
||||||
{peerDependencies && <DependencyBlock dependencies={peerDependencies} title={'PeerDependencies'} />}
|
result.push(<DependencyBlock className={'dependency-block'} dependencies={selectedDepndency} key={key} title={value} />);
|
||||||
</Fragment>
|
}
|
||||||
</Content>
|
return result;
|
||||||
);
|
}, []);
|
||||||
|
|
||||||
|
if (dependencyList.length) {
|
||||||
|
return <Fragment>{dependencyList}</Fragment>;
|
||||||
|
}
|
||||||
|
return <NoItems className={'no-dependencies'} text={`${name} has no dependencies.`} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,21 +8,16 @@ import Card from '@material-ui/core/Card/index';
|
|||||||
import Typography from '@material-ui/core/Typography/index';
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
import Chip from '@material-ui/core/Chip/index';
|
import Chip from '@material-ui/core/Chip/index';
|
||||||
|
|
||||||
export const Content = styled.div`
|
|
||||||
&& {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CardWrap = styled(Card)`
|
export const CardWrap = styled(Card)`
|
||||||
&& {
|
&& {
|
||||||
margin: 0 0 25px;
|
margin: 0 0 16px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Heading = styled(Typography)`
|
export const Heading = styled(Typography)`
|
||||||
&& {
|
&& {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -30,25 +30,31 @@ class DetailContainer extends Component<any, any> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleChange = (event: any, tabPosition: number) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.setState({ tabPosition });
|
||||||
|
};
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
renderTabs = ({ readMe }) => {
|
renderTabs = ({ readMe }) => {
|
||||||
const { tabPosition } = this.state;
|
const { tabPosition } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Tabs indicatorColor={'primary'} onChange={this.handleChange} textColor={'primary'} value={tabPosition} variant={'fullWidth'}>
|
|
||||||
<Tab label={'Readme'} />
|
|
||||||
<Tab label={'Dependencies'} />
|
|
||||||
<Tab label={'Versions'} />
|
|
||||||
<Tab label={'Uplinks'} />
|
|
||||||
</Tabs>
|
|
||||||
<Content>
|
<Content>
|
||||||
|
<Tabs indicatorColor={'primary'} onChange={this.handleChange} textColor={'primary'} value={tabPosition} variant={'fullWidth'}>
|
||||||
|
<Tab id={'readme-tab'} label={'Readme'} />
|
||||||
|
<Tab id={'dependencies-tab'} label={'Dependencies'} />
|
||||||
|
<Tab id={'versions-tab'} label={'Versions'} />
|
||||||
|
<Tab id={'uplinks-tab'} label={'Uplinks'} />
|
||||||
|
</Tabs>
|
||||||
|
<br />
|
||||||
{tabPosition === 0 && this.renderReadme(readMe)}
|
{tabPosition === 0 && this.renderReadme(readMe)}
|
||||||
{tabPosition === 1 && <Dependencies />}
|
{tabPosition === 1 && <Dependencies />}
|
||||||
{tabPosition === 2 && <Versions />}
|
{tabPosition === 2 && <Versions />}
|
||||||
{tabPosition === 3 && <UpLinks />}
|
{tabPosition === 3 && <UpLinks />}
|
||||||
</Content>
|
</Content>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -57,11 +63,6 @@ class DetailContainer extends Component<any, any> {
|
|||||||
|
|
||||||
return <Readme description={encodedReadme} />;
|
return <Readme description={encodedReadme} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChange = (event: any, tabPosition: number) => {
|
|
||||||
event.preventDefault();
|
|
||||||
this.setState({ tabPosition });
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DetailContainer;
|
export default DetailContainer;
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
import { DetailContextConsumer } from '../../pages/version/index';
|
import Card from '@material-ui/core/Card/index';
|
||||||
import Typography from '@material-ui/core/Typography/index';
|
import CardContent from '@material-ui/core/CardContent/index';
|
||||||
import Grid from '@material-ui/core/Grid/index';
|
import List from '@material-ui/core/List/index';
|
||||||
|
|
||||||
import Install from '../Install';
|
import ActtionBar from '../ActionBar';
|
||||||
import { Content } from './styles';
|
import Author from '../Author';
|
||||||
import Authors from '../Author';
|
|
||||||
import License from '../License';
|
|
||||||
import Repository from '../Repository';
|
|
||||||
import Developers from '../Developers';
|
import Developers from '../Developers';
|
||||||
|
import Dist from '../Dist';
|
||||||
|
import Engine from '../Engines';
|
||||||
|
import Install from '../Install';
|
||||||
|
import Repository from '../Repository';
|
||||||
|
|
||||||
class DetailSidebar extends Component<any, any> {
|
|
||||||
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
|
|
||||||
|
import { TitleListItem, TitleListItemText } from './styles';
|
||||||
|
|
||||||
|
class DetailSidebar extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<DetailContextConsumer>
|
<DetailContextConsumer>
|
||||||
@ -20,43 +26,36 @@ class DetailSidebar extends Component<any, any> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderSideBar = ({packageMeta, packageName}) => {
|
renderSideBar = ({packageName, packageMeta}) => {
|
||||||
return (
|
return (
|
||||||
<Content className={'sidebar-info'}>
|
<div className={'sidebar-info'}>
|
||||||
<Grid container={true} spacing={24}>
|
<Card>
|
||||||
<Grid item={true} xs={12}>
|
<CardContent>
|
||||||
{this.renderTitle(packageName, packageMeta)}
|
{this.renderTitle(packageName, packageMeta)}
|
||||||
</Grid>
|
{this.renderActionBar()}
|
||||||
<Grid className={'detail-info'} item={true} xs={12}>
|
|
||||||
{this.renderCopyCLI()}
|
{this.renderCopyCLI()}
|
||||||
</Grid>
|
|
||||||
<Grid item={true} xs={12}>
|
|
||||||
{this.renderSecondLevel(8)}
|
|
||||||
</Grid>
|
|
||||||
<Grid item={true} xs={12}>
|
|
||||||
{this.renderMaintainers()}
|
|
||||||
</Grid>
|
|
||||||
<Grid item={true} xs={12}>
|
|
||||||
{this.renderContributors()}
|
|
||||||
</Grid>
|
|
||||||
<Grid item={true} xs={12}>
|
|
||||||
{this.renderRepository()}
|
{this.renderRepository()}
|
||||||
</Grid>
|
{this.renderEngine()}
|
||||||
</Grid>
|
{this.renderDist()}
|
||||||
</Content>
|
{this.renderAuthor()}
|
||||||
|
{this.renderMaintainers()}
|
||||||
|
{this.renderContributors()}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTitle = (packageName, packageMeta) => {
|
renderTitle = (packageName, packageMeta) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<List className={'detail-info'}>
|
||||||
<Typography color={"textPrimary"} gutterBottom={true} variant={'title'}>
|
<TitleListItem alignItems={"flex-start"}>
|
||||||
{packageName}
|
<TitleListItemText
|
||||||
</Typography>
|
primary={<b>{packageName}</b>}
|
||||||
<Typography color={"textSecondary"} gutterBottom={true} variant={'body2'}>
|
secondary={packageMeta.latest.description}
|
||||||
{packageMeta.latest.description}
|
/>
|
||||||
</Typography>
|
</TitleListItem>
|
||||||
</>
|
</List>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,31 +71,25 @@ class DetailSidebar extends Component<any, any> {
|
|||||||
return <Developers type={'contributors'} />;
|
return <Developers type={'contributors'} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSecondLevel = (spacing = 24) => {
|
|
||||||
return (
|
|
||||||
<Grid container={true} spacing={spacing}>
|
|
||||||
{this.renderAuthor()}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRepository = () => {
|
renderRepository = () => {
|
||||||
return <Repository />;
|
return <Repository />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAuthor = () => {
|
renderAuthor = () => {
|
||||||
return (
|
return <Author />;
|
||||||
<>
|
}
|
||||||
<Grid item={true} xs={6}>
|
|
||||||
<Authors />
|
renderEngine = () => {
|
||||||
</Grid>
|
return <Engine />;
|
||||||
<Grid item={true} xs={6}>
|
}
|
||||||
<License />
|
|
||||||
</Grid>
|
renderDist = () => {
|
||||||
</>
|
return <Dist />;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
renderActionBar = () => {
|
||||||
|
return <ActtionBar />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default DetailSidebar;
|
export default DetailSidebar;
|
||||||
|
@ -4,11 +4,32 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import styled from 'react-emotion';
|
import styled from 'react-emotion';
|
||||||
|
import Avatar from '@material-ui/core/Avatar/index';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||||
|
|
||||||
import colors from '../../utils/styles/colors';
|
import colors from '../../utils/styles/colors';
|
||||||
|
|
||||||
export const Content = styled.div`
|
export const TitleListItem = styled(ListItem)`
|
||||||
&& {
|
&& {
|
||||||
padding: 10px;
|
padding-left: 0;
|
||||||
background-color: ${colors.white};
|
padding-right: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TitleListItemText = styled(ListItemText)`
|
||||||
|
&& {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TitleAvatar = styled(Avatar)`
|
||||||
|
&& {
|
||||||
|
color: ${colors.greySuperLight};
|
||||||
|
background-color: ${colors.primary};
|
||||||
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
import { DetailContextConsumer } from '../../pages/version';
|
|
||||||
import Card from '@material-ui/core/Card';
|
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
import Tooltip from '@material-ui/core/Tooltip';
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
import Add from '@material-ui/icons/Add';
|
import Add from '@material-ui/icons/Add';
|
||||||
import { Details, Heading, Content, CardContent, Fab } from './styles';
|
|
||||||
|
import { DetailContextConsumer } from '../../pages/version';
|
||||||
|
|
||||||
|
import { Details, Heading, Content, Fab } from './styles';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'contributors' | 'maintainers'
|
type: 'contributors' | 'maintainers'
|
||||||
@ -23,44 +24,59 @@ class Developers extends Component<Props, any> {
|
|||||||
const { type } = this.props;
|
const { type } = this.props;
|
||||||
const developerType = packageMeta.latest[type];
|
const developerType = packageMeta.latest[type];
|
||||||
if (!developerType || developerType.length === 0) return null;
|
if (!developerType || developerType.length === 0) return null;
|
||||||
return this.renderDevelopers(developerType);
|
return this.renderDevelopers(developerType, packageMeta);
|
||||||
}}
|
}}
|
||||||
</DetailContextConsumer>
|
</DetailContextConsumer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderDevelopers = (developers) => {
|
handleLoadMore = () => {
|
||||||
|
this.setState((prev) => ({ visibleDevs: prev.visibleDevs + 6 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDevelopers = (developers, packageMeta) => {
|
||||||
const { type } = this.props;
|
const { type } = this.props;
|
||||||
const { visibleDevs } = this.state;
|
const { visibleDevs } = this.state;
|
||||||
return (
|
return (
|
||||||
<Card>
|
<>
|
||||||
<CardContent>
|
<Heading variant={'subheading'}>{type}</Heading>
|
||||||
<Heading variant={'subheading'}>{type}</Heading>
|
<Content>
|
||||||
<Content>
|
{developers.slice(0, visibleDevs).map(developer => (
|
||||||
{developers.slice(0, visibleDevs).map(developer => (
|
<Details key={developer.email}>{this.renderDeveloperDetails(developer, packageMeta)}</Details>
|
||||||
<Details key={developer.email}>{this.renderDeveloperDetails(developer)}</Details>
|
))}
|
||||||
))}
|
{visibleDevs < developers.length &&
|
||||||
{visibleDevs < developers.length &&
|
<Fab onClick={this.handleLoadMore} size={'small'}><Add /></Fab>
|
||||||
<Fab onClick={this.handleLoadMore} size={'small'}><Add /></Fab>
|
}
|
||||||
}
|
</Content>
|
||||||
</Content>
|
</>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDeveloperDetails= ({name, avatar }) => {
|
renderLinkForMail(email, avatarComponent, packageName, version) {
|
||||||
|
if(!email) {
|
||||||
|
return avatarComponent;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={"_top"}>
|
||||||
|
{avatarComponent}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => {
|
||||||
|
const {
|
||||||
|
name: packageName,
|
||||||
|
version,
|
||||||
|
} = packageMeta.latest;
|
||||||
|
|
||||||
|
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
|
||||||
return (
|
return (
|
||||||
<Tooltip title={name}>
|
<Tooltip title={name}>
|
||||||
<Avatar aria-label={name} src={avatar} />
|
{this.renderLinkForMail(email, avatarComponent, packageName, version)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = () => {
|
|
||||||
this.setState((prev) => ({ visibleDevs: prev.visibleDevs + 6 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,8 +7,6 @@ import Typography from '@material-ui/core/Typography';
|
|||||||
import { default as MuiFab } from '@material-ui/core/Fab';
|
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||||
import colors from '../../utils/styles/colors';
|
import colors from '../../utils/styles/colors';
|
||||||
|
|
||||||
import { default as MuiCardContent } from '@material-ui/core/CardContent/index';
|
|
||||||
|
|
||||||
export const Details = styled('span')`
|
export const Details = styled('span')`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -16,7 +14,7 @@ export const Details = styled('span')`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const Content = styled('div')`
|
export const Content = styled('div')`
|
||||||
margin: -5px;
|
margin: 10px 0 10px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
> * {
|
> * {
|
||||||
@ -24,12 +22,6 @@ export const Content = styled('div')`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CardContent = styled(MuiCardContent)`
|
|
||||||
&& {
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Heading = styled(Typography)`
|
export const Heading = styled(Typography)`
|
||||||
&& {
|
&& {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
59
src/components/Dist/index.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import List from '@material-ui/core/List/index';
|
||||||
|
|
||||||
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
|
|
||||||
|
import { Heading, DistListItem, DistChips, DownloadButton } from './styles';
|
||||||
|
import fileSizeSI from '../../utils/file-size';
|
||||||
|
|
||||||
|
class Dist extends Component<any, any> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<DetailContextConsumer>
|
||||||
|
{context => {
|
||||||
|
return this.renderDist(context);
|
||||||
|
}}
|
||||||
|
</DetailContextConsumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChips(dist, license) {
|
||||||
|
const distDict = {
|
||||||
|
'file-count': dist.fileCount,
|
||||||
|
size: dist.unpackedSize && fileSizeSI(dist.unpackedSize),
|
||||||
|
license,
|
||||||
|
};
|
||||||
|
|
||||||
|
const chipsList = Object.keys(distDict).reduce((componentList, title, key) => {
|
||||||
|
const value = distDict[title];
|
||||||
|
if (value) {
|
||||||
|
const label = (
|
||||||
|
<span>
|
||||||
|
<b>{title.split('-').join(' ')}</b>: {value}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
componentList.push(<DistChips label={label} key={key} />);
|
||||||
|
}
|
||||||
|
return componentList;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return chipsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDist = ({ packageMeta }) => {
|
||||||
|
const { dist = {}, license } = packageMeta.latest;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List subheader={<Heading variant={'subheading'}>{'Latest Distribution'}</Heading>}>
|
||||||
|
<DistListItem>{this.renderChips(dist, license)}</DistListItem>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dist;
|
39
src/components/Dist/styles.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styled from 'react-emotion';
|
||||||
|
import { default as MuiFab } from '@material-ui/core/Fab/index';
|
||||||
|
import Chip from '@material-ui/core/Chip/index';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
|
|
||||||
|
import colors from '../../utils/styles/colors';
|
||||||
|
|
||||||
|
export const Heading = styled(Typography)`
|
||||||
|
&& {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DistListItem = styled(ListItem)`
|
||||||
|
&& {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DistChips = styled(Chip)`
|
||||||
|
&& {
|
||||||
|
margin-right: 5px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DownloadButton = styled(MuiFab)`
|
||||||
|
&& {
|
||||||
|
background-color: ${colors.primary};
|
||||||
|
color: ${colors.white};
|
||||||
|
}
|
||||||
|
`;
|
BIN
src/components/Engines/img/node.png
Normal file
After Width: | Height: | Size: 27 KiB |
78
src/components/Engines/index.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
|
import Avatar from '@material-ui/core/Avatar/index';
|
||||||
|
import Grid from '@material-ui/core/Grid/index';
|
||||||
|
import List from '@material-ui/core/List/index';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||||
|
|
||||||
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
|
|
||||||
|
import { Heading, EngineListItem } from './styles';
|
||||||
|
import node from './img/node.png';
|
||||||
|
import npm from '../Install/img/npm.svg'
|
||||||
|
|
||||||
|
|
||||||
|
const ICONS = {
|
||||||
|
'node-JS': <Avatar src={node} />,
|
||||||
|
'NPM-version': <Avatar src={npm} />,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Engine extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<DetailContextConsumer>
|
||||||
|
{(context) => {
|
||||||
|
return this.renderEngine(context);
|
||||||
|
}}
|
||||||
|
</DetailContextConsumer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderEngine = ({packageMeta}) => {
|
||||||
|
const { engines } = packageMeta.latest;
|
||||||
|
if (!engines) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const engineDict = {
|
||||||
|
'node-JS': engines.node,
|
||||||
|
'NPM-version': engines.npm
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = Object.keys(engineDict).reduce((markup, text, key) => {
|
||||||
|
const heading = engineDict[text]
|
||||||
|
if (heading){
|
||||||
|
markup.push(
|
||||||
|
<Grid item={true} xs={6} key={key}>
|
||||||
|
{this.renderListItems(heading, text)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return markup;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (items.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container={true}>
|
||||||
|
{items}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderListItems = (heading, text) => {
|
||||||
|
return (
|
||||||
|
<List subheader={<Heading variant={"subheading"}>{text.split('-').join(' ')}</Heading>}>
|
||||||
|
<EngineListItem>
|
||||||
|
{ ICONS[text] }
|
||||||
|
<ListItemText primary={heading} />
|
||||||
|
</EngineListItem>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Engine;
|
21
src/components/Engines/styles.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styled from 'react-emotion';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
|
|
||||||
|
export const Heading = styled(Typography)`
|
||||||
|
&& {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EngineListItem = styled(ListItem)`
|
||||||
|
&& {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
`;
|
@ -3,53 +3,35 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import type { Element } from "react";
|
import type { Element } from 'react';
|
||||||
|
|
||||||
import { version } from "../../../package.json";
|
import { version } from '../../../../package.json';
|
||||||
import {
|
import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles';
|
||||||
Wrapper,
|
import { goToVerdaccioWebsite } from '../../utils/windows.js';
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Earth,
|
|
||||||
Flags,
|
|
||||||
Love,
|
|
||||||
Flag,
|
|
||||||
Logo,
|
|
||||||
Inner,
|
|
||||||
ToolTip
|
|
||||||
} from "./styles";
|
|
||||||
import { goToVerdaccioWebsite } from "../../utils/windows.js";
|
|
||||||
|
|
||||||
const renderTooltip = () => (
|
const renderTooltip = () => (
|
||||||
<ToolTip>
|
<ToolTip>
|
||||||
<Earth name={"earth"} size={"md"} />
|
<Earth name={'earth'} size={'md'} />
|
||||||
<Flags>
|
<Flags>
|
||||||
<Flag name={"spain"} size={"md"} />
|
<Flag name={'spain'} size={'md'} />
|
||||||
<Flag name={"nicaragua"} size={"md"} />
|
<Flag name={'nicaragua'} size={'md'} />
|
||||||
<Flag name={"india"} size={"md"} />
|
<Flag name={'india'} size={'md'} />
|
||||||
<Flag name={"brazil"} size={"md"} />
|
<Flag name={'brazil'} size={'md'} />
|
||||||
<Flag name={"pakistan"} size={"md"} />
|
<Flag name={'china'} size={'md'} />
|
||||||
<Flag name={"china"} size={"md"} />
|
<Flag name={'austria'} size={'md'} />
|
||||||
<Flag name={"austria"} size={"md"} />
|
|
||||||
</Flags>
|
</Flags>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
);
|
);
|
||||||
const POWERED_LABEL = "Powered by";
|
const POWERED_LABEL = 'Powered by';
|
||||||
const MADEWITH_LABEL = " Made with";
|
const MADEWITH_LABEL = ' Made with';
|
||||||
const ON_LABEL = "on";
|
const ON_LABEL = 'on';
|
||||||
const HEARTH_EMOJI = "♥";
|
const HEARTH_EMOJI = '♥';
|
||||||
|
|
||||||
const renderRight = () => (
|
const renderRight = () => (
|
||||||
<Right>
|
<Right>
|
||||||
{POWERED_LABEL}
|
{POWERED_LABEL}
|
||||||
<Logo
|
<Logo img={true} name={'verdaccio'} onClick={goToVerdaccioWebsite} pointer={true} size={'md'} />
|
||||||
img={true}
|
|
||||||
name={"verdaccio"}
|
|
||||||
onClick={goToVerdaccioWebsite}
|
|
||||||
pointer={true}
|
|
||||||
size={"md"}
|
|
||||||
/>
|
|
||||||
{`/ ${version}`}
|
{`/ ${version}`}
|
||||||
</Right>
|
</Right>
|
||||||
);
|
);
|
||||||
|
@ -127,7 +127,7 @@ class Header extends Component<IProps, IState> {
|
|||||||
renderLogo = (): Node => {
|
renderLogo = (): Node => {
|
||||||
const { logo } = this.props;
|
const { logo } = this.props;
|
||||||
|
|
||||||
if (logo) {
|
if (logo !== '') {
|
||||||
return <img alt={'logo'} height={'40px'} src={logo} />;
|
return <img alt={'logo'} height={'40px'} src={logo} />;
|
||||||
} else {
|
} else {
|
||||||
return <Logo />;
|
return <Logo />;
|
||||||
|
@ -35,8 +35,8 @@ const Help = (): Node => {
|
|||||||
<HelpTitle color={'textSecondary'} gutterBottom={true}>
|
<HelpTitle color={'textSecondary'} gutterBottom={true}>
|
||||||
{'To publish your first package just:'}
|
{'To publish your first package just:'}
|
||||||
</HelpTitle>
|
</HelpTitle>
|
||||||
{renderHeadingClipboardSegments('1. Login', `$ npm adduser --registry ${registryUrl}`)}
|
{renderHeadingClipboardSegments('1. Login', `npm adduser --registry ${registryUrl}`)}
|
||||||
{renderHeadingClipboardSegments('2. Publish', `$ npm publish --registry ${registryUrl}`)}
|
{renderHeadingClipboardSegments('2. Publish', `npm publish --registry ${registryUrl}`)}
|
||||||
<Typography variant={'body2'}>{'3. Refresh this page.'}</Typography>
|
<Typography variant={'body2'}>{'3. Refresh this page.'}</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions>
|
<CardActions>
|
||||||
|
3
src/components/Icon/img/filebinary.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="filebinary">
|
||||||
|
<path d="M8.5 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V4.5L8.5 1zM11 14H1V2h7l3 3v9zM5 6.98L3.5 8.5 5 10l-.5 1L2 8.5 4.5 6l.5.98zM7.5 6L10 8.5 7.5 11l-.5-.98L8.5 8.5 7 7l.5-1z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 265 B |
3
src/components/Icon/img/law.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="law">
|
||||||
|
<path fill-rule="evenodd" d="M7 4c-.83 0-1.5-.67-1.5-1.5S6.17 1 7 1s1.5.67 1.5 1.5S7.83 4 7 4zm7 6c0 1.11-.89 2-2 2h-1c-1.11 0-2-.89-2-2l2-4h-1c-.55 0-1-.45-1-1H8v8c.42 0 1 .45 1 1h1c.42 0 1 .45 1 1H3c0-.55.58-1 1-1h1c0-.55.58-1 1-1h.03L6 5H5c0 .55-.45 1-1 1H3l2 4c0 1.11-.89 2-2 2H2c-1.11 0-2-.89-2-2l2-4H1V5h3c0-.55.45-1 1-1h4c.55 0 1 .45 1 1h3v1h-1l2 4zM2.5 7L1 10h3L2.5 7zM13 10l-1.5-3-1.5 3h3z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 464 B |
6
src/components/Icon/img/version.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16" height="16" width="14" id="version">
|
||||||
|
<path fill-rule="evenodd" d="M13 3H7c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm-1 8H8V5h4v6zM4 4h1v1H4v6h1v1H4c-.55 0-1-.45-1-1V5c0-.55.45-1 1-1zM1 5h1v1H1v4h1v1H1c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1z"></path>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 344 B |
@ -19,8 +19,11 @@ import austria from './img/austria.svg';
|
|||||||
import spain from './img/spain.svg';
|
import spain from './img/spain.svg';
|
||||||
import earth from './img/earth.svg';
|
import earth from './img/earth.svg';
|
||||||
import verdaccio from './img/verdaccio.svg';
|
import verdaccio from './img/verdaccio.svg';
|
||||||
|
import filebinary from './img/filebinary.svg';
|
||||||
|
import law from './img/law.svg';
|
||||||
import license from './img/license.svg';
|
import license from './img/license.svg';
|
||||||
import time from './img/time.svg';
|
import time from './img/time.svg';
|
||||||
|
import version from './img/version.svg';
|
||||||
|
|
||||||
export const Icons: $Shape<IIconsMap> = {
|
export const Icons: $Shape<IIconsMap> = {
|
||||||
// flags
|
// flags
|
||||||
@ -33,8 +36,12 @@ export const Icons: $Shape<IIconsMap> = {
|
|||||||
austria,
|
austria,
|
||||||
earth,
|
earth,
|
||||||
verdaccio,
|
verdaccio,
|
||||||
|
// other icons
|
||||||
|
filebinary,
|
||||||
|
law,
|
||||||
license,
|
license,
|
||||||
time,
|
time,
|
||||||
|
version,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Icon = ({ className, name, size = 'sm', img = false, pointer = false, ...props }: IProps): Node => {
|
const Icon = ({ className, name, size = 'sm', img = false, pointer = false, ...props }: IProps): Node => {
|
||||||
|
@ -21,7 +21,7 @@ const getSize = (size: string) => {
|
|||||||
default:
|
default:
|
||||||
return `
|
return `
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 16px;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,9 @@ export interface IIconsMap {
|
|||||||
verdaccio: string;
|
verdaccio: string;
|
||||||
license: string;
|
license: string;
|
||||||
time: string;
|
time: string;
|
||||||
|
law: string;
|
||||||
|
version: string;
|
||||||
|
filebinary: string;
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
src/components/Install/img/npm.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
|
||||||
|
<polygon fill="#CC0000" points="23,65.6 130,3.3 237,65.6 237,190.1 130,252.4 23,190.1 "/>
|
||||||
|
<polygon fill="#FFFFFF" points="133,127.8 232.5,70.5 236.5,186 133,248.5 "/>
|
||||||
|
<g>
|
||||||
|
<path fill="#CC0000" d="M234,67l-0.3,122.4l-103.8,60.2l-0.5-120.3L234,67z M146.7,139.3l0.3,80.4l34.5-20.1l-0.1-60.6l17.4-10.3
|
||||||
|
l0,60.8l17.4-10.2l0.1-81.4L146.7,139.3z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#910505" d="M136.8,4.2c-4.8-2.7-12.5-2.7-17.3,0L24.7,58.7c-4.8,2.7-8.6,9.5-8.6,14.9v109c0,5.5,3.9,12.2,8.6,14.9
|
||||||
|
l94.8,54.5c4.8,2.7,12.5,2.7,17.3,0l94.8-54.5c4.8-2.7,8.6-9.5,8.6-14.9v-109c0-5.5-3.9-12.2-8.6-14.9L136.8,4.2z M220.9,61.2
|
||||||
|
c4.8,2.7,4.8,7.2,0,9.9l-83,47.7c-4.8,2.7-12.5,2.7-17.3,0L36.4,70.4c-4.8-2.7-4.8-7.2,0-9.9l83-47.7c4.8-2.7,12.5-2.7,17.3,0
|
||||||
|
L220.9,61.2z M23.5,81.5c0-5.5,3.9-7.7,8.6-5l84.9,48.8c4.8,2.7,8.6,9.5,8.6,14.9V237c0,5.5-3.9,7.7-8.6,5l-84.9-48.8
|
||||||
|
c-4.8-2.7-8.6-9.5-8.6-14.9V81.5z M141.8,240.5c-4.8,2.7-8.6,0.5-8.6-5v-95.3c0-5.5,3.9-12.2,8.6-14.9L224.2,78
|
||||||
|
c4.8-2.7,8.6-0.5,8.6,5v95.3c0,5.5-3.9,12.2-8.6,14.9L141.8,240.5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
src/components/Install/img/pnpm.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="66.09157809474142 33.5 184.5 184.49999999999997" width="180" height="180"><defs><path d="M67.59 35L247.59 35L247.59 215L67.59 215L67.59 35Z" id="b2JZZcA3fT"></path><path d="M237.6 95L187.6 95L187.6 45L237.6 45L237.6 95Z" id="bj0tb0Y8q"></path><path d="M182.59 95L132.59 95L132.59 45L182.59 45L182.59 95Z" id="dkDSTzPj3"></path><path d="M127.59 95L77.59 95L77.59 45L127.59 45L127.59 95Z" id="a4vNdcNLpF"></path><path d="M237.6 150L187.6 150L187.6 100L237.6 100L237.6 150Z" id="h2t4Zj1jSU"></path><path d="M182.59 150L132.59 150L132.59 100L182.59 100L182.59 150Z" id="b4t5pngwvT"></path><path d="M182.59 205L132.59 205L132.59 155L182.59 155L182.59 205Z" id="b9s1gd5m2"></path><path d="M237.6 205L187.6 205L187.6 155L237.6 155L237.6 205Z" id="cmt9WLvz7"></path><path d="M127.59 205L77.59 205L77.59 155L127.59 155L127.59 205Z" id="bJUNqgFSg"></path></defs><g><g><use xlink:href="#b2JZZcA3fT" opacity="1" fill="#ffffff" fill-opacity="1"></use></g><g><use xlink:href="#bj0tb0Y8q" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#dkDSTzPj3" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#a4vNdcNLpF" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#h2t4Zj1jSU" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#b4t5pngwvT" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#b9s1gd5m2" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#cmt9WLvz7" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#bJUNqgFSg" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
1
src/components/Install/img/yarn.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 518 518"><style>.st0{fill:#2c8ebb}.st1{fill:#fff}</style><path class="st0" d="M259 0c143 0 259 116 259 259S402 518 259 518 0 402 0 259 116 0 259 0z"/><path class="st1" d="M435.2 337.5c-1.8-14.2-13.8-24-29.2-23.8-23 .3-42.3 12.2-55.1 20.1-5 3.1-9.3 5.4-13 7.1.8-11.6.1-26.8-5.9-43.5-7.3-20-17.1-32.3-24.1-39.4 8.1-11.8 19.2-29 24.4-55.6 4.5-22.7 3.1-58-7.2-77.8-2.1-4-5.6-6.9-10-8.1-1.8-.5-5.2-1.5-11.9.4C293.1 96 289.6 93.8 286.9 92c-5.6-3.6-12.2-4.4-18.4-2.1-8.3 3-15.4 11-22.1 25.2-1 2.1-1.9 4.1-2.7 6.1-12.7.9-32.7 5.5-49.6 23.8-2.1 2.3-6.2 4-10.5 5.6h.1c-8.8 3.1-12.8 10.3-17.7 23.3-6.8 18.2.2 36.1 7.1 47.7-9.4 8.4-21.9 21.8-28.5 37.5-8.2 19.4-9.1 38.4-8.8 48.7-7 7.4-17.8 21.3-19 36.9-1.6 21.8 6.3 36.6 9.8 42 1 1.6 2.1 2.9 3.3 4.2-.4 2.7-.5 5.6.1 8.6 1.3 7 5.7 12.7 12.4 16.3 13.2 7 31.6 10 45.8 2.9 5.1 5.4 14.4 10.6 31.3 10.6h1c4.3 0 58.9-2.9 74.8-6.8 7.1-1.7 12-4.7 15.2-7.4 10.2-3.2 38.4-12.8 65-30 18.8-12.2 25.3-14.8 39.3-18.2 13.6-3.3 22.1-15.7 20.4-29.4zm-23.8 14.7c-16 3.8-24.1 7.3-43.9 20.2-30.9 20-64.7 29.3-64.7 29.3s-2.8 4.2-10.9 6.1c-14 3.4-66.7 6.3-71.5 6.4-12.9.1-20.8-3.3-23-8.6-6.7-16 9.6-23 9.6-23s-3.6-2.2-5.7-4.2c-1.9-1.9-3.9-5.7-4.5-4.3-2.5 6.1-3.8 21-10.5 27.7-9.2 9.3-26.6 6.2-36.9.8-11.3-6 .8-20.1.8-20.1s-6.1 3.6-11-3.8c-4.4-6.8-8.5-18.4-7.4-32.7 1.2-16.3 19.4-32.1 19.4-32.1s-3.2-24.1 7.3-48.8c9.5-22.5 35.1-40.6 35.1-40.6s-21.5-23.8-13.5-45.2c5.2-14 7.3-13.9 9-14.5 6-2.3 11.8-4.8 16.1-9.5 21.5-23.2 48.9-18.8 48.9-18.8s13-39.5 25-31.8c3.7 2.4 17 32 17 32s14.2-8.3 15.8-5.2c8.6 16.7 9.6 48.6 5.8 68-6.4 32-22.4 49.2-28.8 60-1.5 2.5 17.2 10.4 29 43.1 10.9 29.9 1.2 55 2.9 57.8.3.5.4.7.4.7s12.5 1 37.6-14.5c13.4-8.3 29.3-17.6 47.4-17.8 17.5-.3 18.4 20.2 5.2 23.4z"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -1,46 +1,58 @@
|
|||||||
import React, {Component} from 'react';
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import List from '@material-ui/core/List/index';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||||
|
|
||||||
import { DetailContextConsumer } from '../../pages/version/index';
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
import Card from '@material-ui/core/Card/index';
|
|
||||||
import CardContent from '@material-ui/core/CardContent/index';
|
|
||||||
import CopyToClipBoard from '../CopyToClipBoard';
|
import CopyToClipBoard from '../CopyToClipBoard';
|
||||||
import Button from '@material-ui/core/Button';
|
|
||||||
import CardActions from '@material-ui/core/CardActions';
|
|
||||||
|
|
||||||
class Install extends Component<any, any> {
|
import { Heading, InstallItem, PackageMangerAvatar } from './styles';
|
||||||
|
// logos of package managers
|
||||||
|
import npm from './img/npm.svg';
|
||||||
|
import pnpm from './img/pnpm.svg';
|
||||||
|
import yarn from './img/yarn.svg';
|
||||||
|
|
||||||
|
class Install extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<DetailContextConsumer>
|
<DetailContextConsumer>
|
||||||
{(context) => {
|
{context => {
|
||||||
return this.renderCopyCLI(context);
|
return this.renderCopyCLI(context);
|
||||||
}}
|
}}
|
||||||
</DetailContextConsumer>
|
</DetailContextConsumer>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCopyCLI = ({ packageName }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<List subheader={<Heading variant={'subheading'}>{'Installation'}</Heading>}>{this.renderListItems(packageName)}</List>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderCopyCLI = ({packageName}) => {
|
renderListItems = packageName => {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<>
|
||||||
<CardContent>
|
<InstallItem>
|
||||||
<CopyToClipBoard text={`npm install ${packageName}`} />
|
<PackageMangerAvatar alt={'npm logo'} src={npm} />
|
||||||
<CopyToClipBoard text={`pnpm install ${packageName}`} />
|
<ListItemText primary={<CopyToClipBoard text={`npm install ${packageName}`} />} secondary={'Install using NPM'} />
|
||||||
<CopyToClipBoard text={`yarn add ${packageName}`} />
|
</InstallItem>
|
||||||
<CardActions>
|
<InstallItem>
|
||||||
{this.renderDownloadButton()}
|
<PackageMangerAvatar alt={'yarn logo'} src={yarn} />
|
||||||
</CardActions>
|
<ListItemText primary={<CopyToClipBoard text={`yarn add ${packageName}`} />} secondary={'Install using Yarn'} />
|
||||||
</CardContent>
|
</InstallItem>
|
||||||
</Card>
|
<InstallItem>
|
||||||
|
<PackageMangerAvatar alt={'pnpm logo'} src={pnpm} />
|
||||||
|
<ListItemText primary={<CopyToClipBoard text={`pnpm install ${packageName}`} />} secondary={'Install using PNPM'} />
|
||||||
|
</InstallItem>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
renderDownloadButton = () => {
|
|
||||||
return (
|
|
||||||
<Button color={"primary"} size={'small'} variant={"contained"}>
|
|
||||||
{'Download Tarball'}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default Install;
|
export default Install;
|
||||||
|
28
src/components/Install/styles.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styled from 'react-emotion';
|
||||||
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import Avatar from '@material-ui/core/Avatar/index';
|
||||||
|
|
||||||
|
export const Heading = styled(Typography)`
|
||||||
|
&& {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const InstallItem = styled(ListItem)`
|
||||||
|
&& {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PackageMangerAvatar = styled(Avatar)`
|
||||||
|
&& {
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
`;
|
@ -1,51 +0,0 @@
|
|||||||
/* eslint no-unused-vars: 0 */
|
|
||||||
|
|
||||||
import React, {Component, Fragment} from 'react';
|
|
||||||
|
|
||||||
import { DetailContextConsumer } from '../../pages/version/index';
|
|
||||||
import Card from '@material-ui/core/Card/index';
|
|
||||||
import CardContent from '@material-ui/core/CardContent/index';
|
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
|
||||||
import Notes from '@material-ui/icons/Notes';
|
|
||||||
import Typography from "@material-ui/core/Typography/index";
|
|
||||||
|
|
||||||
class License extends Component<any, any> {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<DetailContextConsumer>
|
|
||||||
{(context) => {
|
|
||||||
return this.renderAuthor(context);
|
|
||||||
}}
|
|
||||||
</DetailContextConsumer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderAuthor = ({packageMeta}) => {
|
|
||||||
const { license } = packageMeta.latest;
|
|
||||||
if (!license) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardContent style={{ textAling: 'center'}}>
|
|
||||||
{this.renderLicense(license)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLicense = (license) => {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<Notes style={{ fontSize: 38 }} />
|
|
||||||
<Typography color={"textPrimary"} gutterBottom={true} variant={'caption'}>
|
|
||||||
{license}
|
|
||||||
</Typography>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default License;
|
|
@ -4,13 +4,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
|
|
||||||
import { IProps } from './types';
|
import { IProps } from './types';
|
||||||
import { Wrapper } from './styles';
|
|
||||||
|
|
||||||
const NoItems = ({ text }: IProps) => (
|
const NoItems = ({ text }: IProps) => (
|
||||||
<Wrapper>
|
<Typography gutterBottom={true} variant={'subtitle1'}>
|
||||||
<h2>{text}</h2>
|
{text}
|
||||||
</Wrapper>
|
</Typography>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default NoItems;
|
export default NoItems;
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* @prettier
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import styled from 'react-emotion';
|
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
|
||||||
&& {
|
|
||||||
margin: 5em 0;
|
|
||||||
}
|
|
||||||
`;
|
|
@ -10,6 +10,8 @@ import Typography from '@material-ui/core/Typography/index';
|
|||||||
import { Wrapper, Inner, EmptyPackage, Heading, Card, List } from './styles';
|
import { Wrapper, Inner, EmptyPackage, Heading, Card, List } from './styles';
|
||||||
import PackageImg from './img/package.svg';
|
import PackageImg from './img/package.svg';
|
||||||
|
|
||||||
|
export const NOT_FOUND_TEXT = "Sorry, we couldn't find it...";
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const NotFound = ({ history, width }) => {
|
const NotFound = ({ history, width }) => {
|
||||||
const handleGoTo = to => () => {
|
const handleGoTo = to => () => {
|
||||||
@ -42,7 +44,9 @@ const NotFound = ({ history, width }) => {
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Inner>
|
<Inner>
|
||||||
<EmptyPackage alt={'404 - Page not found'} src={PackageImg} />
|
<EmptyPackage alt={'404 - Page not found'} src={PackageImg} />
|
||||||
<Heading variant={isWidthUp('sm', width) ? 'h2' : 'h4'}>{"Sorry, we couldn't find it..."}</Heading>
|
<Heading className={'not-found-text'} variant={isWidthUp('sm', width) ? 'h2' : 'h4'}>
|
||||||
|
{NOT_FOUND_TEXT}
|
||||||
|
</Heading>
|
||||||
{renderSubTitle()}
|
{renderSubTitle()}
|
||||||
<Card>{renderList()}</Card>
|
<Card>{renderList()}</Card>
|
||||||
</Inner>
|
</Inner>
|
||||||
|
@ -2,104 +2,160 @@
|
|||||||
* @prettier
|
* @prettier
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Element } from 'react';
|
import type { Element } from 'react';
|
||||||
import { spacing } from '../../utils/styles/mixings';
|
|
||||||
|
import BugReport from '@material-ui/icons/BugReport';
|
||||||
|
import Grid from '@material-ui/core/Grid/index';
|
||||||
|
import HomeIcon from '@material-ui/icons/Home';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||||
|
|
||||||
import Tag from '../Tag';
|
import Tag from '../Tag';
|
||||||
|
import fileSizeSI from '../../utils/file-size';
|
||||||
import { formatDate, formatDateDistance } from '../../utils/package';
|
import { formatDate, formatDateDistance } from '../../utils/package';
|
||||||
|
|
||||||
import { IProps } from './types';
|
import { IProps } from './types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
WrapperLink,
|
|
||||||
Header,
|
|
||||||
MainInfo,
|
|
||||||
Name,
|
|
||||||
Version,
|
|
||||||
Overview,
|
|
||||||
Published,
|
|
||||||
OverviewItem,
|
|
||||||
Description,
|
|
||||||
Icon,
|
|
||||||
Text,
|
|
||||||
Details,
|
|
||||||
Avatar,
|
|
||||||
Author,
|
Author,
|
||||||
Field,
|
Avatar,
|
||||||
Content,
|
Description,
|
||||||
Footer,
|
Details,
|
||||||
|
GridRightAligned,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
|
OverviewItem,
|
||||||
|
PackageList,
|
||||||
|
PackageListItem,
|
||||||
|
PackageListItemText,
|
||||||
|
PackageTitle,
|
||||||
|
Published,
|
||||||
|
TagContainer,
|
||||||
|
Text,
|
||||||
|
WrapperLink,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
const getInitialsName = (name: string) =>
|
const Package = ({
|
||||||
name
|
author: { name: authorName, avatar: authorAvatar },
|
||||||
.split(' ')
|
bugs: { url } = {},
|
||||||
.reduce((accumulator, currentValue) => accumulator.charAt(0) + currentValue.charAt(0), '')
|
description,
|
||||||
.toUpperCase();
|
dist: { unpackedSize } = {},
|
||||||
|
homepage,
|
||||||
|
keywords = [],
|
||||||
|
license,
|
||||||
|
name: packageName,
|
||||||
|
time,
|
||||||
|
version,
|
||||||
|
}: IProps): Element<WrapperLink> => {
|
||||||
|
//
|
||||||
|
const renderVersionInfo = () =>
|
||||||
|
version && (
|
||||||
|
<OverviewItem>
|
||||||
|
<Icon name={'version'} />
|
||||||
|
{`v${version}`}
|
||||||
|
</OverviewItem>
|
||||||
|
);
|
||||||
|
|
||||||
const Package = ({ name: label, version, time, author: { name, avatar }, description, license, keywords = [] }: IProps): Element<WrapperLink> => {
|
const renderAuthorInfo = () =>
|
||||||
const renderMainInfo = () => (
|
authorName && (
|
||||||
<MainInfo>
|
<Author>
|
||||||
<Name>{label}</Name>
|
<Avatar alt={authorName} src={authorAvatar} />
|
||||||
<Version>{`v${version}`}</Version>
|
<Details>
|
||||||
</MainInfo>
|
<Text text={authorName} />
|
||||||
);
|
</Details>
|
||||||
|
</Author>
|
||||||
|
);
|
||||||
|
|
||||||
const renderAuthorInfo = () => (
|
const renderFileSize = () =>
|
||||||
<Author>
|
unpackedSize && (
|
||||||
<Avatar alt={name} src={avatar}>
|
<OverviewItem>
|
||||||
{!avatar && getInitialsName(name)}
|
<Icon name={'filebinary'} />
|
||||||
</Avatar>
|
{fileSizeSI(unpackedSize)}
|
||||||
<Details>
|
</OverviewItem>
|
||||||
<Text text={name} weight={'bold'} />
|
);
|
||||||
</Details>
|
|
||||||
</Author>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderLicenseInfo = () =>
|
const renderLicenseInfo = () =>
|
||||||
license && (
|
license && (
|
||||||
<OverviewItem>
|
<OverviewItem>
|
||||||
<Icon modifiers={spacing('margin', '4px', '5px', '0px', '0px')} name={'license'} pointer={true} />
|
<Icon name={'law'} />
|
||||||
{license}
|
{license}
|
||||||
</OverviewItem>
|
</OverviewItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderPublishedInfo = () => (
|
const renderPublishedInfo = () =>
|
||||||
<OverviewItem>
|
time && (
|
||||||
<Icon name={'time'} pointer={true} />
|
<OverviewItem>
|
||||||
<Published modifiers={spacing('margin', '0px', '5px', '0px', '0px')}>{`Published on ${formatDate(time)} •`}</Published>
|
<Icon name={'time'} />
|
||||||
{`${formatDateDistance(time)} ago`}
|
<Published>{`Published on ${formatDate(time)} •`}</Published>
|
||||||
</OverviewItem>
|
{`${formatDateDistance(time)} ago`}
|
||||||
);
|
</OverviewItem>
|
||||||
|
|
||||||
const renderDescription = () =>
|
|
||||||
description && (
|
|
||||||
<Field>
|
|
||||||
<Description>{description}</Description>
|
|
||||||
</Field>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderHomePageLink = () =>
|
||||||
|
homepage && (
|
||||||
|
<a href={homepage} target={'_blank'}>
|
||||||
|
<Tooltip aria-label={'Homepage'} title={'Visit homepage'}>
|
||||||
|
<IconButton aria-label={'Homepage'}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-max-depth */}
|
||||||
|
<HomeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderBugsLink = () =>
|
||||||
|
url && (
|
||||||
|
<a href={url} target={'_blank'}>
|
||||||
|
<Tooltip aria-label={'Bugs'} title={'Open an issue'}>
|
||||||
|
<IconButton aria-label={'Bugs'}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-max-depth */}
|
||||||
|
<BugReport />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderPrimaryComponent = () => {
|
||||||
|
return (
|
||||||
|
<Grid container={true} item={true} xs={12}>
|
||||||
|
<Grid item={true} xs={true}>
|
||||||
|
<WrapperLink to={`/-/web/detail/${packageName}`}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-max-depth */}
|
||||||
|
<PackageTitle>{packageName}</PackageTitle>
|
||||||
|
</WrapperLink>
|
||||||
|
</Grid>
|
||||||
|
<GridRightAligned item={true} xs={true}>
|
||||||
|
{renderHomePageLink()}
|
||||||
|
{renderBugsLink()}
|
||||||
|
</GridRightAligned>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSecondaryComponent = () => {
|
||||||
|
const tags = keywords.sort().map((keyword, index) => <Tag key={index}>{keyword}</Tag>);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Description component={'span'}>{description}</Description>
|
||||||
|
{tags.length > 0 && <TagContainer>{tags}</TagContainer>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperLink className={'package'} to={`/-/web/version/${label}`}>
|
<PackageList className={'package'}>
|
||||||
<Header>
|
<ListItem alignItems={'flex-start'}>
|
||||||
{renderMainInfo()}
|
<PackageListItemText className={'package-link'} component={'div'} primary={renderPrimaryComponent()} secondary={renderSecondaryComponent()} />
|
||||||
<Overview>
|
</ListItem>
|
||||||
{renderLicenseInfo()}
|
<PackageListItem alignItems={'flex-start'}>
|
||||||
{renderPublishedInfo()}
|
{renderAuthorInfo()}
|
||||||
</Overview>
|
{renderVersionInfo()}
|
||||||
</Header>
|
{renderPublishedInfo()}
|
||||||
<Content>
|
{renderFileSize()}
|
||||||
<Field>{renderAuthorInfo()}</Field>
|
{renderLicenseInfo()}
|
||||||
{renderDescription()}
|
</PackageListItem>
|
||||||
</Content>
|
</PackageList>
|
||||||
{keywords.length > 0 && (
|
|
||||||
<Footer>
|
|
||||||
{keywords.sort().map((keyword, index) => (
|
|
||||||
<Tag key={index}>{keyword}</Tag>
|
|
||||||
))}
|
|
||||||
</Footer>
|
|
||||||
)}
|
|
||||||
</WrapperLink>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Package;
|
export default Package;
|
||||||
|
@ -3,126 +3,68 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styled, { css } from 'react-emotion';
|
import styled from 'react-emotion';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { default as Photo } from '@material-ui/core/Avatar';
|
|
||||||
import { default as Ico } from '../Icon';
|
|
||||||
|
|
||||||
import mq from '../../utils/styles/media';
|
import Grid from '@material-ui/core/Grid/index';
|
||||||
import { ellipsis } from '../../utils/styles/mixings';
|
import List from '@material-ui/core/List/index';
|
||||||
import colors from '../../utils/styles/colors';
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||||
|
import MuiIconButton from '@material-ui/core/IconButton/index';
|
||||||
|
import Photo from '@material-ui/core/Avatar';
|
||||||
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
|
|
||||||
|
import { breakpoints } from '../../utils/styles/media';
|
||||||
|
import Ico from '../Icon';
|
||||||
import Label from '../Label';
|
import Label from '../Label';
|
||||||
|
import colors from '../../utils/styles/colors';
|
||||||
// HEADER
|
|
||||||
export const Header = styled.div`
|
|
||||||
&& {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 0 0 5px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Name = styled.span`
|
|
||||||
&& {
|
|
||||||
${ellipsis('50%')};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const MainInfo = styled.span`
|
|
||||||
&& {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 30px;
|
|
||||||
flex: 1;
|
|
||||||
color: #3a8bff;
|
|
||||||
padding: 0 10px 0 0;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
:hover {
|
|
||||||
${Name} {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const OverviewItem = styled.span`
|
export const OverviewItem = styled.span`
|
||||||
&& {
|
&& {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 0 5px 0;
|
margin: 0 0 0 16px;
|
||||||
color: ${colors.greyLight};
|
color: ${colors.greyLight2};
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Overview = styled.span`
|
|
||||||
&& {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Version = styled.span`
|
|
||||||
&& {
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 0 0 0 10px;
|
@media (max-width: ${breakpoints.medium}px) {
|
||||||
margin: 0 0 0 5px;
|
&:nth-child(3) {
|
||||||
color: #9f9f9f;
|
display: none;
|
||||||
position: relative;
|
}
|
||||||
${ellipsis('100%')};
|
}
|
||||||
:before {
|
@media (max-width: ${breakpoints.small}px) {
|
||||||
content: '•';
|
&:nth-child(4) {
|
||||||
position: absolute;
|
display: none;
|
||||||
left: 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Icon = styled(Ico)`
|
export const Icon = styled(Ico)`
|
||||||
&& {
|
&& {
|
||||||
margin: 1px 5px 0 0;
|
margin: 2px 10px 0px 0;
|
||||||
fill: ${colors.greyLight};
|
fill: ${colors.greyLight2};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Published = styled.span`
|
export const Published = styled.span`
|
||||||
&& {
|
&& {
|
||||||
display: none;
|
color: ${colors.greyLight2};
|
||||||
color: ${colors.greyLight};
|
margin: 0px 5px 0px 0px;
|
||||||
${({ modifiers }) => modifiers};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Content
|
|
||||||
export const Field = styled.div`
|
|
||||||
&& {
|
|
||||||
padding: 0 0 5px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Content = styled.div`
|
|
||||||
&& {
|
|
||||||
${Field} {
|
|
||||||
:last-child {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Text = styled(Label)`
|
export const Text = styled(Label)`
|
||||||
&& {
|
&& {
|
||||||
color: #908ba1;
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: ${colors.greyLight2};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Details = styled.span`
|
export const Details = styled.span`
|
||||||
&& {
|
&& {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
line-height: 14px;
|
line-height: 1.5;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@ -137,63 +79,88 @@ export const Author = styled.div`
|
|||||||
|
|
||||||
export const Avatar = styled(Photo)`
|
export const Avatar = styled(Photo)`
|
||||||
&& {
|
&& {
|
||||||
width: 30px;
|
width: 20px;
|
||||||
height: 30px;
|
height: 20px;
|
||||||
background: #4b5e40;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Description = styled.div`
|
|
||||||
&& {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Footer
|
|
||||||
export const Footer = styled.div`
|
|
||||||
&& {
|
|
||||||
display: none;
|
|
||||||
padding: 5px 0 0 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Container
|
|
||||||
export const WrapperLink = styled(Link)`
|
export const WrapperLink = styled(Link)`
|
||||||
&& {
|
&& {
|
||||||
font-size: 12px;
|
|
||||||
background-color: white;
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
transition: box-shadow 0.15s;
|
|
||||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15);
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 10px;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
|
||||||
color: #2f273c;
|
|
||||||
${mq.medium(css`
|
|
||||||
${Header} {
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
${OverviewItem} {
|
|
||||||
margin: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
${Overview} {
|
|
||||||
flex-direction: row;
|
|
||||||
${OverviewItem} {
|
|
||||||
:first-child {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${Footer} {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
${Published} {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
`)};
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const PackageTitle = styled.span`
|
||||||
|
&& {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 20px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: ${colors.eclipse};
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${colors.black};
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: ${breakpoints.small}px) {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GridRightAligned = styled(Grid)`
|
||||||
|
&& {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PackageList = styled(List)`
|
||||||
|
&& {
|
||||||
|
padding: 12px 0 12px 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: ${colors.greyLight3};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const IconButton = styled(MuiIconButton)`
|
||||||
|
&& {
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TagContainer = styled.span`
|
||||||
|
&& {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: block;
|
||||||
|
@media (max-width: ${breakpoints.medium}px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PackageListItem = styled(ListItem)`
|
||||||
|
&& {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PackageListItemText = styled(ListItemText)`
|
||||||
|
&& {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Description = styled(Typography)`
|
||||||
|
color: ${colors.greyDark2};
|
||||||
|
font-size: 14px;
|
||||||
|
padding-right: 0;
|
||||||
|
`;
|
||||||
|
@ -11,6 +11,9 @@ export interface IProps {
|
|||||||
description?: string;
|
description?: string;
|
||||||
keywords?: string[];
|
keywords?: string[];
|
||||||
license?: string;
|
license?: string;
|
||||||
|
homepage: string;
|
||||||
|
bugs: IBugs;
|
||||||
|
dist: IDist;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAuthor {
|
export interface IAuthor {
|
||||||
@ -18,3 +21,10 @@ export interface IAuthor {
|
|||||||
avatar: string;
|
avatar: string;
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IBugs {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
export interface IDist {
|
||||||
|
unpackedSize: number;
|
||||||
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import isNil from 'lodash/isNil';
|
|
||||||
|
|
||||||
import Readme from '../Readme';
|
|
||||||
|
|
||||||
import classes from './packageDetail.scss';
|
|
||||||
|
|
||||||
const displayState = (description) => {
|
|
||||||
return !isNil(description) ? <Readme description={description} /> : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const PackageDetail = ({packageName, readMe}) => {
|
|
||||||
return (
|
|
||||||
<div className={classes.pkgDetail}>
|
|
||||||
<h1 className={classes.title}>
|
|
||||||
{packageName}
|
|
||||||
</h1>
|
|
||||||
<div className={classes.readme}>
|
|
||||||
{displayState(readMe)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PackageDetail.propTypes = {
|
|
||||||
readMe: PropTypes.string,
|
|
||||||
packageName: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PackageDetail;
|
|
@ -1,16 +0,0 @@
|
|||||||
@import '../../styles/variables';
|
|
||||||
@import '../../styles/mixins';
|
|
||||||
|
|
||||||
.pkgDetail {
|
|
||||||
.title {
|
|
||||||
font-size: $font-size-xxl;
|
|
||||||
font-weight: $font-weight-semibold;
|
|
||||||
margin: 0 0 40px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
@include border-bottom-default($greyGainsboro);
|
|
||||||
}
|
|
||||||
|
|
||||||
.readme {
|
|
||||||
margin-bottom: 5em;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,11 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Divider from '@material-ui/core/Divider';
|
||||||
|
|
||||||
import Package from '../Package';
|
import Package from '../Package';
|
||||||
import Help from '../Help';
|
import Help from '../Help';
|
||||||
import { formatAuthor, formatLicense } from '../../utils/package';
|
import { formatLicense } from '../../utils/package';
|
||||||
|
|
||||||
import classes from './packageList.scss';
|
import classes from './packageList.scss';
|
||||||
|
|
||||||
@ -12,28 +14,6 @@ export default class PackageList extends React.Component {
|
|||||||
packages: PropTypes.array,
|
packages: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPackages = () => {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{this.renderList()}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderList = () => {
|
|
||||||
const { packages } = this.props;
|
|
||||||
return (
|
|
||||||
packages.map((pkg, i) => {
|
|
||||||
const { name, version, description, time, keywords } = pkg;
|
|
||||||
const author = formatAuthor(pkg.author);
|
|
||||||
const license = formatLicense(pkg.license);
|
|
||||||
return (
|
|
||||||
<Package key={i} {...{ name, version, author, description, license, time, keywords }} />
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={"package-list-items"}>
|
<div className={"package-list-items"}>
|
||||||
@ -46,7 +26,32 @@ export default class PackageList extends React.Component {
|
|||||||
|
|
||||||
hasPackages() {
|
hasPackages() {
|
||||||
const {packages} = this.props;
|
const {packages} = this.props;
|
||||||
|
|
||||||
return packages.length > 0;
|
return packages.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderPackages = () => {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{this.renderList()}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderList = () => {
|
||||||
|
const { packages } = this.props;
|
||||||
|
return (
|
||||||
|
packages.map((pkg, i) => {
|
||||||
|
const { name, version, description, time, keywords, dist, homepage, bugs } = pkg;
|
||||||
|
const author = pkg.author;
|
||||||
|
// TODO: move format license to API side.
|
||||||
|
const license = formatLicense(pkg.license);
|
||||||
|
return (
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
{i !== 0 && <Divider></Divider>}
|
||||||
|
<Package {...{ name, dist, version, author, description, license, time, keywords, homepage, bugs }} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
export default function Module({title, description, children, className}) {
|
|
||||||
return (
|
|
||||||
<div className={`${classes.module} ${className}`}>
|
|
||||||
<h2 className={classes.moduleTitle}>
|
|
||||||
{title}
|
|
||||||
{description && <span>{description}</span>}
|
|
||||||
</h2>
|
|
||||||
<div>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Module.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
description: PropTypes.string,
|
|
||||||
children: PropTypes.any.isRequired,
|
|
||||||
className: PropTypes.string,
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
@import '../../../styles/variables';
|
|
||||||
@import '../../../styles/mixins';
|
|
||||||
|
|
||||||
.module {
|
|
||||||
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
.moduleTitle {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
font-size: $font-size-lg;
|
|
||||||
margin: 0 0 10px;
|
|
||||||
padding: 5px 0;
|
|
||||||
font-weight: $font-weight-semibold;
|
|
||||||
@include border-bottom-default($greyGainsboro);
|
|
||||||
|
|
||||||
span { // description
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
color: $greyChateau;
|
|
||||||
margin-left: auto;
|
|
||||||
font-weight: $font-weight-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
export default function ModuleContentPlaceholder({text}) {
|
|
||||||
return <p className={classes.emptyPlaceholder}>{text}</p>;
|
|
||||||
}
|
|
||||||
ModuleContentPlaceholder.propTypes = {
|
|
||||||
text: PropTypes.string.isRequired,
|
|
||||||
};
|
|
@ -1,8 +0,0 @@
|
|||||||
@import '../../../styles/variables';
|
|
||||||
|
|
||||||
.emptyPlaceholder {
|
|
||||||
text-align: center;
|
|
||||||
margin: 20px 0;
|
|
||||||
font-size: $font-size-base;
|
|
||||||
color: $greyChateau;
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import LastSync from './modules/LastSync';
|
|
||||||
import DistTags from './modules/DistTags';
|
|
||||||
import Maintainers from './modules/Maintainers';
|
|
||||||
import Dependencies from './modules/Dependencies';
|
|
||||||
import PeerDependencies from './modules/PeerDependencies';
|
|
||||||
import Infos from './modules/Infos';
|
|
||||||
|
|
||||||
import {
|
|
||||||
formatLicense,
|
|
||||||
formatRepository,
|
|
||||||
getLastUpdatedPackageTime,
|
|
||||||
getRecentReleases,
|
|
||||||
} from '../../utils/package';
|
|
||||||
import API from '../../utils/api';
|
|
||||||
import {DIST_TAGS} from '../../../lib/constants';
|
|
||||||
|
|
||||||
export default class PackageSidebar extends React.Component {
|
|
||||||
state = {};
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
packageName: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.loadPackageData = this.loadPackageData.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
const { packageName } = this.props;
|
|
||||||
await this.loadPackageData(packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadPackageData(packageName) {
|
|
||||||
let packageMeta;
|
|
||||||
|
|
||||||
try {
|
|
||||||
packageMeta = await API.request(`sidebar/${packageName}`, 'GET');
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({
|
|
||||||
failed: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
packageMeta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { packageMeta } = this.state;
|
|
||||||
|
|
||||||
if (packageMeta) {
|
|
||||||
const {time, _uplinks} = packageMeta;
|
|
||||||
|
|
||||||
// Infos component
|
|
||||||
const license = formatLicense(get(packageMeta, 'latest.license', null));
|
|
||||||
const repository = formatRepository(
|
|
||||||
get(packageMeta, 'latest.repository', null)
|
|
||||||
);
|
|
||||||
const homepage = get(packageMeta, 'latest.homepage', null);
|
|
||||||
|
|
||||||
// dist-tags
|
|
||||||
const distTags = packageMeta[DIST_TAGS];
|
|
||||||
|
|
||||||
// Lastsync component
|
|
||||||
const recentReleases = getRecentReleases(time);
|
|
||||||
const lastUpdated = getLastUpdatedPackageTime(_uplinks);
|
|
||||||
|
|
||||||
// Dependencies component
|
|
||||||
const dependencies = get(packageMeta, 'latest.dependencies', {});
|
|
||||||
const peerDependencies = get(packageMeta, 'latest.peerDependencies', {});
|
|
||||||
|
|
||||||
// Maintainers component
|
|
||||||
return (
|
|
||||||
<aside className={'sidebar-info'}>
|
|
||||||
{time && (
|
|
||||||
<LastSync
|
|
||||||
lastUpdated={lastUpdated}
|
|
||||||
recentReleases={recentReleases}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<DistTags distTags={distTags} />
|
|
||||||
<Infos
|
|
||||||
homepage={homepage}
|
|
||||||
license={license}
|
|
||||||
repository={repository}
|
|
||||||
/>
|
|
||||||
{/* TODO: Refacor later, when we decide to show only maintainers/authors */}
|
|
||||||
<Maintainers packageMeta={packageMeta} />
|
|
||||||
<Dependencies dependencies={dependencies} />
|
|
||||||
<PeerDependencies dependencies={peerDependencies} />
|
|
||||||
{/* Package management module? Help us implement it! */}
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<aside className={'sidebar-loading'}>{'Loading package information...'}</aside>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Module from '../../Module';
|
|
||||||
|
|
||||||
import {getDetailPageURL} from '../../../../utils/url';
|
|
||||||
import ModuleContentPlaceholder from '../../ModuleContentPlaceholder';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
export const NO_DEPENDENCIES = 'Zero Dependencies!';
|
|
||||||
export const DEP_ITEM_CLASS = 'dependency-item';
|
|
||||||
|
|
||||||
const renderDependenciesList = (dependencies, dependenciesList) => {
|
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{dependenciesList.map((dependenceName, index) => {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={DEP_ITEM_CLASS}
|
|
||||||
key={index}
|
|
||||||
title={`Depend on version: ${dependencies[dependenceName]}`}
|
|
||||||
>
|
|
||||||
<a href={getDetailPageURL(dependenceName)}>{dependenceName}</a>
|
|
||||||
{index < dependenciesList.length - 1 && <span>{', '}</span>}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dependencies = ({dependencies = {}, title = 'Dependencies'}) => {
|
|
||||||
const dependenciesList = Object.keys(dependencies);
|
|
||||||
return (
|
|
||||||
<Module className={classes.dependenciesModule} title={title}>
|
|
||||||
{dependenciesList.length > 0 ? (
|
|
||||||
renderDependenciesList(dependencies, dependenciesList)
|
|
||||||
) : (
|
|
||||||
<ModuleContentPlaceholder text={NO_DEPENDENCIES} />
|
|
||||||
)}
|
|
||||||
</Module>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Dependencies.propTypes = {
|
|
||||||
dependencies: PropTypes.object,
|
|
||||||
title: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Dependencies;
|
|
@ -1,13 +0,0 @@
|
|||||||
@import '../../../../styles/variables';
|
|
||||||
|
|
||||||
.dependenciesModule {
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
line-height: $line-height-xxs;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import propTypes from 'prop-types';
|
|
||||||
import Module from '../../Module';
|
|
||||||
import ModuleContentPlaceholder from '../../ModuleContentPlaceholder';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
const renderDistTags = (distTags) => {
|
|
||||||
|
|
||||||
const tags = Object.entries(distTags);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{tags.map((tagItem) => {
|
|
||||||
const [tag, version] = tagItem;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li className={'dist-tag-item'} key={tag}>
|
|
||||||
<span>{tag}</span>
|
|
||||||
<span>{version}</span>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DistTags = ({distTags = {}}) => {
|
|
||||||
const hasTags = Object.keys(distTags).length > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Module
|
|
||||||
className={classes.releasesModule}
|
|
||||||
description={''}
|
|
||||||
title={'Dist-Tags'}
|
|
||||||
>
|
|
||||||
{hasTags ? (
|
|
||||||
renderDistTags(distTags)
|
|
||||||
) : (
|
|
||||||
<ModuleContentPlaceholder text={'Not Available!'} />
|
|
||||||
)}
|
|
||||||
</Module>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
DistTags.propTypes = {
|
|
||||||
distTags: propTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DistTags;
|
|
@ -1,13 +0,0 @@
|
|||||||
@import '../../../../styles/variables';
|
|
||||||
|
|
||||||
.releasesModule {
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
line-height: $line-height-xs;
|
|
||||||
|
|
||||||
span:last-child {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Module from '../../Module';
|
|
||||||
import ModuleContentPlaceholder from '../../ModuleContentPlaceholder';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
const renderSection = (title, url) => (
|
|
||||||
<li>
|
|
||||||
<span>{title}</span>
|
|
||||||
<a href={url} rel={'noopener noreferrer'} target={'_blank'}>
|
|
||||||
{url}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Infos = ({homepage, repository, license}) => {
|
|
||||||
const showInfo = homepage || repository || license;
|
|
||||||
return (
|
|
||||||
<Module className={classes.infosModule} title={'Infos'}>
|
|
||||||
{showInfo ? (
|
|
||||||
<ul>
|
|
||||||
{homepage && renderSection('Homepage', homepage)}
|
|
||||||
{repository && renderSection('Repository', repository)}
|
|
||||||
{license && (
|
|
||||||
<li>
|
|
||||||
<span>{'License'}</span>
|
|
||||||
<span>{license}</span>
|
|
||||||
</li>)}
|
|
||||||
</ul>) : <ModuleContentPlaceholder text={'Not Available!'} />}
|
|
||||||
</Module>);
|
|
||||||
};
|
|
||||||
|
|
||||||
Infos.propTypes = {
|
|
||||||
homepage: PropTypes.string,
|
|
||||||
repository: PropTypes.string,
|
|
||||||
license: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Infos;
|
|
@ -1,21 +0,0 @@
|
|||||||
@import '../../../../styles/variables';
|
|
||||||
@import '../../../../styles/mixins';
|
|
||||||
|
|
||||||
.infosModule {
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
line-height: $line-height-xs;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
max-width: 150px;
|
|
||||||
@include ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:last-child,
|
|
||||||
span:last-child {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import propTypes from 'prop-types';
|
|
||||||
import Module from '../../Module';
|
|
||||||
import ModuleContentPlaceholder from '../../ModuleContentPlaceholder';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
const renderRecentReleases = (recentReleases) => (
|
|
||||||
<ul>
|
|
||||||
{recentReleases.map((versionInfo) => {
|
|
||||||
const {version, time} = versionInfo;
|
|
||||||
return (
|
|
||||||
<li className={'last-sync-item'} key={version}>
|
|
||||||
<span>{version}</span>
|
|
||||||
<span>{time}</span>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
|
|
||||||
const LastSync = ({recentReleases = [], lastUpdated = ''}) => {
|
|
||||||
return (
|
|
||||||
<Module
|
|
||||||
className={classes.releasesModule}
|
|
||||||
description={lastUpdated}
|
|
||||||
title={'Last Sync'}
|
|
||||||
>
|
|
||||||
{recentReleases.length ? (
|
|
||||||
renderRecentReleases(recentReleases)
|
|
||||||
) : (
|
|
||||||
<ModuleContentPlaceholder text={'Not Available!'} />
|
|
||||||
)}
|
|
||||||
</Module>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
LastSync.propTypes = {
|
|
||||||
recentReleases: propTypes.array,
|
|
||||||
lastUpdated: propTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LastSync;
|
|
@ -1,13 +0,0 @@
|
|||||||
@import '../../../../styles/variables';
|
|
||||||
|
|
||||||
.releasesModule {
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
line-height: $line-height-xs;
|
|
||||||
|
|
||||||
span:last-child {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
const MaintainerInfo = ({title, name, avatar}) => {
|
|
||||||
const avatarDescription = `${title} ${name}'s avatar`;
|
|
||||||
return (
|
|
||||||
<div className={classes.maintainer} title={name}>
|
|
||||||
<img alt={avatarDescription} src={avatar} title={avatarDescription} />
|
|
||||||
<span className={'maintainer-name'}>{name}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
MaintainerInfo.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
avatar: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MaintainerInfo;
|
|
@ -1,26 +0,0 @@
|
|||||||
@import '../../../../../styles/variables';
|
|
||||||
@import '../../../../../styles/mixins';
|
|
||||||
|
|
||||||
.maintainer {
|
|
||||||
display: flex;
|
|
||||||
line-height: $line-height-xl;
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
margin-right: 10px;
|
|
||||||
border-radius: 100%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
flex-shrink: 1;
|
|
||||||
@include ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import filter from 'lodash/filter';
|
|
||||||
import size from 'lodash/size';
|
|
||||||
import has from 'lodash/has';
|
|
||||||
import uniqBy from 'lodash/uniqBy';
|
|
||||||
|
|
||||||
import Module from '../../Module';
|
|
||||||
import MaintainerInfo from './MaintainerInfo';
|
|
||||||
import ModuleContentPlaceholder from '../../ModuleContentPlaceholder';
|
|
||||||
|
|
||||||
import classes from './style.scss';
|
|
||||||
|
|
||||||
const CONTRIBUTORS_TO_SHOW = 5;
|
|
||||||
|
|
||||||
export default class Maintainers extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
packageMeta: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.handleShowAllContributors = this.handleShowAllContributors.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get author() {
|
|
||||||
return get(this, 'props.packageMeta.latest.author');
|
|
||||||
}
|
|
||||||
|
|
||||||
get contributors() {
|
|
||||||
const contributors = get(this, 'props.packageMeta.latest.contributors', {});
|
|
||||||
return filter(contributors, (contributor) => {
|
|
||||||
return (
|
|
||||||
contributor.name !== get(this, 'author.name') &&
|
|
||||||
contributor.email !== get(this, 'author.email')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get showAllContributors() {
|
|
||||||
const { showAllContributors } = this.state;
|
|
||||||
return showAllContributors || size(this.contributors) <= 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
get uniqueContributors() {
|
|
||||||
if (!this.contributors) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return uniqBy(this.contributors, (contributor) => contributor.name).slice(
|
|
||||||
0,
|
|
||||||
CONTRIBUTORS_TO_SHOW
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleShowAllContributors() {
|
|
||||||
this.setState({
|
|
||||||
showAllContributors: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContributors() {
|
|
||||||
if (!this.contributors) return null;
|
|
||||||
|
|
||||||
return (this.showAllContributors
|
|
||||||
? this.contributors
|
|
||||||
: this.uniqueContributors
|
|
||||||
).map((contributor, index) => {
|
|
||||||
return (
|
|
||||||
<MaintainerInfo
|
|
||||||
avatar={contributor.avatar}
|
|
||||||
key={index}
|
|
||||||
name={contributor.name}
|
|
||||||
title={'Contributors'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAuthorAndContributors(author) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ul className={'maintainer-author'}>
|
|
||||||
{author &&
|
|
||||||
author.name && (
|
|
||||||
<MaintainerInfo
|
|
||||||
avatar={author.avatar}
|
|
||||||
name={author.name}
|
|
||||||
title={'Author'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{this.renderContributors()}
|
|
||||||
</ul>
|
|
||||||
{!this.showAllContributors && (
|
|
||||||
<button
|
|
||||||
className={classes.showAllContributors}
|
|
||||||
onClick={this.handleShowAllContributors}
|
|
||||||
title={'Current list only show the author and first 5 contributors unique by name'}
|
|
||||||
>
|
|
||||||
{'Show all contributor'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const contributors = this.renderContributors();
|
|
||||||
return (
|
|
||||||
<Module className={classes.maintainersModule} title={'Maintainers'}>
|
|
||||||
{contributors.length || has(this.author, 'name') ? (
|
|
||||||
this.renderAuthorAndContributors(this.author)
|
|
||||||
) : (
|
|
||||||
<ModuleContentPlaceholder text={'Not Available!'} />
|
|
||||||
)}
|
|
||||||
</Module>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
@import '../../../../styles/variables';
|
|
||||||
|
|
||||||
.maintainersModule {
|
|
||||||
.showAllContributors {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Dependencies from '../Dependencies';
|
|
||||||
|
|
||||||
export const TITLE = 'Peer Dependencies';
|
|
||||||
|
|
||||||
const PeerDependencies = ({dependencies = {}, title = TITLE}) => {
|
|
||||||
return (
|
|
||||||
<Dependencies dependencies={dependencies} title={title} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PeerDependencies.propTypes = {
|
|
||||||
dependencies: PropTypes.object,
|
|
||||||
title: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PeerDependencies;
|
|
BIN
src/components/Repository/img/git.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
@ -1,61 +1,59 @@
|
|||||||
/* eslint no-unused-vars: 0 */
|
|
||||||
/* eslint react/jsx-max-depth: 0 */
|
/* eslint react/jsx-max-depth: 0 */
|
||||||
|
|
||||||
import React, {Component, Fragment} from 'react';
|
import React, {Component} from 'react';
|
||||||
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
|
import List from '@material-ui/core/List';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
|
||||||
import { DetailContextConsumer } from '../../pages/version/index';
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
import Card from '@material-ui/core/Card/index';
|
|
||||||
import CardContent from '@material-ui/core/CardContent/index';
|
|
||||||
import Grid from '@material-ui/core/Grid/index';
|
|
||||||
import GitHub from '../../icons/GitHub';
|
|
||||||
import CopyToClipBoard from '../CopyToClipBoard';
|
import CopyToClipBoard from '../CopyToClipBoard';
|
||||||
import BugReport from '@material-ui/icons/BugReport';
|
|
||||||
import CardActions from '@material-ui/core/CardActions/index';
|
import { Heading, GithubLink, RepositoryListItem } from './styles';
|
||||||
import Button from '@material-ui/core/Button';
|
import git from './img/git.png';
|
||||||
import {GridRepo} from './styles';
|
|
||||||
|
|
||||||
class Repository extends Component<any, any> {
|
class Repository extends Component<any, any> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<DetailContextConsumer>
|
<DetailContextConsumer>
|
||||||
{(context) => {
|
{(context) => {
|
||||||
return this.renderAuthor(context);
|
return this.renderRepository(context);
|
||||||
}}
|
}}
|
||||||
</DetailContextConsumer>
|
</DetailContextConsumer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderAuthor = ({packageMeta}) => {
|
renderRepositoryText(url) {
|
||||||
const { repository, bugs } = packageMeta.latest;
|
return (<GithubLink href={url} target={"_blank"}>{url}</GithubLink>);
|
||||||
if (!repository) {
|
}
|
||||||
|
|
||||||
|
renderRepository = ({packageMeta}) => {
|
||||||
|
const {
|
||||||
|
repository: {
|
||||||
|
url,
|
||||||
|
} = {},
|
||||||
|
} = packageMeta.latest;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<>
|
||||||
<CardContent style={{ textAling: 'center'}}>
|
<List dense={true} subheader={<Heading variant={"subheading"}>{'Repository'}</Heading>}>
|
||||||
<GridRepo container={true} spacing={24}>
|
<RepositoryListItem>
|
||||||
{this.renderRepository(repository, bugs)}
|
<Avatar src={git} />
|
||||||
</GridRepo>
|
<ListItemText primary={this.renderContent(url)} />
|
||||||
</CardContent>
|
</RepositoryListItem>
|
||||||
<CardActions>
|
</List>
|
||||||
<Button size={"small"}>{'Open Bugs'}</Button>
|
</>
|
||||||
<Button size={"small"}>{'Open Repository'}</Button>
|
|
||||||
</CardActions>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderRepository = ({url, type}, bugs) => {
|
renderContent(url) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<CopyToClipBoard text={url}>
|
||||||
<Grid item={true} xs={3}>
|
{this.renderRepositoryText(url)}
|
||||||
<GitHub style={{ fontSize: 45 }} />
|
</CopyToClipBoard>
|
||||||
</Grid>
|
|
||||||
<Grid item={true} xs={9}>
|
|
||||||
<CopyToClipBoard text={url} />
|
|
||||||
</Grid>
|
|
||||||
</Fragment>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,42 @@
|
|||||||
|
|
||||||
import styled from 'react-emotion';
|
import styled from 'react-emotion';
|
||||||
import Grid from '@material-ui/core/Grid/index';
|
import Grid from '@material-ui/core/Grid/index';
|
||||||
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
|
import Typography from '@material-ui/core/Typography/index';
|
||||||
|
|
||||||
|
import Github from '../../icons/GitHub';
|
||||||
|
import colors from '../../utils/styles/colors';
|
||||||
|
|
||||||
|
export const Heading = styled(Typography)`
|
||||||
|
&& {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const GridRepo = styled(Grid)`
|
export const GridRepo = styled(Grid)`
|
||||||
&& {
|
&& {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const GithubLink = styled('a')`
|
||||||
|
&& {
|
||||||
|
color: ${colors.primary};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GithubLogo = styled(Github)`
|
||||||
|
&& {
|
||||||
|
font-size: 40px;
|
||||||
|
color: ${colors.primary};
|
||||||
|
background-color: ${colors.greySuperLight};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RepositoryListItem = styled(ListItem)`
|
||||||
|
&& {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -100,7 +100,7 @@ export class Search extends Component<IProps, IState> {
|
|||||||
case 'enter':
|
case 'enter':
|
||||||
this.setState({ search: '' });
|
this.setState({ search: '' });
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
history.push(`/-/web/version/${suggestionValue}`);
|
history.push(`/-/web/detail/${suggestionValue}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -11,10 +11,10 @@ export const Wrapper = styled.span`
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
color: #9f9f9f;
|
color: #485a3e;
|
||||||
background-color: hsla(0, 0%, 51%, 0.1);
|
background-color: #f3f4f2;
|
||||||
padding: 0.22rem 0.4rem;
|
padding: 0.22rem 0.4rem;
|
||||||
margin: 5px 10px 0 0;
|
margin: 8px 8px 0 0;
|
||||||
${ellipsis('300px')};
|
${ellipsis('300px')};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* @prettier
|
* @prettier
|
||||||
*/
|
*/
|
||||||
|
import React from 'react';
|
||||||
import { DetailContextConsumer } from '../../pages/version/index';
|
|
||||||
import { formatDateDistance } from '../../utils/package';
|
|
||||||
import { Heading, Spacer, ListItemText } from './styles';
|
|
||||||
import List from '@material-ui/core/List/index';
|
import List from '@material-ui/core/List/index';
|
||||||
import ListItem from '@material-ui/core/ListItem/index';
|
import ListItem from '@material-ui/core/ListItem/index';
|
||||||
import React from 'react';
|
|
||||||
|
import { DetailContextConsumer } from '../../pages/version/index';
|
||||||
|
import NoItems from '../NoItems';
|
||||||
|
import { formatDateDistance } from '../../utils/package';
|
||||||
|
|
||||||
|
import { Heading, Spacer, ListItemText } from './styles';
|
||||||
|
|
||||||
class UpLinks extends React.PureComponent<any> {
|
class UpLinks extends React.PureComponent<any> {
|
||||||
render() {
|
render() {
|
||||||
@ -15,7 +17,7 @@ class UpLinks extends React.PureComponent<any> {
|
|||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
<DetailContextConsumer>
|
<DetailContextConsumer>
|
||||||
{({ packageMeta }) => {
|
{({ packageMeta }) => {
|
||||||
return this.renderContent(packageMeta._uplinks);
|
return this.renderContent(packageMeta._uplinks, packageMeta.latest);
|
||||||
}}
|
}}
|
||||||
</DetailContextConsumer>
|
</DetailContextConsumer>
|
||||||
);
|
);
|
||||||
@ -35,15 +37,18 @@ class UpLinks extends React.PureComponent<any> {
|
|||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
|
|
||||||
renderContent(uplinks) {
|
renderContent(uplinks, { name }) {
|
||||||
return (
|
if (Object.keys(uplinks).length > 0) {
|
||||||
uplinks && (
|
return (
|
||||||
<>
|
uplinks && (
|
||||||
<Heading variant={'subheading'}>{'Uplinks'}</Heading>
|
<>
|
||||||
{this.renderUpLinksList(uplinks)}
|
<Heading variant={'subheading'}>{'Uplinks'}</Heading>
|
||||||
</>
|
{this.renderUpLinksList(uplinks)}
|
||||||
)
|
</>
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <NoItems text={`${name} has no uplinks.`} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,40 +17,44 @@ class Versions extends React.PureComponent<any> {
|
|||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
<DetailContextConsumer>
|
<DetailContextConsumer>
|
||||||
{({ packageMeta }) => {
|
{({ packageMeta }) => {
|
||||||
return this.renderContent(packageMeta[DIST_TAGS], packageMeta.versions);
|
return this.renderContent(packageMeta);
|
||||||
}}
|
}}
|
||||||
</DetailContextConsumer>
|
</DetailContextConsumer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPackageList = (packages: any, isVersion: boolean = false) => (
|
renderPackageList = (packages: any, isVersion: boolean = false, timeMap: Object = {}) => {
|
||||||
<List>
|
return (
|
||||||
{Object.keys(packages)
|
<List>
|
||||||
.reverse()
|
{Object.keys(packages)
|
||||||
.map(version => (
|
.reverse()
|
||||||
<ListItem key={version}>
|
.map(version => (
|
||||||
<ListItemText>{version}</ListItemText>
|
<ListItem className={'version-item'} key={version}>
|
||||||
<Spacer />
|
<ListItemText>{version}</ListItemText>
|
||||||
<ListItemText>{isVersion ? `${formatDateDistance('2017-10-26T09:03:15.044Z')} ago` : packages[version]}</ListItemText>
|
<Spacer />
|
||||||
</ListItem>
|
<ListItemText>{isVersion && timeMap[version] ? `${formatDateDistance(timeMap[version])} ago` : packages[version]}</ListItemText>
|
||||||
))}
|
</ListItem>
|
||||||
</List>
|
))}
|
||||||
);
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
renderContent(distTags: object, versions: object) {
|
renderContent(packageMeta) {
|
||||||
|
const { versions = {}, time: timeMap = {}, [DIST_TAGS]: distTags = {} } = packageMeta;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{distTags && (
|
{distTags && (
|
||||||
<>
|
<>
|
||||||
<Heading variant={'subheading'}>{'Current Tags'}</Heading>
|
<Heading variant={'subheading'}>{'Current Tags'}</Heading>
|
||||||
{this.renderPackageList(distTags)}
|
{this.renderPackageList(distTags, false, timeMap)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{versions && (
|
{versions && (
|
||||||
<>
|
<>
|
||||||
<Heading variant={'subheading'}>{'Version History'}</Heading>
|
<Heading variant={'subheading'}>{'Version History'}</Heading>
|
||||||
{this.renderPackageList(versions, true)}
|
{this.renderPackageList(versions, true, timeMap)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -2,14 +2,8 @@
|
|||||||
* @prettier
|
* @prettier
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {createBrowserHistory} from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
|
|
||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
|
|
||||||
// Listen for changes to the current location.
|
|
||||||
history.listen((location, action) => {
|
|
||||||
// location is an object like window.location
|
|
||||||
console.log('====>', action, location.pathname, location.state);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default history;
|
export default history;
|
||||||
|
16
src/index.js
@ -1,12 +1,14 @@
|
|||||||
import React from "react";
|
import './utils/__setPublicPath__';
|
||||||
import ReactDOM from "react-dom";
|
|
||||||
import { AppContainer } from "react-hot-loader";
|
|
||||||
|
|
||||||
import App from "./app";
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import {AppContainer} from 'react-hot-loader';
|
||||||
|
|
||||||
const rootNode = document.getElementById("root");
|
import App from './app';
|
||||||
|
|
||||||
const renderApp = Component => {
|
const rootNode = document.getElementById('root');
|
||||||
|
|
||||||
|
const renderApp = (Component) => {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<Component />
|
<Component />
|
||||||
@ -18,7 +20,7 @@ const renderApp = Component => {
|
|||||||
renderApp(App);
|
renderApp(App);
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept("./app", () => {
|
module.hot.accept('./app', () => {
|
||||||
renderApp(App);
|
renderApp(App);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
@import '../../styles/variables';
|
|
||||||
@import '../../styles/mixins';
|
|
||||||
|
|
||||||
.twoColumn {
|
|
||||||
@include container-size;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
&:first-child {
|
|
||||||
flex-shrink: 1;
|
|
||||||
min-width: 300px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> aside {
|
|
||||||
&:last-child {
|
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
padding-left: 15px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 285px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
|
|
||||||
import PackageDetail from '../../components/PackageDetail';
|
|
||||||
import NotFound from '../../components/NotFound';
|
|
||||||
import Spinner from '../../components/Spinner';
|
|
||||||
import API from '../../utils/api';
|
|
||||||
|
|
||||||
import classes from './detail.scss';
|
|
||||||
import PackageSidebar from '../../components/PackageSidebar/index';
|
|
||||||
|
|
||||||
export default class Detail extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
match: PropTypes.object,
|
|
||||||
isUserLoggedIn: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
readMe: '',
|
|
||||||
notFound: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
getPackageName(props = this.props) {
|
|
||||||
const params = props.match.params;
|
|
||||||
return `${(params.scope && '@' + params.scope + '/') || ''}${
|
|
||||||
params.package
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get packageName() {
|
|
||||||
return this.getPackageName();
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
await this.loadPackageInfo(this.packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const { isUserLoggedIn, match } = this.props;
|
|
||||||
const condition1 = prevProps.isUserLoggedIn !== isUserLoggedIn;
|
|
||||||
const condition2 =
|
|
||||||
prevProps.match.params.package !== match.params.package;
|
|
||||||
if (condition1 || condition2) {
|
|
||||||
const packageName = this.getPackageName(this.props);
|
|
||||||
this.loadPackageInfo(packageName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadPackageInfo(packageName) {
|
|
||||||
this.setState({
|
|
||||||
readMe: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await API.request(`package/readme/${packageName}`, 'GET');
|
|
||||||
this.setState({
|
|
||||||
readMe: resp,
|
|
||||||
notFound: false,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({
|
|
||||||
notFound: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { notFound, readMe } = this.state;
|
|
||||||
|
|
||||||
if (notFound) {
|
|
||||||
return (
|
|
||||||
<div className={'container content'}>
|
|
||||||
<NotFound pkg={this.packageName} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (isEmpty(readMe)) {
|
|
||||||
return <Spinner centered={true} />;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={`container content ${classes.twoColumn}`}>
|
|
||||||
<PackageDetail packageName={this.packageName} readMe={readMe} />
|
|
||||||
<PackageSidebar packageName={this.packageName} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import PackageList from '../../components/PackageList';
|
import PackageList from '../../components/PackageList';
|
||||||
@ -10,10 +10,10 @@ class Home extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {packages} = this.props;
|
const { packages } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={'container content'}>
|
<div className={"container content"}>
|
||||||
<PackageList packages={packages} />
|
<PackageList packages={packages} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react';
|
||||||
import Grid from '@material-ui/core/Grid/index';
|
import Grid from '@material-ui/core/Grid/index';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
import DetailContainer from '../../components/DetailContainer';
|
import DetailContainer from '../../components/DetailContainer';
|
||||||
import DetailSidebar from '../../components/DetailSidebar';
|
import DetailSidebar from '../../components/DetailSidebar';
|
||||||
import {callDetailPage} from '../../utils/calls';
|
import { callDetailPage } from '../../utils/calls';
|
||||||
import {getRouterPackageName} from '../../utils/package';
|
import { getRouterPackageName } from '../../utils/package';
|
||||||
|
import NotFound from '../../components/NotFound';
|
||||||
|
|
||||||
export const DetailContext = React.createContext();
|
export const DetailContext = React.createContext();
|
||||||
|
|
||||||
@ -35,9 +36,9 @@ class VersionPage extends Component<any, any> {
|
|||||||
|
|
||||||
/* eslint no-unused-vars: 0 */
|
/* eslint no-unused-vars: 0 */
|
||||||
async componentDidUpdate(nextProps: any, prevState: any) {
|
async componentDidUpdate(nextProps: any, prevState: any) {
|
||||||
const {packageName} = this.state;
|
const { packageName } = this.state;
|
||||||
if (packageName !== prevState.packageName) {
|
if (packageName !== prevState.packageName) {
|
||||||
const {readMe, packageMeta} = await callDetailPage(packageName);
|
const { readMe, packageMeta } = await callDetailPage(packageName);
|
||||||
this.setState({
|
this.setState({
|
||||||
readMe,
|
readMe,
|
||||||
packageMeta,
|
packageMeta,
|
||||||
@ -49,7 +50,7 @@ class VersionPage extends Component<any, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps: any, prevState: any) {
|
static getDerivedStateFromProps(nextProps: any, prevState: any) {
|
||||||
const {match} = nextProps;
|
const { match } = nextProps;
|
||||||
const packageName = getRouterPackageName(match);
|
const packageName = getRouterPackageName(match);
|
||||||
|
|
||||||
if (packageName !== prevState.packageName) {
|
if (packageName !== prevState.packageName) {
|
||||||
@ -70,7 +71,7 @@ class VersionPage extends Component<any, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadPackageInfo() {
|
async loadPackageInfo() {
|
||||||
const {packageName} = this.state;
|
const { packageName } = this.state;
|
||||||
// FIXME: use utility
|
// FIXME: use utility
|
||||||
document.title = `Verdaccio - ${packageName}`;
|
document.title = `Verdaccio - ${packageName}`;
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ class VersionPage extends Component<any, any> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {readMe, packageMeta} = await callDetailPage(packageName);
|
const { readMe, packageMeta } = await callDetailPage(packageName);
|
||||||
this.setState({
|
this.setState({
|
||||||
readMe,
|
readMe,
|
||||||
packageMeta,
|
packageMeta,
|
||||||
@ -103,11 +104,15 @@ class VersionPage extends Component<any, any> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {isLoading, packageMeta, readMe, packageName} = this.state;
|
const { isLoading, packageMeta, readMe, packageName } = this.state;
|
||||||
|
|
||||||
if (isLoading === false) {
|
if (isLoading) {
|
||||||
|
return <Loading />;
|
||||||
|
} else if (!packageMeta) {
|
||||||
|
return <NotFound />;
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<DetailContextProvider value={{packageMeta, readMe, packageName, enableLoading: this.enableLoading}}>
|
<DetailContextProvider value={{ packageMeta, readMe, packageName, enableLoading: this.enableLoading }}>
|
||||||
<Grid className={'container content'} container={true} spacing={0}>
|
<Grid className={'container content'} container={true} spacing={0}>
|
||||||
<Grid item={true} xs={8}>
|
<Grid item={true} xs={8}>
|
||||||
{this.renderDetail()}
|
{this.renderDetail()}
|
||||||
@ -118,8 +123,6 @@ class VersionPage extends Component<any, any> {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</DetailContextProvider>
|
</DetailContextProvider>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return <Loading />;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,19 +5,17 @@
|
|||||||
|
|
||||||
/* eslint react/jsx-max-depth:0 */
|
/* eslint react/jsx-max-depth:0 */
|
||||||
|
|
||||||
import React, { Component, Fragment } from "react";
|
import React, { Component, Fragment } from 'react';
|
||||||
import { Router, Route, Switch } from "react-router-dom";
|
import { Router, Route, Switch } from 'react-router-dom';
|
||||||
import { AppContextConsumer } from "./app";
|
import { AppContextConsumer } from './app';
|
||||||
|
|
||||||
// import {asyncComponent} from '../others/utils/asyncComponent';
|
import { asyncComponent } from './utils/asyncComponent';
|
||||||
import history from "./history";
|
import history from './history';
|
||||||
import Header from "./components/Header";
|
import Header from './components/Header';
|
||||||
import HomePage from "./pages/home";
|
|
||||||
import NotFound from './components/NotFound'
|
const NotFound = asyncComponent(() => import('./components/NotFound'));
|
||||||
// const NotFound = asyncComponent(() => import("./components/NotFound"));
|
const VersionPackage = asyncComponent(() => import('./pages/version'));
|
||||||
// const DetailPackage = asyncComponent(() => import("./pages/detail"));
|
const HomePage = asyncComponent(() => import('./pages/home'));
|
||||||
// const VersionPackage = asyncComponent(() => import("./pages/version"));
|
|
||||||
// const HomePage = asyncComponent(() => import("./pages/home"));
|
|
||||||
|
|
||||||
class RouterApp extends Component<any, any> {
|
class RouterApp extends Component<any, any> {
|
||||||
render() {
|
render() {
|
||||||
@ -26,11 +24,9 @@ class RouterApp extends Component<any, any> {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
{this.renderHeader()}
|
{this.renderHeader()}
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact={true} path={"/"} render={this.renderHomePage} />
|
<Route exact={true} path={'/'} render={this.renderHomePage} />
|
||||||
{/* <Route exact={true} path={'/-/web/detail/@:scope/:package'} render={this.renderDetailPage} />
|
<Route exact={true} path={'/-/web/detail/@:scope/:package'} render={this.renderVersionPage} />
|
||||||
<Route exact={true} path={'/-/web/detail/:package'} render={this.renderDetailPage} />
|
<Route exact={true} path={'/-/web/detail/:package'} render={this.renderVersionPage} />
|
||||||
<Route exact={true} path={'/-/web/version/@:scope/:package'} render={this.renderVersionPage} />
|
|
||||||
<Route exact={true} path={'/-/web/version/:package'} render={this.renderVersionPage} /> */}
|
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@ -44,15 +40,7 @@ class RouterApp extends Component<any, any> {
|
|||||||
return (
|
return (
|
||||||
<AppContextConsumer>
|
<AppContextConsumer>
|
||||||
{function renderConsumerVersionPage({ logoUrl, scope, user }) {
|
{function renderConsumerVersionPage({ logoUrl, scope, user }) {
|
||||||
return (
|
return <Header logo={logoUrl} onLogout={onLogout} onToggleLoginModal={onToggleLoginModal} scope={scope} username={user.username} />;
|
||||||
<Header
|
|
||||||
logo={logoUrl}
|
|
||||||
onLogout={onLogout}
|
|
||||||
onToggleLoginModal={onToggleLoginModal}
|
|
||||||
scope={scope}
|
|
||||||
username={user.username}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
</AppContextConsumer>
|
</AppContextConsumer>
|
||||||
);
|
);
|
||||||
@ -62,21 +50,7 @@ class RouterApp extends Component<any, any> {
|
|||||||
return (
|
return (
|
||||||
<AppContextConsumer>
|
<AppContextConsumer>
|
||||||
{function renderConsumerVersionPage({ isUserLoggedIn, packages }) {
|
{function renderConsumerVersionPage({ isUserLoggedIn, packages }) {
|
||||||
return (
|
return <HomePage isUserLoggedIn={isUserLoggedIn} packages={packages} />;
|
||||||
<HomePage isUserLoggedIn={isUserLoggedIn} packages={packages} />
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</AppContextConsumer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderDetailPage = (routerProps: any) => {
|
|
||||||
return (
|
|
||||||
<AppContextConsumer>
|
|
||||||
{function renderConsumerVersionPage({ isUserLoggedIn }) {
|
|
||||||
return (
|
|
||||||
<DetailPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
</AppContextConsumer>
|
</AppContextConsumer>
|
||||||
);
|
);
|
||||||
@ -86,9 +60,7 @@ class RouterApp extends Component<any, any> {
|
|||||||
return (
|
return (
|
||||||
<AppContextConsumer>
|
<AppContextConsumer>
|
||||||
{function renderConsumerVersionPage({ isUserLoggedIn }) {
|
{function renderConsumerVersionPage({ isUserLoggedIn }) {
|
||||||
return (
|
return <VersionPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />;
|
||||||
<VersionPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
</AppContextConsumer>
|
</AppContextConsumer>
|
||||||
);
|
);
|
||||||
|
@ -37,9 +37,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin container-size {
|
@mixin container-size {
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
@media screen and (min-width: $break-lg) {
|
@media screen and (min-width: $break-lg) {
|
||||||
max-width: $break-lg;
|
max-width: $break-lg;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
if (!__DEBUG__) {
|
if (!__DEBUG__) {
|
||||||
__webpack_public_path__ = window.VERDACCIO_API_URL.replace(/\/verdaccio\/$/, '/static/'); // eslint-disable-line
|
__webpack_public_path__ = window.VERDACCIO_API_URL.replace(/\/verdaccio\/$/, '/static/') // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
@ -2,48 +2,48 @@ import storage from './storage';
|
|||||||
|
|
||||||
class API {
|
class API {
|
||||||
request(url, method = 'GET', options = {}) {
|
request(url, method = 'GET', options = {}) {
|
||||||
if (!window.VERDACCIO_API_URL) {
|
if (!window.VERDACCIO_API_URL) {
|
||||||
throw new Error('VERDACCIO_API_URL is not defined!');
|
throw new Error('VERDACCIO_API_URL is not defined!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = storage.getItem('token');
|
const token = storage.getItem('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
if (!options.headers) options.headers = {};
|
if (!options.headers) options.headers = {};
|
||||||
|
|
||||||
options.headers.authorization = `Bearer ${token}`;
|
options.headers.authorization = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!['http://', 'https://', '//'].some((prefix) => url.startsWith(prefix))) {
|
if (!['http://', 'https://', '//'].some((prefix) => url.startsWith(prefix))) {
|
||||||
url = window.VERDACCIO_API_URL + url;
|
url = window.VERDACCIO_API_URL + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles response according to content type
|
* Handles response according to content type
|
||||||
* @param {object} response
|
* @param {object} response
|
||||||
* @returns {promise}
|
* @returns {promise}
|
||||||
*/
|
*/
|
||||||
function handleResponseType(response) {
|
function handleResponseType(response) {
|
||||||
if (response.headers) {
|
if (response.headers) {
|
||||||
const contentType = response.headers.get('Content-Type');
|
const contentType = response.headers.get('Content-Type');
|
||||||
if (contentType.includes('application/pdf')) {
|
if (contentType.includes('application/pdf')) {
|
||||||
return Promise.all([response.ok, response.blob()]);
|
return Promise.all([response.ok, response.blob()]);
|
||||||
}
|
}
|
||||||
if (contentType.includes('application/json')) {
|
if (contentType.includes('application/json')) {
|
||||||
return Promise.all([response.ok, response.json()]);
|
return Promise.all([response.ok, response.json()]);
|
||||||
}
|
}
|
||||||
// it includes all text types
|
// it includes all text types
|
||||||
if (contentType.includes('text/')) {
|
if (contentType.includes('text/')) {
|
||||||
return Promise.all([response.ok, response.text()]);
|
return Promise.all([response.ok, response.text()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method,
|
method,
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
.then(handleResponseType)
|
.then(handleResponseType)
|
||||||
.then(([responseOk, body]) => {
|
.then(([responseOk, body]) => {
|
||||||
if (responseOk) {
|
if (responseOk) {
|
||||||
@ -52,11 +52,11 @@ class API {
|
|||||||
reject(body);
|
reject(body);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new API();
|
export default new API();
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* @prettier
|
||||||
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export function asyncComponent(getComponent) {
|
export function asyncComponent(getComponent) {
|
||||||
return class AsyncComponent extends React.Component {
|
return class AsyncComponent extends React.Component {
|
||||||
static Component = null;
|
static Component = null;
|
||||||
state = {Component: AsyncComponent.Component};
|
state = { Component: AsyncComponent.Component };
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {Component} = this.state;
|
const { Component } = this.state;
|
||||||
|
|
||||||
if (!Component) {
|
if (!Component) {
|
||||||
getComponent().then(({default: Component}) => {
|
getComponent()
|
||||||
AsyncComponent.Component = Component;
|
.then(({ default: Component }) => {
|
||||||
/* eslint react/no-did-mount-set-state:0 */
|
AsyncComponent.Component = Component;
|
||||||
this.setState({Component});
|
/* eslint react/no-did-mount-set-state:0 */
|
||||||
});
|
this.setState({ Component });
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const {Component} = this.state;
|
const { Component } = this.state;
|
||||||
if (Component) {
|
if (Component) {
|
||||||
|
// eslint-disable-next-line verdaccio/jsx-spread
|
||||||
return <Component {...this.props} />;
|
return <Component {...this.props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
src/utils/file-size.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default function fileSizeSI(a, b, c, d, e) {
|
||||||
|
return (b = Math, c = b.log, d = 1e3, e = c(a) / c(d) | 0, a / b.pow(d, e)).toFixed(2)
|
||||||
|
+ ' ' + (e ? 'kMGTPEZY'[--e] + 'B' : 'Bytes');
|
||||||
|
};
|
||||||
|
|
@ -1,76 +1,70 @@
|
|||||||
import isString from "lodash/isString";
|
import isString from 'lodash/isString';
|
||||||
import isNumber from "lodash/isNumber";
|
import isNumber from 'lodash/isNumber';
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { Base64 } from "js-base64";
|
import {Base64} from 'js-base64';
|
||||||
import API from "./api";
|
import API from './api';
|
||||||
|
import {HEADERS} from '../../lib/constants';
|
||||||
const HEADERS = {
|
|
||||||
JSON: "application/json",
|
|
||||||
JSON_CHARSET: "application/json; charset=utf-8",
|
|
||||||
OCTET_STREAM: "application/octet-stream; charset=utf-8",
|
|
||||||
TEXT_CHARSET: "text/plain; charset=utf-8",
|
|
||||||
GZIP: "gzip"
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isTokenExpire(token) {
|
export function isTokenExpire(token) {
|
||||||
if (!isString(token)) {
|
if (!isString(token)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let [, payload] = token.split(".");
|
let [,payload] = token.split('.');
|
||||||
|
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(Base64.decode(payload));
|
payload = JSON.parse(Base64.decode(payload));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error("Invalid token:", error, token);
|
console.error('Invalid token:', error, token);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!payload.exp || !isNumber(payload.exp)) {
|
if (!payload.exp || !isNumber(payload.exp)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Report as expire before (real expire time - 30s)
|
// Report as expire before (real expire time - 30s)
|
||||||
const jsTimestamp = payload.exp * 1000 - 30000;
|
const jsTimestamp = (payload.exp * 1000) - 30000;
|
||||||
const expired = Date.now() >= jsTimestamp;
|
const expired = Date.now() >= jsTimestamp;
|
||||||
|
|
||||||
return expired;
|
return expired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function makeLogin(username, password) {
|
export async function makeLogin(username, password) {
|
||||||
// checks isEmpty
|
// checks isEmpty
|
||||||
if (isEmpty(username) || isEmpty(password)) {
|
if (isEmpty(username) || isEmpty(password)) {
|
||||||
const error = {
|
const error = {
|
||||||
title: "Unable to login",
|
title: 'Unable to login',
|
||||||
type: "error",
|
type: 'error',
|
||||||
description: "Username or password can't be empty!"
|
description: 'Username or password can\'t be empty!',
|
||||||
};
|
};
|
||||||
return { error };
|
return {error};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await API.request("login", "POST", {
|
const response = await API.request('login', 'POST', {
|
||||||
body: JSON.stringify({ username, password }),
|
body: JSON.stringify({username, password}),
|
||||||
headers: {
|
headers: {
|
||||||
Accept: HEADERS.JSON,
|
Accept: HEADERS.JSON,
|
||||||
"Content-Type": HEADERS.JSON
|
'Content-Type': HEADERS.JSON,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const result = {
|
const result = {
|
||||||
username: response.username,
|
username: response.username,
|
||||||
token: response.token
|
token: response.token,
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = {
|
const error = {
|
||||||
title: "Unable to login",
|
title: 'Unable to login',
|
||||||
type: "error",
|
type: 'error',
|
||||||
description: e.error
|
description: e.error,
|
||||||
};
|
};
|
||||||
return { error };
|
return {error};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import format from 'date-fns/format';
|
|||||||
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
||||||
|
|
||||||
export const TIMEFORMAT = 'DD.MM.YYYY, HH:mm:ss';
|
export const TIMEFORMAT = 'DD.MM.YYYY, HH:mm:ss';
|
||||||
export const DEFAULT_USER = 'Anonymous';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats license field for webui.
|
* Formats license field for webui.
|
||||||
@ -39,36 +38,6 @@ export function formatRepository(repository) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats author field for webui.
|
|
||||||
* @see https://docs.npmjs.com/files/package.json#author
|
|
||||||
*/
|
|
||||||
export function formatAuthor(author) {
|
|
||||||
let authorDetails = {
|
|
||||||
name: DEFAULT_USER,
|
|
||||||
email: '',
|
|
||||||
avatar: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isString(author)) {
|
|
||||||
authorDetails = {
|
|
||||||
...authorDetails,
|
|
||||||
name: author ? author : authorDetails.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isObject(author)) {
|
|
||||||
authorDetails = {
|
|
||||||
...authorDetails,
|
|
||||||
name: author.name ? author.name : authorDetails.name,
|
|
||||||
email: author.email ? author.email : authorDetails.email,
|
|
||||||
avatar: author.avatar ? author.avatar : authorDetails.avatar,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For <LastSync /> component
|
* For <LastSync /> component
|
||||||
* @param {array} uplinks
|
* @param {array} uplinks
|
||||||
|
@ -10,7 +10,10 @@ const colors = {
|
|||||||
grey: '#808080',
|
grey: '#808080',
|
||||||
greySuperLight: '#f5f5f5',
|
greySuperLight: '#f5f5f5',
|
||||||
greyLight: '#d3d3d3',
|
greyLight: '#d3d3d3',
|
||||||
|
greyLight2: '#908ba1',
|
||||||
|
greyLight3: '#f3f4f240',
|
||||||
greyDark: '#a9a9a9',
|
greyDark: '#a9a9a9',
|
||||||
|
greyDark2: '#586069',
|
||||||
greyChateau: '#95989a',
|
greyChateau: '#95989a',
|
||||||
greyGainsboro: '#e3e3e3',
|
greyGainsboro: '#e3e3e3',
|
||||||
greyAthens: '#d3dddd',
|
greyAthens: '#d3dddd',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
|
|
||||||
const breakpoints = {
|
export const breakpoints = {
|
||||||
small: 576,
|
small: 576,
|
||||||
medium: 768,
|
medium: 768,
|
||||||
large: 1024,
|
large: 1024,
|
||||||
|
@ -2,11 +2,3 @@ export function getRegistryURL() {
|
|||||||
// Don't add slash if it's not a sub directory
|
// Don't add slash if it's not a sub directory
|
||||||
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
|
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get specified package detail page url
|
|
||||||
* @param {string} packageName
|
|
||||||
*/
|
|
||||||
export function getDetailPageURL(packageName) {
|
|
||||||
return `${getRegistryURL()}/-/web/version/${packageName}`;
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export function goToVerdaccioWebsite() {
|
export function goToVerdaccioWebsite() {
|
||||||
window.open('https://www.verdaccio.org/', '_blank');
|
window.open('https://www.verdaccio.org/', '_blank');
|
||||||
}
|
}
|
||||||
|