Skip to content

Commit

Permalink
WIP link out modal + taxonium link
Browse files Browse the repository at this point in the history
  • Loading branch information
jameshadfield committed Apr 9, 2024
1 parent c2398a6 commit 45791aa
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 3 deletions.
28 changes: 25 additions & 3 deletions src/components/framework/fine-print.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import React, { Suspense, lazy } from "react";
import { connect } from "react-redux";
import styled from 'styled-components';
import { withTranslation } from "react-i18next";
import { FaDownload } from "react-icons/fa";
import { FaDownload, FaExternalLinkAlt } from "react-icons/fa";
import { dataFont, medGrey, materialButton } from "../../globalStyles";
import { SET_MODAL } from "../../actions/types";
import Flex from "./flex";
import { version } from "../../version";
import { publications } from "../download/downloadModal";
import { hasExtension, getExtension } from "../../util/extensions";
import { canShowLinkOuts } from "../modal/LinkOutModalContents.jsx";

const logoPNG = require("../../images/favicon.png");

Expand Down Expand Up @@ -85,7 +86,22 @@ class FinePrint extends React.Component {
onClick={() => { this.props.dispatch({ type: SET_MODAL, modal: "download" }); }}
>
<FaDownload />
<span style={{position: "relative"}}>{" "+t("Download data")}</span>
<span style={{position: "relative", paddingLeft: '3px'}}>{" "+t("Download data")}</span>
</button>
);
}
linkOutButton() {
// TODO XXX conditional on extensions? i.e. extensions should set which platforms they can link to...
// Ideally this entire stuff would in injected as it's nextstrain.org specific but Auspice is the nextstrain viewer
// and we have lots of nextstrain.org-specific code
const { t } = this.props;
return (
<button
style={Object.assign({}, materialButton, {backgroundColor: "rgba(0,0,0,0)", color: medGrey, margin: 0, padding: 0})}
onClick={() => { this.props.dispatch({ type: SET_MODAL, modal: "linkOut" }); }}
>
<FaExternalLinkAlt /> {/* FIXME XXX */}
<span style={{position: "relative", paddingLeft: '3px'}}>{" "+t("View in other platforms")}</span>
</button>
);
}
Expand All @@ -96,10 +112,16 @@ class FinePrint extends React.Component {
return (
<FinePrintStyles>
<div style={{width: width}}>
<Flex className='finePrint'>
<Flex className='finePrint' wrap='wrap'>
{this.getUpdated()}
{dot}
{this.downloadDataButton()}
{canShowLinkOuts() && (
<>
{dot}
{this.linkOutButton()}
</>
)}
{dot}
<a href="https://docs.nextstrain.org/projects/auspice/page/releases/changelog.html" target="_blank" rel="noreferrer noopener">
{"Auspice v" + version}
Expand Down
141 changes: 141 additions & 0 deletions src/components/modal/LinkOutModalContents.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React from "react";
import styled from 'styled-components';
import { useSelector } from "react-redux";
import { infoPanelStyles } from "../../globalStyles";
import { dataFont, lighterGrey} from "../../globalStyles";
import { isColorByGenotype, decodeColorByGenotype} from "../../util/getGenotype";

/**
* The following value is useful for development purposes as we'll not show any
* link-outs on localhost (because external sites can't access it!) so we can
* replace it with something else such as "https://nextstrain.org" for testing
*/
const forceNextstrainHost = false;

const ButtonText = styled.a`
border: 1px solid ${lighterGrey};
border-radius: 4px;
cursor: pointer;
padding: 4px 7px;
margin-right: 10px;
font-family: ${dataFont};
background-color: rgba(0,0,0,0);
color: white !important;
font-weight: 400;
text-decoration: none !important;
font-size: 16px;
& :hover {
background-color: ${(props) => props.theme.selectedColor};
}
`

const ButtonDescription = styled.div`
display: inline-block;
height: 30px;
font-style: italic;
font-size: 14px;
color: white;
`

const ButtonContainer = styled.div`
margin-top: 10px;
margin-bottom: 10px;
`

const data = ({distanceMeasure, colorBy}) => {
const pathname = window.location.pathname;
const origin = forceNextstrainHost ? 'https://nextstrain.org' : window.location.origin;
return ([
{
name: 'taxonium.org',
description() {
return <>{`Visualise this dataset in Taxonium. We'll try to preserve your current view settings where possible.`}</>
},
taxoniumColoring() {
if (isColorByGenotype(colorBy)) {
/* Taxonium syntax looks like 'color={"field":"genotype","pos":485,"gene":"M"}'
Note that taxonium (I think) does't backfill bases/residues which match the root like Auspice does
*/
const subfields = ['"genotype"']; // include quoting as taxonium uses
const colorInfo = decodeColorByGenotype(colorBy);
// Multiple mutations (positions) aren't allowed
if (!colorInfo || colorInfo.positions.length>1) return null;
// The (integer) position is not enclosed in double quotes
subfields.push(`"pos":${colorInfo.positions[0]}`);
// The gene value is optional, without it we use nucleotide ("nt" in taxonium syntax)
if (colorInfo.aa) subfields.push(`"gene":"${colorInfo.gene}"`);
// Note that this string will be encoded when converted to a URL
return `{"field":${subfields.join(',')}}`;
}
return `{"field":"meta_${colorBy}"}`;
},
url() {
/**
* Tested on genotype colors + normal colors
* TODO: tanglegrams (they'll be part of the pathname)
* TODO: /fetch URLs (won't work)
*/
const baseUrl = 'https://taxonium.org';
const queries = {
treeUrl: `${origin}${pathname}`, // no nextstrain queries
// treeUrl: `https://nextstrain.org${pathname}`, // uncomment for development on localhost
treeType: 'nextstrain',
ladderizeTree: 'false', // keep same orientation as Auspice
xType: distanceMeasure==='num_date' ? 'x_time' : 'x_dist',
}
const color = this.taxoniumColoring();
if (color) queries.color = color;

return `${baseUrl}?${Object.entries(queries).map(([k,v]) => `${k}=${encodeURIComponent(v)}`).join("&")}`;
}
}
])
}

export const LinkOutModalContents = () => {
const {distanceMeasure, colorBy} = useSelector((state) => state.controls)

return (
<>
<div style={infoPanelStyles.modalSubheading}>
View this dataset in other platforms:
</div>

<div style={infoPanelStyles.break}/>

<div>
Clicking on the following links will take you to an external site which will then make requests to nextstrain.org for the data you are currently viewing
</div>

{window.location.hostname==='localhost' && (
<div>
NOTE: The site is currently running on localhost and thus the following links will not work
</div>
)}

<div style={infoPanelStyles.break}/>

{data({distanceMeasure, colorBy}).map((d) => (
<ButtonContainer key={d.name}>
<ButtonText href={d.url()} target="_blank" rel="noreferrer noopener">{d.name}</ButtonText>
<ButtonDescription>{d.description()}</ButtonDescription>
</ButtonContainer>
))}

<div>
These are only gonna work with nextstrain.org (and dev, review apps etc) because it uses the RESTful API
</div>

</>
);
}


export const canShowLinkOuts = () => {
// TODO XXX - additionally query an extension flag
if (window.location.hostname==='localhost' && !forceNextstrainHost) {
console.log("Link-out modal disabled while running on localhost")
return false;
}
return true;
}
4 changes: 4 additions & 0 deletions src/components/modal/Modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SET_MODAL } from "../../actions/types";
import { infoPanelStyles } from "../../globalStyles";
import { stopProp } from "../tree/infoPanels/click";
import DownloadModalContents from "../download/downloadModal";
import { LinkOutModalContents } from "./LinkOutModalContents.jsx";

@connect((state) => ({
browserDimensions: state.browserDimensions.browserDimensions,
Expand Down Expand Up @@ -85,6 +86,9 @@ class Modal extends React.Component {
case 'download':
Contents = DownloadModalContents;
break;
case 'linkOut':
Contents = LinkOutModalContents;
break;
default:
return null;
}
Expand Down

0 comments on commit 45791aa

Please sign in to comment.