Skip to content

Commit

Permalink
Add types to ListResources
Browse files Browse the repository at this point in the history
Non-exhaustive attempt to add types. Note that with noImplicitAny
disabled, there is still a decent chunk of untyped variables.
  • Loading branch information
victorlin committed Jun 4, 2024
1 parent 2aeb155 commit 9aec897
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const CardInner = styled.div`
cursor: pointer;
`;

export const CardOuter = styled.div`
export const CardOuter = styled.div<{$squashed: boolean}>`
background-color: #FFFFFF;
padding: 0;
overflow: hidden;
Expand All @@ -38,7 +38,7 @@ export const CardOuter = styled.div`
}
`;

export const CardTitle = styled.div`
export const CardTitle = styled.div<{$squashed: boolean}>`
font-family: ${(props) => props.theme.generalFont};
font-weight: 500;
font-size: ${(props) => props.$squashed ? "22px" : "26px"};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React, {useState, useRef, useEffect, useContext} from 'react';
import styled from 'styled-components';
import { MdHistory } from "react-icons/md";
import { SetModalContext } from './Modal';
import { ResourceDisplayName, Resource } from './types';
import { IconType } from 'react-icons';

export const LINK_COLOR = '#5097BA'
export const LINK_HOVER_COLOR = '#31586c'
Expand All @@ -17,7 +19,7 @@ export const LINK_HOVER_COLOR = '#31586c'
const [resourceFontSize, namePxPerChar, summaryPxPerChar] = [16, 10, 9];
const iconWidth = 20; // not including text
const gapSize = 10;
export const getMaxResourceWidth = (displayResources) => {
export const getMaxResourceWidth = (displayResources: Resource[]) => {
return displayResources.reduce((w, r) => {
/* add the pixels for the display name */
let _w = r.displayName.default.length * namePxPerChar;
Expand All @@ -29,15 +31,22 @@ export const getMaxResourceWidth = (displayResources) => {
}, 200); // 200 (pixels) is the minimum
}

export const ResourceLink = styled.a`
export const ResourceLink = styled.a<{$hovered?: boolean}>`
font-size: ${resourceFontSize}px;
font-family: monospace;
white-space: pre; /* don't collapse back-to-back spaces */
color: ${(props) => props.$hovered ? LINK_HOVER_COLOR : LINK_COLOR} !important;
text-decoration: none !important;
`;

function Name({displayName, $hovered, href, topOfColumn}) {
interface NameProps {
displayName: ResourceDisplayName
$hovered?: boolean
href: string
topOfColumn: boolean
}

function Name({displayName, $hovered = false, href, topOfColumn}: NameProps) {
return (
<ResourceLink href={href} target="_blank" rel="noreferrer" $hovered={$hovered}>
{'• '}{($hovered||topOfColumn) ? displayName.hovered : displayName.default}
Expand Down Expand Up @@ -70,7 +79,15 @@ export function TooltipWrapper({description, children}) {
)
}

export function IconContainer({Icon, text, handleClick=undefined, color=undefined, hoverColor=undefined}) {
interface IconContainerProps {
Icon: IconType
text: string
handleClick?: () => void
color?: string
hoverColor?: string
}

export function IconContainer({Icon, text, handleClick, color, hoverColor}: IconContainerProps) {
const [hovered, setHovered] = useState(false);
const defaultColor = '#aaa';
const defaultHoverColor = "rgb(79, 75, 80)";
Expand All @@ -89,14 +106,14 @@ export function IconContainer({Icon, text, handleClick=undefined, color=undefine
}


/**
*
* @param {*} param0
* @returns
*/
export const IndividualResource = ({data, isMobile}) => {
interface IndividualResourceProps {
data: Resource
isMobile: boolean
}

export const IndividualResource = ({data, isMobile}: IndividualResourceProps) => {
const setModal = useContext(SetModalContext);
const ref = useRef(null);
const ref = useRef<HTMLDivElement>(null);
const [topOfColumn, setTopOfColumn] = useState(false);
useEffect(() => {
/* The column CSS is great but doesn't allow us to know if an element is at
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import styled from 'styled-components';
import * as d3 from "d3";
import { MdClose } from "react-icons/md";
import { dodge } from "./dodge";
import { Resource } from './types';

export const SetModalContext = createContext(null);
export const SetModalContext = createContext<React.Dispatch<React.SetStateAction<Resource | undefined>> | null>(null);

export const RAINBOW20 = ["#511EA8", "#4432BD", "#3F4BCA", "#4065CF", "#447ECC", "#4C91BF", "#56A0AE", "#63AC9A", "#71B486", "#81BA72", "#94BD62", "#A7BE54", "#BABC4A", "#CBB742", "#D9AE3E", "#E29E39", "#E68935", "#E56E30", "#E14F2A", "#DC2F24"];
const lightGrey = 'rgba(0,0,0,0.1)';

export const ResourceModal = ({data, dismissModal}) => {

interface ResourceModalProps {
data?: Resource
dismissModal: () => void
}

export const ResourceModal = ({data, dismissModal}: ResourceModalProps) => {
const [ref, setRef] = useState(null);
const handleRef = useCallback((node) => {setRef(node)}, [])

Expand Down Expand Up @@ -123,18 +130,19 @@ const Title = styled.div`
padding-bottom: 15px;
`

function _snapshotSummary(dates) {
function _snapshotSummary(dates: string[]) {
const d = [...dates].sort()
const [d1, d2] = [d[0], d.at(-1)].map((di) => new Date(di));
const days = (d2-d1)/1000/60/60/24;
const d1 = new Date(d.at( 0)).getTime();
const d2 = new Date(d.at(-1)).getTime();
const days = (d2 - d1)/1000/60/60/24;
let duration = '';
if (days < 100) duration=`${days} days`;
else if (days < 365*2) duration=`${Math.round(days/(365/12))} months`;
else duration=`${Math.round(days/365)} years`;
return {duration, first: d[0], last:d.at(-1)};
}

function _draw(ref, data) {
function _draw(ref, data: Resource) {
/* Note that _page_ resizes by themselves will not result in this function
rerunning, which isn't great, but for a modal I think it's perfectly
acceptable */
Expand Down Expand Up @@ -345,7 +353,7 @@ function _draw(ref, data) {

const dateWithYear = d3.utcFormat("%B %d, %Y");
const dateSameYear = d3.utcFormat("%B %d");
function prettyDate(mainDate, secondDate) {
function prettyDate(mainDate: string, secondDate?: string) {
const d1 = dateWithYear(new Date(mainDate));
if (!secondDate) return d1;
const d2 = (mainDate.slice(0,4)===secondDate.slice(0,4) ? dateSameYear : dateWithYear)(new Date(secondDate));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@ import { MdHistory, MdFormatListBulleted, MdChevronRight } from "react-icons/md"
import { IndividualResource, getMaxResourceWidth, TooltipWrapper, IconContainer,
ResourceLinkWrapper, ResourceLink, LINK_COLOR, LINK_HOVER_COLOR } from "./IndividualResource"
import { SetModalContext } from "./Modal";
import { Group, QuickLink, Resource } from './types';

const ResourceGroupHeader = ({data, isMobile, setCollapsed, collapsible, isCollapsed, resourcesToShowWhenCollapsed, quickLinks}) => {
interface ResourceGroupHeaderProps {
data: Group
isMobile: boolean
setCollapsed: React.Dispatch<React.SetStateAction<boolean>>
collapsible: boolean
isCollapsed: boolean
resourcesToShowWhenCollapsed: number
quickLinks: QuickLink[]
}

const ResourceGroupHeader = ({data, isMobile, setCollapsed, collapsible, isCollapsed, resourcesToShowWhenCollapsed, quickLinks}: ResourceGroupHeaderProps) => {
const setModal = useContext(SetModalContext);
/* Filter the known quick links to those which appear in resources of this group */
const resourcesByName = Object.fromEntries(data.resources.map((r) => [r.name, r]));
Expand Down Expand Up @@ -44,14 +55,14 @@ const ResourceGroupHeader = ({data, isMobile, setCollapsed, collapsible, isColla
<TooltipWrapper description={`There are ${data.nResources} datasets in this group`}>
<IconContainer
Icon={MdFormatListBulleted} color={"rgb(79, 75, 80)"}
text={data.nResources}
text={`${data.nResources}`}
/>
</TooltipWrapper>
{data.nVersions && !isMobile && (
<TooltipWrapper description={`${data.nVersions} snapshots exist across the ${data.nResources} datasets in this group`}>
<IconContainer
Icon={MdHistory} color={"rgb(79, 75, 80)"}
text={data.nVersions}
text={`${data.nVersions}`}
/>
</TooltipWrapper>
)}
Expand Down Expand Up @@ -99,15 +110,24 @@ const ResourceGroupHeader = ({data, isMobile, setCollapsed, collapsible, isColla
)
}

interface ResourceGroupProps {
data: Group
elWidth: number
numGroups: number
sortMethod: string
quickLinks: QuickLink[]
}

/**
* Displays a single resource group (e.g. a single pathogen)
*/
export const ResourceGroup = ({data, elWidth, numGroups, sortMethod, quickLinks}) => {
export const ResourceGroup = ({data, elWidth, numGroups, sortMethod, quickLinks}: ResourceGroupProps) => {
const {collapseThreshold, resourcesToShowWhenCollapsed} = collapseThresolds(numGroups);
const collapsible = data.resources.length > collapseThreshold;
const [isCollapsed, setCollapsed] = useState(collapsible); // if it is collapsible, start collapsed
const displayResources = isCollapsed ? data.resources.slice(0, resourcesToShowWhenCollapsed) : data.resources;
_setDisplayName(displayResources)

/* isMobile: boolean determines whether we expose snapshots, as we hide them on small screens */
const isMobile = elWidth < 500;

Expand Down Expand Up @@ -141,7 +161,7 @@ const ResourceGroupContainer = styled.div`
border-radius: 5px;
`;

const IndividualResourceContainer = styled.div`
const IndividualResourceContainer = styled.div<{$maxResourceWidth: number}>`
/* Columns are a simple CSS solution which works really well _if_ we can calculate the expected maximum
resource width */
column-width: ${(props) => props.$maxResourceWidth}px;
Expand Down Expand Up @@ -188,7 +208,7 @@ const Clickable = styled.div`
cursor: pointer;
`;

const Rotate = styled.div`
const Rotate = styled.div<{rotate: string}>`
max-width: 30px;
max-height: 30px;
/* font-size: 25px; */
Expand Down Expand Up @@ -225,7 +245,7 @@ function NextstrainLogo() {
* "seasonal-flu | h1n1pdm"
* " | h3n2"
*/
function _setDisplayName(resources) {
function _setDisplayName(resources: Resource[]) {
const sep = "│"; // ASCII 179
resources.forEach((r, i) => {
let name;
Expand All @@ -242,7 +262,7 @@ function _setDisplayName(resources) {
})
}

function collapseThresolds(numGroups) {
function collapseThresolds(numGroups: number) {
/* The collapse thresholds are determined by the total number of groups displayed */
let collapseThreshold = 12; /* if there are more than this many resources then we can collapse */
let resourcesToShowWhenCollapsed = 8; /* iff collapsed, show this many resources */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import { theme } from "../../layouts/theme";
import { goToAnchor } from '../../../vendored/react-scrollable-anchor/index';
import { createFilterOption } from "./useFilterOptions";
import { LIST_ANCHOR } from "./index";
import { Card, FilterOption, Group } from './types';

const cardWidthHeight = 160; // pixels

export const Showcase = ({cards, setSelectedFilterOptions}) => {
interface ShowcaseProps {
cards: Card[]
setSelectedFilterOptions: React.Dispatch<React.SetStateAction<readonly FilterOption[]>>
}

export const Showcase = ({cards, setSelectedFilterOptions}: ShowcaseProps) => {
if (!cards.length) return null;
return (
<div>
Expand All @@ -28,10 +34,15 @@ export const Showcase = ({cards, setSelectedFilterOptions}) => {
)
}

interface ShowcaseTileProps {
data: Card
setSelectedFilterOptions: React.Dispatch<React.SetStateAction<readonly FilterOption[]>>
}

/**
* NOTE: Many of the React components here are taken from the existing Cards UI
*/
const ShowcaseTile = ({data, setSelectedFilterOptions}) => {
const ShowcaseTile = ({data, setSelectedFilterOptions}: ShowcaseTileProps) => {
const filter = useCallback(
() => {
setSelectedFilterOptions(data.filters.map(createFilterOption));
Expand Down Expand Up @@ -115,8 +126,8 @@ const Byline = styled.div`
* which the filters are valid given the resources known to the resource listing
* UI
*/
export const useShowcaseCards = (cards, resources) => {
const [restrictedCards, setRestrictedCards] = useState([]);
export const useShowcaseCards = (cards?: Card[], resources?: Group[]) => {
const [restrictedCards, setRestrictedCards] = useState<Card[]>([]);
useEffect(() => {
if (!cards || !resources) return;
const words = resources.reduce((words, group) => {
Expand All @@ -126,7 +137,7 @@ export const useShowcaseCards = (cards, resources) => {
}
}
return words;
}, new Set());
}, new Set<string>());
setRestrictedCards(cards.filter((card) => card.filters.every((word) => words.has(word))));
}, [cards, resources]);
return restrictedCards;
Expand Down
Loading

0 comments on commit 9aec897

Please sign in to comment.