diff --git a/CHANGELOG.md b/CHANGELOG.md index 16ac87231..5263c8af5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## version 2.52.0 - 2024/02/09 - +* Bugfix: Restore the intended behaviour when hovering or clicking on terminal branches. ([#1753](https://github.com/nextstrain/auspice/pull/1753)) +## version 2.52.0 - 2024/02/09 * Sidebar filtering now contains all non-continuous metadata defined across the tree (i.e. all data within `node.node_attrs`). The traits listed in `meta.filters` are now only used to determine which filters to list in the footer of the page. ([#1743](https://github.com/nextstrain/auspice/pull/1743)) * The interaction between strain-selected modals and the corresponding strain-filter has been improved. We now preserve the strain filter state present before the node was clicked. ([#1749](https://github.com/nextstrain/auspice/issues/1749)) diff --git a/src/components/tree/infoPanels/click.js b/src/components/tree/infoPanels/click.js index eefb5148a..dc21c7281 100644 --- a/src/components/tree/infoPanels/click.js +++ b/src/components/tree/infoPanels/click.js @@ -247,31 +247,33 @@ const NodeClickedPanel = ({selectedNode, nodes, clearSelectedNode, colorings, ob const panelStyle = { ...infoPanelStyles.panel}; panelStyle.maxHeight = "70%"; const isTerminal = !node.hasChildren; - const title = isTerminal ? + const isTip = !selectedNode.isBranch; + + const title = isTip ? node.name : isTerminal ? `Branch leading to ${node.name}` : "Internal branch"; return ( -
clearSelectedNode(selectedNode, isTerminal)}> +
clearSelectedNode(selectedNode)}>
stopProp(e)}> {title} - {!isTerminal && item(t("Number of terminal tips"), node.fullTipCount)} - {isTerminal && } + {!isTip && item(t("Number of terminal tips"), node.fullTipCount)} + {isTip && } - {!isTerminal && item("Node name", node.name)} - {isTerminal && } + {!isTip && item("Node name", node.name)} + {isTip && } {getTraitsToDisplay(node).map((trait) => ( ))} - {isTerminal && } + {isTip && } {item("", "")}
- +

{t("Click outside this box to go back to the tree")}

diff --git a/src/components/tree/infoPanels/hover.js b/src/components/tree/infoPanels/hover.js index 393c36bbc..1812bea7c 100644 --- a/src/components/tree/infoPanels/hover.js +++ b/src/components/tree/infoPanels/hover.js @@ -402,11 +402,12 @@ const HoverInfoPanel = ({ t }) => { if (!selectedNode) return null - const node = selectedNode.n; + const node = selectedNode.node.n; // want the redux node, not the phylo node const idxOfInViewRootNode = getIdxOfInViewRootNode(node); + return ( - {node.hasChildren===false ? ( + {selectedNode.isBranch===false ? ( <> diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index dbd13f7d0..139bfdadc 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -13,7 +13,9 @@ export const onTipHover = function onTipHover(d) { this.state.treeToo; phylotree.svg.select("#"+getDomId("tip", d.n.name)) .attr("r", (e) => e["r"] + 4); - this.setState({hoveredNode: d}); + this.setState({ + hoveredNode: {node: d, isBranch: false} + }); }; export const onTipClick = function onTipClick(d) { @@ -22,7 +24,7 @@ export const onTipClick = function onTipClick(d) { /* The order of these two dispatches is important: the reducer handling `SELECT_NODE` must have access to the filtering state _prior_ to these filters being applied */ - this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx}); + this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx, isBranch: false}); this.props.dispatch(applyFilter("add", strainSymbol, [d.n.name])); }; @@ -46,7 +48,9 @@ export const onBranchHover = function onBranchHover(d) { } /* Set the hovered state so that an info box can be displayed */ - this.setState({hoveredNode: d}); + this.setState({ + hoveredNode: {node: d, isBranch: true} + }); }; export const onBranchClick = function onBranchClick(d) { @@ -56,7 +60,7 @@ export const onBranchClick = function onBranchClick(d) { /* if a branch was clicked while holding the shift key, we instead display a node-clicked modal */ if (window.event.shiftKey) { // no need to dispatch a filter action - this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx}) + this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx, isBranch: true}) return; } @@ -120,8 +124,8 @@ export const onTipLeave = function onTipLeave(d) { }; /* clearSelectedNode when clicking to remove the node-selected modal */ -export const clearSelectedNode = function clearSelectedNode(selectedNode, isTerminal) { - if (isTerminal) { +export const clearSelectedNode = function clearSelectedNode(selectedNode) { + if (!selectedNode.isBranch) { /* perform the filtering action (if necessary) that will restore the filtering state of the node prior to the selection */ if (!selectedNode.existingFilterState) { diff --git a/src/components/tree/tree.js b/src/components/tree/tree.js index 2020657dd..f91040fa4 100644 --- a/src/components/tree/tree.js +++ b/src/components/tree/tree.js @@ -44,10 +44,7 @@ class Tree extends React.Component { /* pressing the escape key should dismiss an info modal (if one exists) */ this.handlekeydownEvent = (event) => { if (event.key==="Escape" && this.props.selectedNode) { - this.clearSelectedNode( - this.props.selectedNode, - !this.props.tree.nodes[this.props.selectedNode.idx].hasChildren - ); + this.clearSelectedNode(this.props.selectedNode); } }; } diff --git a/src/reducers/controls.ts b/src/reducers/controls.ts index 503c8ded5..ca4024447 100644 --- a/src/reducers/controls.ts +++ b/src/reducers/controls.ts @@ -238,7 +238,7 @@ const Controls = (state: ControlsState = getDefaultControlsState(), action): Con const existingFilterInfo = (state.filters?.[strainSymbol]||[]).find((info) => info.value===action.name); const existingFilterState = existingFilterInfo === undefined ? null : existingFilterInfo.active ? 'active' : 'inactive'; - return {...state, selectedNode: {name: action.name, idx: action.idx, existingFilterState}}; + return {...state, selectedNode: {name: action.name, idx: action.idx, existingFilterState, isBranch: action.isBranch}}; } case types.DESELECT_NODE: { return {...state, selectedNode: null}