UNPKG

@macrostrat/column-components

Version:

React rendering primitives for stratigraphic columns

304 lines (303 loc) 8.11 kB
import { format } from "d3-format"; import { Component, createRef, useContext } from "react"; import h from "./hyper.js"; import { Popover, Position, Button, Intent } from "@blueprintjs/core"; import "./chroma-js/index.js"; import { Box } from "@macrostrat/ui-components"; import chroma from "./chroma-js/src/chroma.js"; import { ColumnLayoutContext } from "./context/layout.js"; format(".1f"); const fmt2 = format(".2f"); const PopoverEditorTitle = function(props) { const { interval, children } = props; return h("div.interval-editor-title", [ h("h3", `${fmt2(interval.bottom)}–${fmt2(interval.top)} m`), h("div.id", [h("code", interval.id)]), children ]); }; const OverlayBox = (props) => { const { division, background, className, onClick } = props; const { widthForDivision, scaleClamped } = useContext(ColumnLayoutContext); if (scaleClamped == null) return null; const top = scaleClamped(division.top); const bottom = scaleClamped(division.bottom); const height = bottom - top; const width = widthForDivision(division); const style = { marginTop: top, height, width, pointerEvents: "none", position: "absolute" }; return h("div", { style }, [ h("div", { onClick, className, style: { cursor: onClick != null ? "pointer" : null, width: "100%", height: "100%", background } }), props.children ]); }; const EditingBox = function({ division, color, ...rest }) { if (division == null) { return null; } if (color == null) { color = "red"; } const background = chroma(color).alpha(0.5).css(); return h(OverlayBox, { className: "editing-box", division, background, ...rest }); }; class DivisionEditOverlay extends Component { static contextType = ColumnLayoutContext; static defaultProps = { onHoverInterval() { }, onClick() { }, left: 0, top: 0, showInfoBox: false, allowEditing: true, renderEditorPopup() { return null; }, color: "red", popoverWidth: 340 }; elementRef; timeout; constructor(props) { super(props); this.onHoverInterval = this.onHoverInterval.bind(this); this.removeHoverBox = this.removeHoverBox.bind(this); this.heightForEvent = this.heightForEvent.bind(this); this.onEditInterval = this.onEditInterval.bind(this); this.onClick = this.onClick.bind(this); this.renderCursorLine = this.renderCursorLine.bind(this); this.renderHoveredBox = this.renderHoveredBox.bind(this); this.closePopover = this.closePopover.bind(this); this.state = { height: null, hoveredDivision: null, popoverIsOpen: false }; this.timeout = null; this.elementRef = createRef(); } onHoverInterval(event) { event.stopPropagation(); if (this.elementRef.current !== event.target) { return; } const height = this.heightForEvent(event); this.setState({ height }); if (!this.props.allowEditing) { return; } const { divisions } = this.context; let division = null; for (let d of Array.from(divisions)) { if (d.bottom <= height && height < d.top) { division = d; break; } } if (division === this.state.hoveredDivision) { return; } this.setState({ hoveredDivision: division }); if (this.timeout != null) { clearTimeout(this.timeout); return this.timeout = null; } } removeHoverBox() { this.setState({ hoveredDivision: null, popoverIsOpen: false }); return this.timeout = null; } heightForEvent(event) { const { scale } = this.context; const { offsetY } = event.nativeEvent; return scale.invert(offsetY); } onEditInterval(event) { if (this.state.popoverIsOpen) { return; } const { showInfoBox } = this.props; const { hoveredDivision } = this.state; const height = this.heightForEvent(event); event.stopPropagation(); if (event.shiftKey && showInfoBox) { this.setState({ popoverIsOpen: true }); return; } return this.props.onClick({ event, height, division: hoveredDivision }); } onClick(event) { if (this.props.allowEditing) { return this.onEditInterval(event); } const height = this.heightForEvent(event); return this.props.onClick({ height }); } renderCursorLine() { let { height, hoveredDivision } = this.state; const { scaleClamped } = this.context; const { selectedHeight } = this.props; if (height == null) { height = selectedHeight; } if (height == null) { return; } const style = { top: scaleClamped(height), height: 0, border: "0.5px solid black", width: this.context.widthForDivision(hoveredDivision), position: "absolute", pointerEvents: "none" }; return h("div.cursor", { style }, [ h( "div.cursor-position", { style: { pointerEvents: "none", fontWeight: "bold", fontSize: "12px", left: "2px", top: "-14px", position: "absolute", color: "black" } }, [fmt2(height)] ) ]); } renderHoveredBox() { if (this.state.hoveredDivision == null) { return null; } const { popoverIsOpen, hoveredDivision: division } = this.state; const width = this.context.widthForDivision(division); const { color } = this.props; const background = chroma(color).alpha(0.3).css(); return h( OverlayBox, { division, className: "hovered-box", background }, [ h.if(this.props.renderEditorPopup != null)( Popover, { isOpen: popoverIsOpen && division != null, //style: { display: "block", width }, position: Position.LEFT }, [ h("div", { style: { width, height: 30, transform: "translate(0,-30)" } }), h( "div.editor-popover-contents", { style: { width: this.props.popoverWidth, padding: "10px" } }, [ h( PopoverEditorTitle, { interval: division }, [ h(Button, { icon: "cross", minimal: true, intent: Intent.WARNING, onClick: this.closePopover.bind(this) }) ] ), this.props.renderEditorPopup(division) ] ) ] ) ] ); } closePopover() { return this.setState({ popoverIsOpen: false }); } render() { let { divisions, pixelHeight, width } = this.context; const { popoverIsOpen, hoveredDivision: division } = this.state; const { left, top, color } = this.props; if (width == null) { ({ width } = this.props); } return h( Box, { className: "edit-overlay", width, height: pixelHeight, ref: this.elementRef, style: { left, top, position: "absolute", zIndex: 18, pointerEvents: "all", cursor: "pointer" }, onClick: this.onEditInterval, onMouseEnter: this.onHoverInterval, onMouseMove: this.onHoverInterval, onMouseLeave: () => { if (popoverIsOpen) { return; } this.setState({ height: null }); return this.timeout = setTimeout(this.removeHoverBox, 1e3); } }, [ this.renderHoveredBox(), h(EditingBox, { division: this.props.editingInterval, color }), this.renderCursorLine() ] ); } } export { DivisionEditOverlay }; //# sourceMappingURL=edit-overlay.js.map