higlass
Version:
HiGlass Hi-C / genomic / large data viewer
345 lines (315 loc) • 11.3 kB
JSX
// @ts-nocheck
import clsx from 'clsx';
import PropTypes from 'prop-types';
import React from 'react';
import AddTrackPositionMenu from './AddTrackPositionMenu';
import ConfigViewMenu from './ConfigViewMenu';
import ContextMenuContainer from './ContextMenuContainer';
import PopupMenu from './PopupMenu';
// HOCS
import withTheme from './hocs/with-theme';
// Configs
import {
MOUSE_TOOL_SELECT,
THEME_DARK,
VIEW_HEADER_MED_WIDTH_SEARCH_BAR,
VIEW_HEADER_MIN_WIDTH_SEARCH_BAR,
} from './configs';
// Styles
import classes from '../styles/ViewHeader.module.scss';
class ViewHeader extends React.Component {
constructor(props) {
super(props);
this.configImg = null;
this.plusImg = null;
this.state = {
addTrackPositionMenuUid: null,
addTrackPositionMenuPosition: null,
configMenuUid: null,
configMenuPosition: null,
isFocused: false,
width: -1,
};
this.handleTrackPositionChosenBound =
this.handleTrackPositionChosen.bind(this);
}
componentDidMount() {
this.setState({ width: this.el.clientWidth });
}
checkWidth() {
const width = this.el.clientWidth;
if (width !== this.state.width) this.setState({ width });
}
/**
* The user clicked on the `cog` of the menu so we need to open
* it.
*/
handleConfigMenuOpened(uid) {
this.setState({
configMenuUid: uid,
configMenuPosition: this.configImg.getBoundingClientRect(),
});
}
/**
* The user has clicked on the 'plus' sign at the top of a TiledPlot
* so we need to open the Track Position Chooser dialog
*/
handleAddTrackPositionMenuOpened(uid) {
this.setState({
addTrackPositionMenuUid: uid,
addTrackPositionMenuPosition: this.plusImg.getBoundingClientRect(),
});
}
/**
* The user has chosen a position for the new track. The actual
* track selection will be handled by TiledPlot
*
* We just need to close the menu here.
*/
handleTrackPositionChosen(position, extent) {
this.props.onTrackPositionChosen(position, extent);
this.setState({
addTrackPositionMenuUid: null,
addTrackPositionMenuPosition: null,
});
}
render() {
let configMenu = null;
let addTrackPositionMenu = null;
if (this.state.addTrackPositionMenuPosition) {
addTrackPositionMenu = (
<PopupMenu
onMenuClosed={() => {
this.setState({
addTrackPositionMenuUid: null,
addTrackPositionMenuPosition: null,
});
}}
>
<ContextMenuContainer
orientation="left"
position={this.state.addTrackPositionMenuPosition}
theme={this.props.theme}
>
<AddTrackPositionMenu
onTrackPositionChosen={this.handleTrackPositionChosenBound}
/>
</ContextMenuContainer>
</PopupMenu>
);
}
if (this.state.configMenuUid) {
configMenu = (
<PopupMenu onMenuClosed={() => this.setState({ configMenuUid: null })}>
<ConfigViewMenu
onClearView={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onClearView();
}}
onEditViewConfig={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onEditViewConfig(this.state.configMenuUid);
}}
onExportPNG={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onExportPNG();
}}
onExportSVG={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onExportSVG();
}}
onExportViewAsJSON={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onExportViewsAsJSON();
}}
onExportViewAsLink={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onExportViewsAsLink();
}}
onLockLocation={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onLockLocation(this.state.configMenuUid);
}}
onLockZoom={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onLockZoom(this.state.configMenuUid);
}}
onLockZoomAndLocation={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onLockZoomAndLocation(this.state.configMenuUid);
}}
onOptionsChanged={(newOptions) => {
this.props.onViewOptionsChanged(newOptions);
this.setState({ configMenuUid: null }); // hide the menu
}}
onProjectViewport={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onProjectViewport(this.state.configMenuUid);
}}
onTakeAndLockZoomAndLocation={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onTakeAndLockZoomAndLocation(this.state.configMenuUid);
}}
onTogglePositionSearchBox={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onTogglePositionSearchBox(this.state.configMenuUid);
}}
onUnlockLocation={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onUnlockLocation(this.state.configMenuUid);
}}
onUnlockZoom={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onUnlockZoom(this.state.configMenuUid);
}}
onUnlockZoomAndLocation={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onUnlockZoomAndLocation(this.state.configMenuUid);
}}
onYankLocation={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onYankLocation(this.state.configMenuUid);
}}
onYankZoom={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onYankZoom(this.state.configMenuUid);
}}
onYankZoomAndLocation={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onYankZoomAndLocation(this.state.configMenuUid);
}}
onZoomToData={() => {
this.setState({ configMenuUid: null }); // hide the menu
this.props.onZoomToData(this.state.configMenuUid);
}}
orientation="left"
position={this.state.configMenuPosition}
theme={this.props.theme}
/>
</PopupMenu>
);
}
const GenomePositionSearchBox = this.props.getGenomePositionSearchBox(
this.state.isFocused,
(focus) => {
this.setState({
isFocused: focus,
});
},
);
const className = clsx(
this.state.isFocused
? classes['multitrack-header-focus']
: classes['multitrack-header'],
{
[classes['multitrack-header-dark']]: this.props.theme === THEME_DARK,
},
);
const classNameIcon =
this.state.width <= VIEW_HEADER_MED_WIDTH_SEARCH_BAR
? classes['multitrack-header-icon-squeazed']
: classes['multitrack-header-icon'];
return (
<div
ref={(c) => {
this.el = c;
}}
className={className}
>
<div className={classes['multitrack-header-left']}>
{this.props.mouseTool === MOUSE_TOOL_SELECT && (
<svg
className={clsx(classes['mouse-tool-selection'], classNameIcon)}
>
<title>Selection tool active</title>
<use xlinkHref="#select" />
</svg>
)}
<div
className={classes['multitrack-header-grabber']}
title="Drag to move the view"
>
<div />
<div />
<div />
</div>
{this.state.width > VIEW_HEADER_MIN_WIDTH_SEARCH_BAR && (
<div className={classes['multitrack-header-search']}>
{this.props.isGenomePositionSearchBoxVisible &&
GenomePositionSearchBox}
</div>
)}
</div>
<nav className={classes['multitrack-header-nav-list']}>
<svg className={classNameIcon} onClick={this.props.onAddView}>
<title>Add new view (clone this view)</title>
<use xlinkHref="#copy" />
</svg>
<svg
ref={(c) => {
this.configImg = c;
}}
className={classNameIcon}
onClick={() => this.handleConfigMenuOpened(this.props.viewUid)}
>
<title>Configure this view</title>
<use xlinkHref="#cog" />
</svg>
<svg
ref={(c) => {
this.plusImg = c;
}}
className={classNameIcon}
onClick={() =>
this.handleAddTrackPositionMenuOpened(this.props.viewUid)
}
data-testid="add-track"
>
<title>Add Track</title>
<use xlinkHref="#plus" />
</svg>
<svg className={classNameIcon} onClick={this.props.onCloseView}>
<title>Close View</title>
<use xlinkHref="#cross" />
</svg>
</nav>
{configMenu}
{addTrackPositionMenu}
</div>
);
}
}
ViewHeader.defaultProps = {
isGenomePositionSearchBoxVisible: false,
};
ViewHeader.propTypes = {
getGenomePositionSearchBox: PropTypes.func.isRequired,
isGenomePositionSearchBoxVisible: PropTypes.bool,
mouseTool: PropTypes.string.isRequired,
onAddView: PropTypes.func.isRequired,
onClearView: PropTypes.func.isRequired,
onCloseView: PropTypes.func.isRequired,
onEditViewConfig: PropTypes.func.isRequired,
onExportSVG: PropTypes.func.isRequired,
onExportPNG: PropTypes.func.isRequired,
onExportViewsAsJSON: PropTypes.func.isRequired,
onExportViewsAsLink: PropTypes.func.isRequired,
onLockLocation: PropTypes.func.isRequired,
onLockZoom: PropTypes.func.isRequired,
onLockZoomAndLocation: PropTypes.func.isRequired,
onProjectViewport: PropTypes.func.isRequired,
onTakeAndLockZoomAndLocation: PropTypes.func.isRequired,
onTogglePositionSearchBox: PropTypes.func.isRequired,
onTrackPositionChosen: PropTypes.func.isRequired,
onUnlockLocation: PropTypes.func.isRequired,
onUnlockZoom: PropTypes.func.isRequired,
onUnlockZoomAndLocation: PropTypes.func.isRequired,
onViewOptionsChanged: PropTypes.func.isRequired,
onYankLocation: PropTypes.func.isRequired,
onYankZoom: PropTypes.func.isRequired,
onYankZoomAndLocation: PropTypes.func.isRequired,
onZoomToData: PropTypes.func.isRequired,
theme: PropTypes.symbol.isRequired,
viewUid: PropTypes.string.isRequired,
};
export default withTheme(ViewHeader);