@rcsb/rcsb-saguaro
Version:
RCSB 1D Feature Viewer
190 lines (189 loc) • 9.51 kB
JavaScript
import { __awaiter } from "tslib";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import React from "react";
import * as classes from "../../scss/RcsbFvRow.module.scss";
import { CSSTransition } from "react-transition-group";
import { RcsbFvDefaultConfigValues } from "../RcsbFvConfig/RcsbFvDefaultConfigValues";
import { EventType } from "../RcsbFvContextManager/RcsbFvContextManager";
import { asyncScheduler } from "rxjs";
import { computePosition, detectOverflow } from "@floating-ui/dom";
import BxPlus from "./icons/bx-plus.svg";
import BxMinus from "./icons/bx-minus.svg";
import BxRight from "./icons/bx-right-arrow.svg";
import BxLeft from "./icons/bx-left-arrow.svg";
import BxDown from "./icons/bxs-down-arrow.svg";
export class RcsbFvUI extends React.Component {
constructor() {
super(...arguments);
this.collapseRef = React.createRef();
this.expandRef = React.createRef();
/**UI config Object*/
this.config = [{
icon: _jsx(BxPlus, Object.assign({}, RcsbFvUI.ICON_PROPS)),
callback: this.zoomIn.bind(this),
name: "zoom-in"
}, {
icon: _jsx(BxMinus, Object.assign({}, RcsbFvUI.ICON_PROPS)),
callback: this.zoomOut.bind(this),
name: "zoom-out"
}, {
icon: _jsx(BxRight, Object.assign({}, RcsbFvUI.ICON_PROPS)),
callback: this.move.bind(this, 1),
name: "move-right"
}, {
icon: _jsx(BxLeft, Object.assign({}, RcsbFvUI.ICON_PROPS)),
callback: this.move.bind(this, -1),
name: "move-left"
}];
this.hideTask = null;
this.state = {
collapse: false
};
}
render() {
return (_jsx("div", { id: this.props.boardId + "_uiDiv" /* RcsbFvDOMConstants.UI_DOM_ID_PREFIX */, className: classes.rcsbUI + " " + classes.rcsbSmoothDivHide, style: { position: "absolute", top: 0, left: 0 }, children: _jsxs("div", { style: { position: "relative" }, children: [_jsx(CSSTransition, { in: this.state.collapse, timeout: 300, classNames: classes.rcsbCollapseUI, nodeRef: this.collapseRef, children: _jsx("div", { style: { position: "absolute" }, className: classes.rcsbCollapsedUIDiv + " " + classes.rcsbCollapseUI, onMouseEnter: this.changeState.bind(this, { collapse: false }), ref: this.collapseRef, children: _jsx("div", { className: classes.rcsbCollapsedIcon, children: _jsx(BxDown, Object.assign({}, RcsbFvUI.ICON_PROPS)) }) }) }), _jsx(CSSTransition, { in: !this.state.collapse, timeout: 300, classNames: classes.rcsbExpandUI, nodeRef: this.expandRef, children: _jsx("div", { style: { position: "absolute" }, className: classes.rcsbExpandUI, onMouseLeave: this.changeState.bind(this, { collapse: true }), ref: this.expandRef, children: this.config.map(button => {
return this.buildButton(button);
}) }) })] }) }));
}
componentDidMount() {
this.subscription = this.subscribe();
const refDiv = document.querySelector("#" + this.props.boardId);
if (refDiv == null)
throw "Main board DOM element not found";
this.refDiv = refDiv;
const tooltipDiv = document.querySelector("#" + this.props.boardId + "_uiDiv" /* RcsbFvDOMConstants.UI_DOM_ID_PREFIX */);
if (tooltipDiv == null)
throw "Tooltip DOM element not found";
this.tooltipDiv = tooltipDiv;
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
subscribe() {
return this.props.contextManager.subscribe((o) => {
switch (o.eventType) {
case EventType.BOARD_HOVER:
this.boardHover(o.eventData);
break;
}
});
}
boardHover(flag) {
if (flag) {
this.displayUI();
}
else {
this.hideUI();
}
}
displayUI() {
if (this.hideTask)
this.hideTask.unsubscribe();
const offsetHeight = this.props.boardConfigData.includeAxis === true ? RcsbFvDefaultConfigValues.trackAxisHeight + 2 : 0;
computePosition(this.refDiv, this.tooltipDiv, {
placement: 'right-start',
middleware: [{
name: 'middleware',
fn(middlewareArguments) {
return __awaiter(this, void 0, void 0, function* () {
const overflow = yield detectOverflow(middlewareArguments, {
rootBoundary: "viewport"
});
if (overflow.top > offsetHeight)
return { y: overflow.top + middlewareArguments.y - offsetHeight };
return {};
});
},
}]
}).then(({ x, y }) => {
Object.assign(this.tooltipDiv.style, {
left: `${x}px`,
top: `${y + offsetHeight}px`
});
});
this.tooltipDiv.classList.remove(classes.rcsbSmoothDivHide);
this.tooltipDiv.classList.add(classes.rcsbSmoothDivDisplay);
}
hideUI() {
const tooltipDiv = document.querySelector("#" + this.props.boardId + "_uiDiv" /* RcsbFvDOMConstants.UI_DOM_ID_PREFIX */);
if (tooltipDiv == null)
return;
this.hideTask = asyncScheduler.schedule(() => {
tooltipDiv.classList.remove(classes.rcsbSmoothDivDisplay);
tooltipDiv.classList.add(classes.rcsbSmoothDivHide);
}, 300);
}
buildButton(buttonConfig) {
return (_jsx("div", { className: classes.rcsbUIButton, children: _jsx("div", { className: classes.rcsbIcon, onClick: buttonConfig.callback, children: buttonConfig.icon }) }, buttonConfig.name));
}
changeState(state) {
this.setState(state);
}
/***************
** UI methods **
****************/
zoomIn() {
const max = this.props.boardConfigData.range != null ? this.props.boardConfigData.range.max : this.props.boardConfigData.length;
if (max == null)
return;
const currentDomain = this.props.xScale.domain();
const deltaZoom = Math.floor((currentDomain[1] - currentDomain[0]) * 0.1);
const x = currentDomain[0] + deltaZoom;
const y = currentDomain[1] - deltaZoom;
if ((y - x) > 20)
this.setDomain({ domain: [x, y] });
}
zoomOut() {
const max = this.props.boardConfigData.range != null ? this.props.boardConfigData.range.max : this.props.boardConfigData.length;
const min = this.props.boardConfigData.range != null ? this.props.boardConfigData.range.min : 1;
if (max == null)
return;
const currentDomain = this.props.xScale.domain();
const deltaZoom = Math.floor((currentDomain[1] - currentDomain[0]) * 0.1);
const x = currentDomain[0] - deltaZoom > (min - RcsbFvDefaultConfigValues.increasedView) ? currentDomain[0] - deltaZoom : (min - RcsbFvDefaultConfigValues.increasedView);
const y = currentDomain[1] + deltaZoom < max + RcsbFvDefaultConfigValues.increasedView ? currentDomain[1] + deltaZoom : max + RcsbFvDefaultConfigValues.increasedView;
if ((y - x) < (max + RcsbFvDefaultConfigValues.increasedView))
this.setDomain({ domain: [x, y] });
else
this.setDomain({ domain: [(min - RcsbFvDefaultConfigValues.increasedView), max + RcsbFvDefaultConfigValues.increasedView] });
}
move(direction) {
const max = this.props.boardConfigData.range != null ? this.props.boardConfigData.range.max : this.props.boardConfigData.length;
const min = this.props.boardConfigData.range != null ? this.props.boardConfigData.range.min : 1;
if (max == null)
return;
const currentDomain = this.props.xScale.domain();
let deltaZoom = Math.floor((currentDomain[1] - currentDomain[0]) * 0.1);
if (currentDomain[0] + direction * deltaZoom < (min - RcsbFvDefaultConfigValues.increasedView))
deltaZoom = currentDomain[0] - (min - RcsbFvDefaultConfigValues.increasedView);
else if (currentDomain[1] + direction * deltaZoom > (max + RcsbFvDefaultConfigValues.increasedView))
deltaZoom = max + RcsbFvDefaultConfigValues.increasedView - currentDomain[1];
const x = currentDomain[0] + direction * deltaZoom;
const y = currentDomain[1] + direction * deltaZoom;
if ((y - x) < (max + RcsbFvDefaultConfigValues.increasedView))
this.setDomain({ domain: [x, y] });
else
this.setDomain({ domain: [(min - RcsbFvDefaultConfigValues.increasedView), max + RcsbFvDefaultConfigValues.increasedView] });
}
/**Force all board track annotation cells to set xScale. Called when a new track has been added*/
setScale() {
if (this.props.xScale != null) {
this.props.contextManager.next({
eventType: EventType.SCALE,
eventData: this.props.boardId
});
}
}
/**Update d3 xScale domain
* @param domainData new xScale domain
* */
setDomain(domainData) {
this.props.xScale.domain(domainData.domain);
this.setScale();
}
}
RcsbFvUI.ICON_PROPS = {
width: 16,
height: 16,
viewBox: "0 0 24 24"
};