Skip to content

Commit

Permalink
Merge pull request #821 from nextstrain/display-repo
Browse files Browse the repository at this point in the history
Display repo and maintainers as byline
  • Loading branch information
trvrb committed Nov 26, 2019
2 parents cc8820b + d4ac068 commit 32b2688
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 51 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Expand Up @@ -56,6 +56,7 @@ rules:
prefer-destructuring: off
implicit-arrow-linebreak: off
jsx-quotes: off
no-use-before-define: ["error", { "functions": false }]
parserOptions:
ecmaVersion: 6
sourceType: module
Expand Down
3 changes: 3 additions & 0 deletions src/actions/recomputeReduxState.js
Expand Up @@ -524,6 +524,9 @@ const createMetadataStateFromJSON = (json) => {
if (json.meta.maintainers) {
metadata.maintainers = json.meta.maintainers;
}
if (json.meta.build_url) {
metadata.buildUrl = json.meta.build_url;
}
if (json.meta.genome_annotations) {
metadata.genomeAnnotations = json.meta.genome_annotations;
}
Expand Down
19 changes: 0 additions & 19 deletions src/components/controls/choose-dataset.js
@@ -1,20 +1,8 @@
import React from "react";
import { connect } from "react-redux";
import { withTheme } from 'styled-components';
import ChooseDatasetSelect from "./choose-dataset-select";
import { SidebarHeader } from "./styles";

const BuildLink = withTheme((props) => {
const niceUrl = props.url.replace(/^(http[s]?:\/\/)/, "");
return (
<div style={{ fontSize: 14, marginTop: 5, marginBottom: 10, color: props.theme.color}}>
<i className="fa fa-clone fa-lg" aria-hidden="true"/>
<span style={{position: "relative", paddingLeft: 10}}/>
<a href={props.url} target="_blank">{niceUrl}</a>
</div>
);
});

// const DroppedFiles = withTheme((props) => {
// /* TODO: this shouldn't be in the auspice src, rather injected as an extension when needed */
// return (
Expand Down Expand Up @@ -47,17 +35,13 @@ class ChooseDataset extends React.Component {
.replace(/\/$/, '')
.split(":")[0];
const displayedDataset = displayedDatasetString.split("/");
let optionForCurrentDataset = {};
const options = [[]];

this.props.available.datasets.forEach((d) => {
const firstField = d.request.split("/")[0];
if (!options[0].includes(firstField)) {
options[0].push(firstField);
}
if (displayedDatasetString === d.request) {
optionForCurrentDataset = d;
}
});


Expand All @@ -78,9 +62,6 @@ class ChooseDataset extends React.Component {
return (
<>
<SidebarHeader>Dataset</SidebarHeader>
{optionForCurrentDataset.buildUrl ? (
<BuildLink url={optionForCurrentDataset.buildUrl}/>
) : null}
{options.map((option, optionIdx) => (
<ChooseDatasetSelect
key={option}
Expand Down
27 changes: 2 additions & 25 deletions src/components/framework/footer.js
Expand Up @@ -383,23 +383,6 @@ class Footer extends React.Component {
</button>
);
}
hasMaintainers() {
return Object.prototype.hasOwnProperty.call(this.props.metadata, "maintainers")
}
renderMaintainers() {
const renderLink = (m) => (<a href={m.url} target="_blank">{m.name}</a>);
return (
<span style={{textAlign: "center"}}>
{"Build maintained by "}
{this.props.metadata.maintainers.map((m, i) => (
<React.Fragment key={m.name}>
{m.url ? renderLink(m) : m.name}
{i === this.props.metadata.maintainers.length-1 ? "" : i === this.props.metadata.maintainers.length-2 ? " and " : ", "}
</React.Fragment>
))}
</span>
);
}
getCitation() {
return (
<span>
Expand Down Expand Up @@ -430,21 +413,15 @@ class Footer extends React.Component {
);
})}
<Flex style={styles.fineprint}>
{this.hasMaintainers() ? (
<>
{this.renderMaintainers()}
{dot}
</>
) : null}
{this.getUpdated()}
{dot}
{this.downloadDataButton()}
{dot}
{"Auspice v" + version}
</Flex>
<div style={{height: "3px"}}/>
<Flex style={styles.fineprint}>
{this.getCitation()}
{dot}
{"Auspice v" + version}
</Flex>
</div>
</div>
Expand Down
101 changes: 101 additions & 0 deletions src/components/info/byline.js
@@ -0,0 +1,101 @@
import React from "react";
import { headerFont } from "../../globalStyles";

const styles = {
avatar: {
marginRight: 5,
marginBottom: 2
},
byline: {
fontFamily: headerFont,
fontSize: 15,
marginLeft: 2,
marginTop: 5,
marginBottom: 5,
fontWeight: 500,
color: "#555",
lineHeight: 1.4,
verticalAlign: "middle"
},
bylineWeight: {
fontFamily: headerFont,
fontSize: 15,
fontWeight: 500
}
};

const Byline = ({width, metadata}) => {
return (
<div width={width} style={styles.byline}>
{renderAvatar(metadata)}
{renderBuildInfo(metadata)}
{renderMaintainers(metadata)}
</div>
);
};

function renderAvatar(metadata) {
const repo = metadata.buildUrl;
if (typeof repo === 'string') {
const match = repo.match(/(https?:\/\/)?(www\.)?github.com\/([^/]+)/);
if (match) {
return (
<img style={styles.avatar} alt="avatar" width="28" src={`https://github.com/${match[3]}.png?size=200`}/>
);
}
}
return null;
}

function renderBuildInfo(metadata) {
if (Object.prototype.hasOwnProperty.call(metadata, "buildUrl")) {
const repo = metadata.buildUrl;
if (typeof repo === 'string') {
if (repo.startsWith("https://") || repo.startsWith("http://") || repo.startsWith("www.")) {
return (
<span>
{"Built using "}
<Link url={repo}>
{repo.replace(/^(http[s]?:\/\/)/, "").replace(/^www\./, "")}
</Link>
{". "}
</span>
);
}
}
}
return null;
}

function renderMaintainers(metadata) {
let maintainersArray;
if (Object.prototype.hasOwnProperty.call(metadata, "maintainers")) {
maintainersArray = metadata.maintainers;
if (Array.isArray(maintainersArray) && maintainersArray.length) {
return (
<span>
{"Maintained by "}
{maintainersArray.map((m, i) => (
<React.Fragment key={m.name}>
{m.url ? <Link url={m.url}>{m.name}</Link> : m.name}
{i === maintainersArray.length-1 ? "" : i === maintainersArray.length-2 ? " and " : ", "}
</React.Fragment>
))}
{"."}
</span>
);
}
}
return null;
}

function Link({url, children}) {
return (
<a style={styles.bylineWeight} rel="noopener noreferrer" href={url} target="_blank">
{children}
</a>
);
}


export default Byline;
22 changes: 15 additions & 7 deletions src/components/info/info.js
Expand Up @@ -7,6 +7,7 @@ import { getVisibleDateRange } from "../../util/treeVisibilityHelpers";
import { numericToCalendar } from "../../util/dateHelpers";
import { months, NODE_VISIBLE } from "../../util/globals";
import { displayFilterValueAsButton } from "../framework/footer";
import Byline from "./byline";

const plurals = {
country: "countries",
Expand Down Expand Up @@ -221,17 +222,25 @@ class Info extends React.Component {
);
}

renderTitle(styles) {
let title = "";
if (this.props.metadata.title) {
title = this.props.metadata.title;
}
return (
<div width={this.props.width} style={styles.title}>
{title}
</div>
);
}

render() {
if (!this.props.metadata || !this.props.nodes || !this.props.visibility) return null;
const styles = this.getStyles(this.props.width);
// const filtersWithValues = Object.keys(this.props.filters).filter((n) => this.props.filters[n].length > 0);
const animating = this.props.animationPlayPauseButton === "Pause";
const showExtended = !animating && !this.props.selectedStrain;
const datesMaxed = this.props.dateMin === this.props.absoluteDateMin && this.props.dateMax === this.props.absoluteDateMax;
let title = "";
if (this.props.metadata.title) {
title = this.props.metadata.title;
}

/* the content is made up of two parts:
(1) the summary - e.g. Showing 4 of 379 sequences, from 1 author, 1 country and 1 region, dated Apr 2016 to Jun 2016.
Expand All @@ -250,9 +259,8 @@ class Info extends React.Component {
return (
<Card center infocard>
<div style={styles.base}>
<div width={this.props.width} style={styles.title}>
{title}
</div>
{this.renderTitle(styles)}
<Byline styles={styles} width={this.props.width} metadata={this.props.metadata}/>
<div width={this.props.width} style={styles.n}>
{animating ? `Animation in progress. ` : null}
{this.props.selectedStrain ? this.selectedStrainButton(this.props.selectedStrain) : null}
Expand Down
27 changes: 27 additions & 0 deletions src/reducers/metadata.js
Expand Up @@ -21,9 +21,36 @@ const Metadata = (state = {
case types.ADD_COLOR_BYS:
const colorings = Object.assign({}, state.colorings, action.newColorings);
return Object.assign({}, state, {colorings});
case types.SET_AVAILABLE:
if (state.buildUrl) {
return state; // do not use data from getAvailable to overwrite a buildUrl set from a dataset JSON
}
const buildUrl = getBuildUrlFromGetAvailableJson(action.data.datasets);
if (buildUrl) {
return Object.assign({}, state, {buildUrl});
}
return state;
default:
return state;
}
};

function getBuildUrlFromGetAvailableJson(availableData) {
if (!availableData) return undefined;
/* check if the current dataset is present in the getAvailable data
We currently parse the URL (pathname) for the current dataset but this
really should be stored somewhere in redux */
const displayedDatasetString = window.location.pathname
.replace(/^\//, '')
.replace(/\/$/, '')
.split(":")[0];
for (let i=0; i<availableData.length; i++) {
if (availableData[i].request === displayedDatasetString) {
return availableData[i].buildUrl; // may be `undefined`
}
}
return false;
}


export default Metadata;

0 comments on commit 32b2688

Please sign in to comment.