0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-04-01 02:42:23 -05:00

refactor: 🔨 follow review, split modules to multiple components

This commit is contained in:
Meeeeow 2017-12-04 00:34:34 +08:00 committed by juanpicado
parent ba6811bd91
commit b2d54e192b
18 changed files with 365 additions and 240 deletions

View file

@ -11,7 +11,7 @@ import storage from '../../../utils/storage';
import classes from './header.scss';
import './logo.png';
import getRegistryURL from '../../../utils/getRegistryURL';
import {getRegistryURL} from '../../../utils/url';
export default class Header extends React.Component {
state = {

View file

@ -4,7 +4,7 @@ import sunburst from 'react-syntax-highlighter/src/styles/sunburst';
import js from 'react-syntax-highlighter/dist/languages/javascript';
import classes from './help.scss';
import getRegistryURL from '../../../utils/getRegistryURL';
import {getRegistryURL} from '../../../utils/url';
registerLanguage('javascript', js);

View file

@ -0,0 +1,25 @@
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
};

View file

@ -0,0 +1,21 @@
@import '../../../styles/variable';
.module {
.moduleTitle {
display: flex;
align-items: flex-end;
font-size: 24px;
color: $primary-color;
margin: 0 0 10px;
padding: 5px 0;
font-weight: 600;
border-bottom: 1px solid $border-color;
span { // description
font-size: 14px;
color: $text-grey;
margin-left: auto;
font-weight: lighter;
}
}
}

View file

@ -0,0 +1,11 @@
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
};

View file

@ -0,0 +1,8 @@
@import '../../../styles/variable';
.emptyPlaceholder {
text-align: center;
margin: 20px 0;
font-size: 16px;
color: $text-grey;
}

View file

@ -1,25 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import LastSync from './modules/LastSync';
import Maintainers from './modules/Maintainers';
import Dependencies from './modules/Dependencies';
import API from '../../../utils/api';
import classes from './style.scss';
import getRegistryURL from '../../../utils/getRegistryURL';
export default class PackageSidebar extends React.Component {
state = {
lastUpdate: 'Loading',
recentReleases: [],
author: null,
contributors: null,
showAllContributors: false,
dependencies: {}
state = {};
static propTypes = {
packageName: PropTypes.string.isRequired
};
constructor(props) {
super(props);
this.showAllContributors = this.showAllContributors.bind(this);
}
async componentDidMount() {
@ -44,149 +39,21 @@ export default class PackageSidebar extends React.Component {
return;
}
let lastUpdate = 0;
Object.keys(packageMeta._uplinks).forEach((upLinkName) => {
const status = packageMeta._uplinks[upLinkName];
if (status.fetched > lastUpdate) {
lastUpdate = status.fetched;
}
});
let recentReleases = Object.keys(packageMeta.time).map((version) => {
return {
version,
time: packageMeta.time[version]
};
});
recentReleases = recentReleases.slice(recentReleases.length - 3, recentReleases.length).reverse();
this.setState({
lastUpdate: lastUpdate ? (new Date(lastUpdate)).toLocaleString() : '',
recentReleases,
author: packageMeta.latest.author,
contributors: packageMeta.latest.contributors,
dependencies: packageMeta.latest.dependencies,
showAllContributors: _.size(packageMeta.latest.contributors) <= 5
});
}
showAllContributors() {
this.setState({
showAllContributors: true
packageMeta
});
}
render() {
let {author, contributors, recentReleases, lastUpdate, showAllContributors, dependencies} = this.state;
let uniqueContributors = [];
if (contributors) {
uniqueContributors = _.filter(_.uniqBy(contributors, (contributor) => contributor.name), (contributor) => {
return contributor.name !== _.get(author, 'name');
})
.slice(0, 5);
}
let {packageMeta} = this.state;
return (
<aside>
<Module
title="Last Sync"
description={lastUpdate}
className={classes.releasesModule}
>
<ul>
{recentReleases.length > 0 && recentReleases.map((versionInfo) => {
return (
<li key={versionInfo.version}>
<span>{versionInfo.version}</span>
<span>{(new Date(versionInfo.time)).toLocaleString()}</span>
</li>
);
})}
</ul>
</Module>
<Module
title="Maintainers"
className={classes.authorsModule}
>
<ul>
{author && <MaintainerInfo title="Author" name={author.name} avatar={author.avatar}/>}
{contributors && (showAllContributors ? contributors : uniqueContributors).map((contributor, index) => {
return <MaintainerInfo key={index} title="Contributors" name={contributor.name} avatar={contributor.avatar}/>;
})}
</ul>
{!this.state.showAllContributors && (
<button
onClick={this.showAllContributors}
className={classes.showAllContributors}
title="Current list only show the author and first 5 contributors unique by name"
>
Show all contributor
</button>
)}
</Module>
<LastSync packageMeta={packageMeta} />
<Maintainers packageMeta={packageMeta} />
<Dependencies packageMeta={packageMeta} />
{/* Package management module? Help us implement it! */}
<Module
title="Dependencies"
className={classes.dependenciesModule}
>
<ul>
{_.size(dependencies) ? (
Object.keys(dependencies).map((dependenceName, index) => {
return (
<li key={index} title={`Depend on version: ${dependencies[dependenceName]}`}>
<a href={`${getRegistryURL()}/#/detail/${dependenceName}`} target="_blank">{dependenceName}</a>
{index + 1 < _.size(dependencies) && <span>,&nbsp;</span>}
</li>
);
})
): <p className={classes.emptyPlaceholder}>Zero dependencies</p>}
</ul>
</Module>
</aside>
);
}
}
PackageSidebar.propTypes = {
packageName: PropTypes.string.isRequired
};
function Module(props) {
return (
<div className={`${classes.module} ${props.className}`}>
<h2 className={classes.moduleTitle}>
{props.title}
{props.description && <span>{props.description}</span>}
</h2>
<div>
{props.children}
</div>
</div>
);
}
Module.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
children: PropTypes.any.isRequired,
className: PropTypes.string
};
function MaintainerInfo(props) {
let avatarDescription = `${props.title} ${props.name}'s avatar`;
return (
<div className={classes.maintainer} title={props.name}>
<img src={props.avatar} alt={avatarDescription} title={avatarDescription}/>
<span>{props.name}</span>
</div>
);
}
MaintainerInfo.propTypes = {
title: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
avatar: PropTypes.string.isRequired
};

View file

@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Module from '../../Module';
import classes from './style.scss';
import {getDetailPageURL} from '../../../../../utils/url';
import ModuleContentPlaceholder from '../../ModuleContentPlaceholder';
export default class Dependencies extends React.Component {
static propTypes = {
packageMeta: PropTypes.object
};
get dependencies() {
if (!this.props.packageMeta) return {};
return _.get(this, 'props.packageMeta.latest.dependencies', {});
}
render() {
let dependencies = this.dependencies;
let dependenciesList = Object.keys(dependencies);
if (!dependenciesList.length) {
return <ModuleContentPlaceholder text="Zero Dependencies!"/>;
}
return (
<Module
title="Dependencies"
className={classes.dependenciesModule}
>
<ul>
{
dependenciesList.map((dependenceName, index) => {
return (
<li key={index} title={`Depend on version: ${dependencies[dependenceName]}`}>
<a href={getDetailPageURL(dependenceName)}>{dependenceName}</a>
{index < dependenciesList.length - 1 && <span>,&nbsp;</span>}
</li>
);
})
}
</ul>
</Module>
);
}
}

View file

@ -0,0 +1,13 @@
@import '../../../../styles/variable';
.dependenciesModule {
li {
display: inline-block;
font-size: 14px;
line-height: 1.5;
a {
color: $primary-color;
}
}
}

View file

@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import Module from '../../Module';
import classes from './style.scss';
export default class LastSync extends React.Component {
static propTypes = {
packageMeta: PropTypes.object
};
get lastUpdate() {
if (!this.props.packageMeta) return 'Loading...';
let lastUpdate = 0;
Object.keys(this.props.packageMeta._uplinks).forEach((upLinkName) => {
const status = this.props.packageMeta._uplinks[upLinkName];
if (status.fetched > lastUpdate) {
lastUpdate = status.fetched;
}
});
return lastUpdate ? (new Date(lastUpdate)).toLocaleString() : '';
}
get recentReleases() {
if (!this.props.packageMeta) return [];
let recentReleases = Object.keys(this.props.packageMeta.time).map((version) => {
return {
version,
time: new Date(this.props.packageMeta.time[version]).toLocaleString()
};
});
return recentReleases.slice(recentReleases.length - 3, recentReleases.length).reverse();
}
render() {
return (
<Module
title="Last Sync"
description={this.lastUpdate}
className={classes.releasesModule}
>
<ul>
{this.recentReleases.map((versionInfo) => {
return (
<li key={versionInfo.version}>
<span>{versionInfo.version}</span>
<span>{versionInfo.time}</span>
</li>
);
})}
</ul>
</Module>
);
}
}

View file

@ -0,0 +1,10 @@
.releasesModule {
li {
display: flex;
font-size: 14px;
line-height: 2;
span:last-child {
margin-left: auto;
}
}
}

View file

@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import classes from './style.scss';
export default function MaintainerInfo({title, name, avatar}) {
let avatarDescription = `${title} ${name}'s avatar`;
return (
<div className={classes.maintainer} title={name}>
<img src={avatar} alt={avatarDescription} title={avatarDescription}/>
<span>{name}</span>
</div>
);
}
MaintainerInfo.propTypes = {
title: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
avatar: PropTypes.string.isRequired
};

View file

@ -0,0 +1,25 @@
.maintainer {
$mine-height: 30px;
display: flex;
line-height: $mine-height;
cursor: default;
&:not(:last-child) {
margin-bottom: 10px;
}
img {
width: $mine-height;
height: $mine-height;
margin-right: 10px;
border-radius: 100%;
flex-shrink: 0;
}
span {
font-size: 14px;
flex-shrink: 1;
white-space: nowrap;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
}
}

View file

@ -0,0 +1,84 @@
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Module from '../../Module';
import classes from './style.scss';
import MaintainerInfo from './MaintainerInfo';
export default class Maintainers extends React.Component {
static propTypes = {
packageMeta: PropTypes.object
};
state = {};
constructor(props) {
super(props);
this.handleShowAllContributors = this.handleShowAllContributors.bind(this);
}
get author() {
return _.get(this, 'props.packageMeta.latest.author');
}
get contributors() {
let 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() {
return this.state.showAllContributors || _.size(this.contributors) <= 5;
}
get uniqueContributors() {
if (!this.contributors) return [];
return _.uniqBy(this.contributors, (contributor) => contributor.name).slice(0, 5);
}
handleShowAllContributors() {
this.setState({
showAllContributors: true
});
}
renderContributors() {
if (!this.contributors) return null;
return (this.showAllContributors ? this.contributors : this.uniqueContributors)
.map((contributor, index) => {
return <MaintainerInfo key={index} title="Contributors" name={contributor.name} avatar={contributor.avatar}/>;
});
}
render() {
let author = this.author;
return (
<Module
title="Maintainers"
className={classes.maintainersModule}
>
<ul>
{author && <MaintainerInfo title="Author" name={author.name} avatar={author.avatar}/>}
{this.renderContributors()}
</ul>
{!this.showAllContributors && (
<button
onClick={this.handleShowAllContributors}
className={classes.showAllContributors}
title="Current list only show the author and first 5 contributors unique by name"
>
Show all contributor
</button>
)}
</Module>
);
}
}

View file

@ -0,0 +1,14 @@
@import '../../../../styles/variable';
.maintainersModule {
.showAllContributors {
cursor: pointer;
width: 100%;
background: none;
border: none;
font-size: 14px;
color: $primary-color;
text-align: center;
padding: 10px 0;
}
}

View file

@ -1,90 +1 @@
@import '../../styles/variable';
.module {
.moduleTitle {
display: flex;
align-items: flex-end;
font-size: 24px;
color: $primary-color;
margin: 0 0 10px;
padding: 5px 0;
font-weight: 600;
border-bottom: 1px solid $border-color;
span { // description
font-size: 14px;
color: $text-grey;
margin-left: auto;
font-weight: lighter;
}
}
}
.releasesModule {
li {
display: flex;
font-size: 14px;
line-height: 2;
span:last-child {
margin-left: auto;
}
}
}
.authorsModule {
.maintainer {
$mine-height: 30px;
display: flex;
line-height: $mine-height;
cursor: default;
&:not(:last-child) {
margin-bottom: 10px;
}
img {
width: $mine-height;
height: $mine-height;
margin-right: 10px;
border-radius: 100%;
flex-shrink: 0;
}
span {
font-size: 14px;
flex-shrink: 1;
white-space: nowrap;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
}
}
.showAllContributors {
cursor: pointer;
width: 100%;
background: none;
border: none;
font-size: 14px;
color: $primary-color;
text-align: center;
padding: 10px 0;
}
}
.dependenciesModule {
li {
display: inline-block;
font-size: 14px;
line-height: 1.5;
a {
color: $primary-color;
}
}
}
.emptyPlaceholder {
text-align: center;
margin: 20px 0;
font-size: 16px;
color: $text-grey;
}

View file

@ -1,4 +0,0 @@
export default function getRegistryURL() {
// Don't add slash if it's not a sub directory
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
}

12
src/webui/utils/url.js Normal file
View file

@ -0,0 +1,12 @@
export function getRegistryURL() {
// Don't add slash if it's not a sub directory
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
}
/**
* Get specified package detail page url
* @param {string} packageName
*/
export function getDetailPageURL(packageName) {
return `${getRegistryURL()}/#/detail/${packageName}`;
}