Skip to content

Commit

Permalink
wip: consume narrowed MetadataState
Browse files Browse the repository at this point in the history
  • Loading branch information
jameshadfield committed Nov 8, 2024
1 parent 802b645 commit 8db1b14
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 40 deletions.
36 changes: 18 additions & 18 deletions src/components/info/byline.js → src/components/info/byline.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import React from "react";
import { connect } from "react-redux";
import { withTranslation } from 'react-i18next';
import styled from 'styled-components';
import { headerFont } from "../../globalStyles";
import { MetadataState} from "../../reducers/metadata";

interface Props {
t: any; // TODO XXX - look up how to type WithTranslation
metadata: MetadataState
}

/**
* React component for the byline of the current dataset.
* This details (non-dynamic) information about the dataset, such as the
* maintainers, source, data provenance etc.
*/
@connect((state) => {
return {
metadata: state.metadata
};
})
class Byline extends React.Component {
render() {
class Byline extends React.Component<Props> {
override render() {
const { t } = this.props;
return (
<>
{renderAvatar(t, this.props.metadata)}
{renderAvatar(this.props.metadata)}
{renderBuildInfo(t, this.props.metadata)}
{renderMaintainers(t, this.props.metadata)}
{renderDataUpdated(t, this.props.metadata)}
Expand All @@ -38,9 +38,9 @@ const AvatarImg = styled.img`
* Renders the GitHub avatar of the current dataset for datasets with a `buildUrl`
* which is a GitHub repo. The avatar image is fetched from GitHub (by the client).
*/
function renderAvatar(t, metadata) {
function renderAvatar(metadata: MetadataState) {
const repo = metadata.buildUrl;
if (typeof repo === 'string') {
if (repo) {
const match = repo.match(/(https?:\/\/)?(www\.)?github.com\/([^/]+)/);
if (match) {
return (
Expand All @@ -55,10 +55,10 @@ function renderAvatar(t, metadata) {
* Returns a React component detailing the source of the build (pipeline).
* Renders a <span> containing "Built with X", where X derives from `metadata.buildUrl`
*/
function renderBuildInfo(t, metadata) {
function renderBuildInfo(t, metadata: MetadataState) {
if (Object.prototype.hasOwnProperty.call(metadata, "buildUrl")) {
const repo = metadata.buildUrl;
if (typeof repo === 'string') {
if (typeof repo === 'string') { // TODO - we can relax this now that we have proper types
if (repo.startsWith("https://") || repo.startsWith("http://") || repo.startsWith("www.")) {
return (
<span>
Expand All @@ -80,11 +80,11 @@ function renderBuildInfo(t, metadata) {
* Returns a React component detailing the maintainers of the build (pipeline).
* Renders a <span> containing "Maintained by X", where X derives from `metadata.maintainers`
*/
function renderMaintainers(t, metadata) {
function renderMaintainers(t, metadata: MetadataState): JSX.Element | null {
let maintainersArray;
if (Object.prototype.hasOwnProperty.call(metadata, "maintainers")) {
maintainersArray = metadata.maintainers;
if (Array.isArray(maintainersArray) && maintainersArray.length) {
if (Array.isArray(maintainersArray) && maintainersArray.length) { // TODO - we can relax this now that we have proper types
return (
<span>
{t("Maintained by") + " "}
Expand All @@ -106,7 +106,7 @@ function renderMaintainers(t, metadata) {
* Returns a React component detailing the date the data was last updated.
* Renders a <span> containing "Data updated X", where X derives from `metadata.updated`
*/
function renderDataUpdated(t, metadata) {
function renderDataUpdated(t, metadata: MetadataState): JSX.Element | null {
if (metadata.updated) {
return (
<span>
Expand All @@ -122,7 +122,7 @@ function renderDataUpdated(t, metadata) {
* Renders a <span> containing "Enabled by data from X", where X derives from `metadata.dataProvenance`
* Note that this function includes logic to special-case certain values which may appear there.
*/
function renderDataProvenance(t, metadata) {
function renderDataProvenance(t, metadata: MetadataState): JSX.Element | null {
if (!Array.isArray(metadata.dataProvenance)) return null;
const sources = metadata.dataProvenance
.filter((source) => typeof source === "object")
Expand Down Expand Up @@ -174,7 +174,7 @@ const BylineLink = styled.a`
font-weight: 500;
`;

function Link({url, children}) {
function Link({url, children}): JSX.Element {
return (
<BylineLink rel="noopener noreferrer" href={url} target="_blank">
{children}
Expand Down
51 changes: 32 additions & 19 deletions src/components/info/info.js → src/components/info/info.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
import React from "react";
import { connect } from "react-redux";
import { connect, ConnectedProps } from "react-redux";
import { withTranslation } from 'react-i18next';
import Card from "../framework/card";
import { titleFont, headerFont, medGrey, darkGrey } from "../../globalStyles";
import Byline from "./byline";
import {datasetSummary} from "./datasetSummary";
import FiltersSummary from "./filtersSummary";
import { RootState } from "../../store";

const mapState = (state: RootState) => {
// can we generalise the mapState function so the following is for free?
if (!state.metadata.loaded) { // loaded is the discriminant property to narrow types
throw new Error("Something's gone seriously wrong")
}
return {
browserWidth: state.browserDimensions.browserDimensions.width,
animationPlayPauseButton: state.controls.animationPlayPauseButton,
metadata: state.metadata,
nodes: state.tree.nodes,
branchLengthsToDisplay: state.controls.branchLengthsToDisplay,
visibility: state.tree.visibility
}
}
const connector = connect(mapState)
type PropsFromRedux = ConnectedProps<typeof connector>

interface Props extends PropsFromRedux {
t: any; // TODO XXX - look up how to type WithTranslation
width: number;
}

/**
* The <Info> panel is shown above data viz panels and conveys static and dynamic
Expand All @@ -15,22 +38,12 @@ import FiltersSummary from "./filtersSummary";
* Dataset summary (dynamic)
* Current Filters (dynamic)
*/
@connect((state) => {
return {
browserWidth: state.browserDimensions.browserDimensions.width,
animationPlayPauseButton: state.controls.animationPlayPauseButton,
metadata: state.metadata,
nodes: state.tree.nodes,
branchLengthsToDisplay: state.controls.branchLengthsToDisplay,
visibility: state.tree.visibility
};
})
class Info extends React.Component {
class Info extends React.Component<Props> {
constructor(props) {
super(props);
}

render() {
override render() {
const { t } = this.props;
if (!this.props.metadata || !this.props.nodes || !this.props.visibility) return null;
const styles = computeStyles(this.props.width, this.props.browserWidth);
Expand All @@ -40,15 +53,15 @@ class Info extends React.Component {
<Card center infocard>
<div style={styles.base}>

<div width={this.props.width} style={styles.title}>
{this.props.metadata.title || ""}
<div style={styles.title}>
{this.props.metadata.title}
</div>

<div width={this.props.width} style={styles.byline}>
<Byline/>
<div style={styles.byline}>
<Byline metadata={this.props.metadata}/>
</div>

<div width={this.props.width} style={styles.n}>
<div style={styles.n}>
{animating ? t("Animation in progress") + ". " : null}
{showExtended &&
<>
Expand Down Expand Up @@ -111,5 +124,5 @@ function computeStyles(width, browserWidth) {
};
}

const WithTranslation = withTranslation()(Info);
const WithTranslation = withTranslation()(connector(Info));
export default WithTranslation;
6 changes: 3 additions & 3 deletions src/reducers/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as types from "../actions/types";
import { DatasetJsonRootSequence, DatasetJson, DatasetJsonMeta } from "../types/datasetJson";

export interface MetadataState {
export interface MetadataState { // TODO -- work out exactly what properties you want to have as required here
loaded: true;
rootSequence: DatasetJsonRootSequence;
identicalGenomeMapAcrossBothTrees: boolean;
Expand All @@ -13,7 +13,7 @@ export interface MetadataState {
displayDefaults: Record<string,any>; // TODO XXX
panels: Required<DatasetJsonMeta>['panels'];
mainTreeNumTips: number;
title: Required<DatasetJsonMeta>['title'];
title: DatasetJsonMeta['title'];
version: DatasetJson['version'];
filters: Required<DatasetJsonMeta>['filters'];
dataProvenance: Required<DatasetJsonMeta>['data_provenance'];
Expand All @@ -30,7 +30,7 @@ export function convertIncompleteMetadataStateToMetadataState(meta: IncompleteMe
// and we don't want to be doing that. What's the best path here?
const expectedProperties: [string, string, any][] = [
// THIS IS INCOMPLETE - TODO XXX
["title", "string", null],
// ["title", "string", null], // title is optional!
["version", "string", null],
["filters", "string", []],
["updated", "string", new Error("JSON.meta missing property 'updated' which is essential")], // TKTK - it's not essential, just for testing
Expand Down

0 comments on commit 8db1b14

Please sign in to comment.