Convert Annotation sub-components to TS

pull/10616/head
ebb-tide 2018-06-15 13:49:53 -07:00
parent bb091cb94e
commit 1d3dda6667
13 changed files with 432 additions and 395 deletions

View File

@ -12,7 +12,7 @@ interface State {
@ErrorHandling
class SearchBar extends PureComponent<Props, State> {
constructor(props) {
constructor(props: Props) {
super(props)
this.state = {

View File

@ -20,7 +20,7 @@ interface State {
class QueryEditor extends PureComponent<Props, State> {
private editor: React.RefObject<HTMLTextAreaElement>
constructor(props) {
constructor(props: Props) {
super(props)
this.state = {
value: this.props.query,

View File

@ -9,8 +9,8 @@ export const TEMP_ANNOTATION: AnnotationInterface = {
id: 'tempAnnotation',
text: 'Name Me',
type: '',
startTime: '',
endTime: '',
startTime: null,
endTime: null,
}
export const visibleAnnotations = (
@ -24,10 +24,13 @@ export const visibleAnnotations = (
}
return annotations.filter(a => {
if (a.startTime === null || a.endTime === null) {
return false
}
if (a.endTime === a.startTime) {
return xStart <= +a.startTime && +a.startTime <= xEnd
return xStart <= a.startTime && a.startTime <= xEnd
}
return !(+a.endTime < xStart || xEnd < +a.startTime)
return !(a.endTime < xStart || xEnd < a.startTime)
})
}

View File

@ -1,45 +1,25 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {Component, ChangeEvent, FocusEvent, KeyboardEvent} from 'react'
import onClickOutside from 'react-onclickoutside'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface State {
isEditing: boolean
}
interface Props {
value: string
onChangeInput: (i: string) => void
onConfirmUpdate: () => void
onRejectUpdate: () => void
}
@ErrorHandling
class AnnotationInput extends Component {
state = {
class AnnotationInput extends Component<Props, State> {
public state = {
isEditing: false,
}
handleInputClick = () => {
this.setState({isEditing: true})
}
handleKeyDown = e => {
const {onConfirmUpdate, onRejectUpdate} = this.props
if (e.key === 'Enter') {
onConfirmUpdate()
this.setState({isEditing: false})
}
if (e.key === 'Escape') {
onRejectUpdate()
this.setState({isEditing: false})
}
}
handleFocus = e => {
e.target.select()
}
handleChange = e => {
this.props.onChangeInput(e.target.value)
}
handleClickOutside = () => {
this.props.onConfirmUpdate()
this.setState({isEditing: false})
}
render() {
public render() {
const {isEditing} = this.state
const {value} = this.props
@ -65,15 +45,31 @@ class AnnotationInput extends Component {
</div>
)
}
}
const {func, string} = PropTypes
private handleInputClick = () => {
this.setState({isEditing: true})
}
AnnotationInput.propTypes = {
value: string,
onChangeInput: func.isRequired,
onConfirmUpdate: func.isRequired,
onRejectUpdate: func.isRequired,
private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
const {onConfirmUpdate, onRejectUpdate} = this.props
if (e.key === 'Enter') {
onConfirmUpdate()
this.setState({isEditing: false})
}
if (e.key === 'Escape') {
onRejectUpdate()
this.setState({isEditing: false})
}
}
private handleFocus = (e: FocusEvent<HTMLInputElement>) => {
e.target.select()
}
private handleChange = (e: ChangeEvent<HTMLInputElement>) => {
this.props.onChangeInput(e.target.value)
}
}
export default onClickOutside(AnnotationInput)

View File

@ -1,49 +1,116 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {Component, MouseEvent, DragEvent} from 'react'
import {connect} from 'react-redux'
import {
DYGRAPH_CONTAINER_H_MARGIN,
DYGRAPH_CONTAINER_V_MARGIN,
DYGRAPH_CONTAINER_XLABEL_MARGIN,
} from 'shared/constants'
import {ANNOTATION_MIN_DELTA, EDITING} from 'shared/annotations/helpers'
import * as schema from 'shared/schemas'
import * as actions from 'shared/actions/annotations'
import AnnotationTooltip from 'shared/components/AnnotationTooltip'
} from 'src/shared/constants'
import {ANNOTATION_MIN_DELTA, EDITING} from 'src/shared/annotations/helpers'
import * as actions from 'src/shared/actions/annotations'
import AnnotationTooltip from 'src/shared/components/AnnotationTooltip'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AnnotationInterface, DygraphClass} from 'src/types'
interface State {
isMouseOver: boolean
isDragging: boolean
}
interface Props {
annotation: AnnotationInterface
mode: string
xAxisRange: [number, number]
dygraph: DygraphClass
updateAnnotation: (a: AnnotationInterface) => void
updateAnnotationAsync: (a: AnnotationInterface) => void
staticLegendHeight: number
}
@ErrorHandling
class AnnotationPoint extends React.Component {
state = {
class AnnotationPoint extends Component<Props, State> {
public static defaultProps: Partial<Props> = {
staticLegendHeight: 0,
}
public state = {
isMouseOver: false,
isDragging: false,
}
handleMouseEnter = () => {
public render() {
const {annotation, mode, dygraph, staticLegendHeight} = this.props
const {isDragging} = this.state
const isEditing = mode === EDITING
const flagClass = isDragging
? 'annotation-point--flag__dragging'
: 'annotation-point--flag'
const markerClass = isDragging ? 'annotation dragging' : 'annotation'
const clickClass = isEditing
? 'annotation--click-area editing'
: 'annotation--click-area'
const markerStyles = {
left: `${dygraph.toDomXCoord(Number(annotation.startTime)) +
DYGRAPH_CONTAINER_H_MARGIN}px`,
height: `calc(100% - ${staticLegendHeight +
DYGRAPH_CONTAINER_XLABEL_MARGIN +
DYGRAPH_CONTAINER_V_MARGIN * 2}px)`,
}
return (
<div className={markerClass} style={markerStyles}>
<div
className={clickClass}
draggable={true}
onDrag={this.handleDrag}
onDragStart={this.handleDragStart}
onDragEnd={this.handleDragEnd}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
/>
<div className={flagClass} />
<AnnotationTooltip
isEditing={isEditing}
timestamp={annotation.startTime}
annotation={annotation}
onMouseLeave={this.handleMouseLeave}
annotationState={this.state}
/>
</div>
)
}
private handleMouseEnter = () => {
this.setState({isMouseOver: true})
}
handleMouseLeave = e => {
private handleMouseLeave = (e: MouseEvent<HTMLDivElement>) => {
const {annotation} = this.props
if (e.relatedTarget.id === `tooltip-${annotation.id}`) {
return this.setState({isDragging: false})
if (e.relatedTarget instanceof Element) {
if (e.relatedTarget.id === `tooltip-${annotation.id}`) {
return this.setState({isDragging: false})
}
}
this.setState({isMouseOver: false})
}
handleDragStart = () => {
private handleDragStart = () => {
this.setState({isDragging: true})
}
handleDragEnd = () => {
private handleDragEnd = () => {
const {annotation, updateAnnotationAsync} = this.props
updateAnnotationAsync(annotation)
this.setState({isDragging: false})
}
handleDrag = e => {
private handleDrag = (e: DragEvent<HTMLDivElement>) => {
if (this.props.mode !== EDITING) {
return
}
@ -83,78 +150,19 @@ class AnnotationPoint extends React.Component {
updateAnnotation({
...annotation,
startTime: `${newTime}`,
endTime: `${newTime}`,
startTime: newTime,
endTime: newTime,
})
e.preventDefault()
e.stopPropagation()
}
render() {
const {annotation, mode, dygraph, staticLegendHeight} = this.props
const {isDragging} = this.state
const isEditing = mode === EDITING
const flagClass = isDragging
? 'annotation-point--flag__dragging'
: 'annotation-point--flag'
const markerClass = isDragging ? 'annotation dragging' : 'annotation'
const clickClass = isEditing
? 'annotation--click-area editing'
: 'annotation--click-area'
const markerStyles = {
left: `${dygraph.toDomXCoord(annotation.startTime) +
DYGRAPH_CONTAINER_H_MARGIN}px`,
height: `calc(100% - ${staticLegendHeight +
DYGRAPH_CONTAINER_XLABEL_MARGIN +
DYGRAPH_CONTAINER_V_MARGIN * 2}px)`,
}
return (
<div className={markerClass} style={markerStyles}>
<div
className={clickClass}
draggable={true}
onDrag={this.handleDrag}
onDragStart={this.handleDragStart}
onDragEnd={this.handleDragEnd}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
/>
<div className={flagClass} />
<AnnotationTooltip
isEditing={isEditing}
timestamp={annotation.startTime}
annotation={annotation}
onMouseLeave={this.handleMouseLeave}
annotationState={this.state}
/>
</div>
)
}
}
const {arrayOf, func, number, shape, string} = PropTypes
AnnotationPoint.defaultProps = {
staticLegendHeight: 0,
}
AnnotationPoint.propTypes = {
annotation: schema.annotation.isRequired,
mode: string.isRequired,
xAxisRange: arrayOf(number),
dygraph: shape({}).isRequired,
updateAnnotation: func.isRequired,
updateAnnotationAsync: func.isRequired,
staticLegendHeight: number,
}
const mdtp = {
updateAnnotationAsync: actions.updateAnnotationAsync,
updateAnnotation: actions.updateAnnotation,

View File

@ -1,44 +1,83 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {Component, MouseEvent, DragEvent} from 'react'
import {connect} from 'react-redux'
import {
DYGRAPH_CONTAINER_H_MARGIN,
DYGRAPH_CONTAINER_V_MARGIN,
DYGRAPH_CONTAINER_XLABEL_MARGIN,
} from 'shared/constants'
import {ANNOTATION_MIN_DELTA, EDITING} from 'shared/annotations/helpers'
import * as schema from 'shared/schemas'
import * as actions from 'shared/actions/annotations'
import AnnotationTooltip from 'shared/components/AnnotationTooltip'
import AnnotationWindow from 'shared/components/AnnotationWindow'
} from 'src/shared/constants'
import * as actions from 'src/shared/actions/annotations'
import {ANNOTATION_MIN_DELTA, EDITING} from 'src/shared/annotations/helpers'
import AnnotationTooltip from 'src/shared/components/AnnotationTooltip'
import AnnotationWindow from 'src/shared/components/AnnotationWindow'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AnnotationInterface, DygraphClass} from 'src/types'
interface State {
isMouseOver: string
isDragging: string
}
interface Props {
annotation: AnnotationInterface
mode: string
dygraph: DygraphClass
staticLegendHeight: number
updateAnnotation: (a: AnnotationInterface) => void
updateAnnotationAsync: (a: AnnotationInterface) => void
xAxisRange: [number, number]
}
@ErrorHandling
class AnnotationSpan extends React.Component {
state = {
class AnnotationSpan extends Component<Props, State> {
public static defaultProps: Partial<Props> = {
staticLegendHeight: 0,
}
public state: State = {
isDragging: null,
isMouseOver: null,
}
handleMouseEnter = direction => () => {
public render() {
const {annotation, dygraph, staticLegendHeight} = this.props
const {isDragging} = this.state
return (
<div>
<AnnotationWindow
annotation={annotation}
dygraph={dygraph}
active={!!isDragging}
staticLegendHeight={staticLegendHeight}
/>
{this.renderLeftMarker(annotation.startTime, dygraph)}
{this.renderRightMarker(annotation.endTime, dygraph)}
</div>
)
}
private handleMouseEnter = (direction: string) => () => {
this.setState({isMouseOver: direction})
}
handleMouseLeave = e => {
private handleMouseLeave = (e: MouseEvent<HTMLDivElement>) => {
const {annotation} = this.props
if (e.relatedTarget.id === `tooltip-${annotation.id}`) {
return this.setState({isDragging: null})
if (e.relatedTarget instanceof Element) {
if (e.relatedTarget.id === `tooltip-${annotation.id}`) {
return this.setState({isDragging: null})
}
}
this.setState({isMouseOver: null})
}
handleDragStart = direction => () => {
private handleDragStart = (direction: string) => () => {
this.setState({isDragging: direction})
}
handleDragEnd = () => {
private handleDragEnd = () => {
const {annotation, updateAnnotationAsync} = this.props
const [startTime, endTime] = [
annotation.startTime,
@ -54,7 +93,7 @@ class AnnotationSpan extends React.Component {
this.setState({isDragging: null})
}
handleDrag = timeProp => e => {
private handleDrag = (timeProp: string) => (e: DragEvent<HTMLDivElement>) => {
if (this.props.mode !== EDITING) {
return
}
@ -96,7 +135,10 @@ class AnnotationSpan extends React.Component {
e.stopPropagation()
}
renderLeftMarker(startTime, dygraph) {
private renderLeftMarker(
startTime: number,
dygraph: DygraphClass
): JSX.Element {
const isEditing = this.props.mode === EDITING
const {isDragging, isMouseOver} = this.state
const {annotation, staticLegendHeight} = this.props
@ -147,7 +189,10 @@ class AnnotationSpan extends React.Component {
)
}
renderRightMarker(endTime, dygraph) {
private renderRightMarker(
endTime: number,
dygraph: DygraphClass
): JSX.Element {
const isEditing = this.props.mode === EDITING
const {isDragging, isMouseOver} = this.state
const {annotation, staticLegendHeight} = this.props
@ -197,40 +242,6 @@ class AnnotationSpan extends React.Component {
</div>
)
}
render() {
const {annotation, dygraph, staticLegendHeight} = this.props
const {isDragging} = this.state
return (
<div>
<AnnotationWindow
annotation={annotation}
dygraph={dygraph}
active={!!isDragging}
staticLegendHeight={staticLegendHeight}
/>
{this.renderLeftMarker(annotation.startTime, dygraph)}
{this.renderRightMarker(annotation.endTime, dygraph)}
</div>
)
}
}
const {arrayOf, func, number, shape, string} = PropTypes
AnnotationSpan.defaultProps = {
staticLegendHeight: 0,
}
AnnotationSpan.propTypes = {
annotation: schema.annotation.isRequired,
mode: string.isRequired,
dygraph: shape({}).isRequired,
staticLegendHeight: number,
updateAnnotationAsync: func.isRequired,
updateAnnotation: func.isRequired,
xAxisRange: arrayOf(number),
}
const mapDispatchToProps = {

View File

@ -1,50 +1,62 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {Component, MouseEvent} from 'react'
import {connect} from 'react-redux'
import moment from 'moment'
import classnames from 'classnames'
import AnnotationInput from 'src/shared/components/AnnotationInput'
import * as schema from 'shared/schemas'
import * as actions from 'shared/actions/annotations'
import * as actions from 'src/shared/actions/annotations'
import {ErrorHandling} from 'src/shared/decorators/errors'
const TimeStamp = ({time}) => (
import {AnnotationInterface} from 'src/types'
interface TimeStampProps {
time: string
}
const TimeStamp = ({time}: TimeStampProps): JSX.Element => (
<div className="annotation-tooltip--timestamp">
{`${moment(+time).format('YYYY/MM/DD HH:mm:ss.SS')}`}
</div>
)
interface AnnotationState {
isDragging: boolean
isMouseOver: boolean
}
interface Span {
spanCenter: number
tooltipLeft: number
spanWidth: number
}
interface State {
annotation: AnnotationInterface
}
interface Props {
isEditing: boolean
annotation: AnnotationInterface
timestamp: string
onMouseLeave: (e: MouseEvent<HTMLDivElement>) => {}
annotationState: AnnotationState
deleteAnnotationAsync: (a: AnnotationInterface) => void
updateAnnotationAsync: (a: AnnotationInterface) => void
span: Span
}
@ErrorHandling
class AnnotationTooltip extends Component {
state = {
class AnnotationTooltip extends Component<Props, State> {
public state = {
annotation: this.props.annotation,
}
componentWillReceiveProps = ({annotation}) => {
public componentWillReceiveProps(nextProps: Props) {
const {annotation} = nextProps
this.setState({annotation})
}
handleChangeInput = key => value => {
const {annotation} = this.state
const newAnnotation = {...annotation, [key]: value}
this.setState({annotation: newAnnotation})
}
handleConfirmUpdate = () => {
this.props.updateAnnotationAsync(this.state.annotation)
}
handleRejectUpdate = () => {
this.setState({annotation: this.props.annotation})
}
handleDelete = () => {
this.props.deleteAnnotationAsync(this.props.annotation)
}
render() {
public render() {
const {annotation} = this.state
const {
onMouseLeave,
@ -99,28 +111,30 @@ class AnnotationTooltip extends Component {
</div>
)
}
private handleChangeInput = (key: string) => (value: string) => {
const {annotation} = this.state
const newAnnotation = {...annotation, [key]: value}
this.setState({annotation: newAnnotation})
}
private handleConfirmUpdate = () => {
this.props.updateAnnotationAsync(this.state.annotation)
}
private handleRejectUpdate = () => {
this.setState({annotation: this.props.annotation})
}
private handleDelete = () => {
this.props.deleteAnnotationAsync(this.props.annotation)
}
}
const {bool, func, number, shape, string} = PropTypes
TimeStamp.propTypes = {
time: string.isRequired,
}
AnnotationTooltip.propTypes = {
isEditing: bool,
annotation: schema.annotation.isRequired,
timestamp: string,
onMouseLeave: func.isRequired,
annotationState: shape({}),
deleteAnnotationAsync: func.isRequired,
updateAnnotationAsync: func.isRequired,
span: shape({
spanCenter: number.isRequired,
spanWidth: number.isRequired,
}),
}
export default connect(null, {
const mdtp = {
deleteAnnotationAsync: actions.deleteAnnotationAsync,
updateAnnotationAsync: actions.updateAnnotationAsync,
})(AnnotationTooltip)
}
export default connect(null, mdtp)(AnnotationTooltip)

View File

@ -1,14 +1,23 @@
import React from 'react'
import PropTypes from 'prop-types'
import {
DYGRAPH_CONTAINER_H_MARGIN,
DYGRAPH_CONTAINER_V_MARGIN,
DYGRAPH_CONTAINER_XLABEL_MARGIN,
} from 'shared/constants'
import * as schema from 'shared/schemas'
} from 'src/shared/constants'
import {AnnotationInterface, DygraphClass} from 'src/types'
const windowDimensions = (anno, dygraph, staticLegendHeight) => {
interface WindowDimensionsReturn {
left: string
width: string
height: string
}
const windowDimensions = (
anno: AnnotationInterface,
dygraph: DygraphClass,
staticLegendHeight: number
): WindowDimensionsReturn => {
// TODO: export and test this function
const [startX, endX] = dygraph.xAxisRange()
const startTime = Math.max(+anno.startTime, startX)
@ -34,25 +43,23 @@ const windowDimensions = (anno, dygraph, staticLegendHeight) => {
}
}
interface AnnotationWindowProps {
annotation: AnnotationInterface
dygraph: DygraphClass
active: boolean
staticLegendHeight: number
}
const AnnotationWindow = ({
annotation,
dygraph,
active,
staticLegendHeight,
}) => (
}: AnnotationWindowProps): JSX.Element => (
<div
className={`annotation-window${active ? ' active' : ''}`}
style={windowDimensions(annotation, dygraph, staticLegendHeight)}
/>
)
const {bool, number, shape} = PropTypes
AnnotationWindow.propTypes = {
annotation: schema.annotation.isRequired,
dygraph: shape({}).isRequired,
staticLegendHeight: number,
active: bool,
}
export default AnnotationWindow

View File

@ -3,6 +3,7 @@ import {connect} from 'react-redux'
import Annotation from 'src/shared/components/Annotation'
import NewAnnotation from 'src/shared/components/NewAnnotation'
import {SourceContext} from 'src/CheckSources'
import {ADDING, TEMP_ANNOTATION} from 'src/shared/annotations/helpers'
@ -16,7 +17,7 @@ import {
import {visibleAnnotations} from 'src/shared/annotations/helpers'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AnnotationInterface, DygraphClass} from 'src/types'
import {AnnotationInterface, DygraphClass, Source} from 'src/types'
import {UpdateAnnotationAction} from 'src/shared/actions/annotations'
interface Props {
@ -56,17 +57,22 @@ class Annotations extends Component<Props> {
<div className="annotations-container">
{mode === ADDING &&
this.tempAnnotation && (
<NewAnnotation
dygraph={dygraph}
isTempHovering={isTempHovering}
tempAnnotation={this.tempAnnotation}
staticLegendHeight={staticLegendHeight}
onUpdateAnnotation={handleUpdateAnnotation}
onDismissAddingAnnotation={handleDismissAddingAnnotation}
onAddingAnnotationSuccess={handleAddingAnnotationSuccess}
onMouseEnterTempAnnotation={handleMouseEnterTempAnnotation}
onMouseLeaveTempAnnotation={handleMouseLeaveTempAnnotation}
/>
<SourceContext.Consumer>
{(source: Source) => (
<NewAnnotation
dygraph={dygraph}
source={source}
isTempHovering={isTempHovering}
tempAnnotation={this.tempAnnotation}
staticLegendHeight={staticLegendHeight}
onUpdateAnnotation={handleUpdateAnnotation}
onDismissAddingAnnotation={handleDismissAddingAnnotation}
onAddingAnnotationSuccess={handleAddingAnnotationSuccess}
onMouseEnterTempAnnotation={handleMouseEnterTempAnnotation}
onMouseLeaveTempAnnotation={handleMouseLeaveTempAnnotation}
/>
)}
</SourceContext.Consumer>
)}
{this.annotations.map(a => (
<Annotation

View File

@ -1,6 +1,5 @@
import React, {PureComponent, ChangeEvent} from 'react'
import {connect} from 'react-redux'
import Dygraph from 'dygraphs'
import _ from 'lodash'
import classnames from 'classnames'
@ -13,22 +12,19 @@ import DygraphLegendSort from 'src/shared/components/DygraphLegendSort'
import {makeLegendStyles, removeMeasurement} from 'src/shared/graphs/helpers'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {NO_CELL} from 'src/shared/constants'
interface ExtendedDygraph extends Dygraph {
graphDiv: HTMLElement
}
import {DygraphClass} from 'src/types'
interface Props {
dygraph: ExtendedDygraph
dygraph: DygraphClass
cellID: string
onHide: () => void
onShow: (MouseEvent) => void
onShow: (e: MouseEvent) => void
activeCellID: string
setActiveCell: (cellID: string) => void
}
interface LegendData {
x: string | null
x: number
series: SeriesLegendData[]
xHTML: string
}
@ -49,7 +45,7 @@ interface State {
class DygraphLegend extends PureComponent<Props, State> {
private legendRef: HTMLElement | null = null
constructor(props) {
constructor(props: Props) {
super(props)
this.props.dygraph.updateOptions({
@ -199,7 +195,7 @@ class DygraphLegend extends PureComponent<Props, State> {
this.setState({filterText})
}
private handleSortLegend = sortType => () => {
private handleSortLegend = (sortType: string) => () => {
this.setState({sortType, isAscending: !this.state.isAscending})
}
@ -209,7 +205,7 @@ class DygraphLegend extends PureComponent<Props, State> {
this.props.onShow(e)
}
private legendFormatter = legend => {
private legendFormatter = (legend: LegendData) => {
if (!legend.x) {
return ''
}
@ -229,7 +225,7 @@ class DygraphLegend extends PureComponent<Props, State> {
return ''
}
private unhighlightCallback = e => {
private unhighlightCallback = (e: MouseEvent) => {
const {top, bottom, left, right} = this.legendRef.getBoundingClientRect()
const mouseY = e.clientY

View File

@ -1,120 +1,47 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {Component, MouseEvent} from 'react'
import classnames from 'classnames'
import {connect} from 'react-redux'
import uuid from 'uuid'
import OnClickOutside from 'shared/components/OnClickOutside'
import AnnotationWindow from 'shared/components/AnnotationWindow'
import * as schema from 'shared/schemas'
import * as actions from 'shared/actions/annotations'
import OnClickOutside from 'src/shared/components/OnClickOutside'
import AnnotationWindow from 'src/shared/components/AnnotationWindow'
import * as actions from 'src/shared/actions/annotations'
import {DYGRAPH_CONTAINER_XLABEL_MARGIN} from 'shared/constants'
import {DYGRAPH_CONTAINER_XLABEL_MARGIN} from 'src/shared/constants'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AnnotationInterface, DygraphClass, Source} from 'src/types'
interface Props {
dygraph: DygraphClass
source: Source
isTempHovering: boolean
tempAnnotation: AnnotationInterface
addAnnotationAsync: (url: string, a: AnnotationInterface) => void
onDismissAddingAnnotation: () => void
onAddingAnnotationSuccess: () => void
onUpdateAnnotation: (a: AnnotationInterface) => void
onMouseEnterTempAnnotation: () => void
onMouseLeaveTempAnnotation: () => void
staticLegendHeight: number
}
interface State {
isMouseOver: boolean
gatherMode: string
}
@ErrorHandling
class NewAnnotation extends Component {
state = {
isMouseOver: false,
gatherMode: 'startTime',
}
clampWithinGraphTimerange = timestamp => {
const [xRangeStart] = this.props.dygraph.xAxisRange()
return Math.max(xRangeStart, timestamp)
}
eventToTimestamp = ({pageX: pxBetweenMouseAndPage}) => {
const {left: pxBetweenGraphAndPage} = this.wrapper.getBoundingClientRect()
const graphXCoordinate = pxBetweenMouseAndPage - pxBetweenGraphAndPage
const timestamp = this.props.dygraph.toDataXCoord(graphXCoordinate)
const clamped = this.clampWithinGraphTimerange(timestamp)
return `${clamped}`
}
handleMouseDown = e => {
const startTime = this.eventToTimestamp(e)
this.props.onUpdateAnnotation({...this.props.tempAnnotation, startTime})
this.setState({gatherMode: 'endTime'})
}
handleMouseMove = e => {
if (this.props.isTempHovering === false) {
return
}
const {tempAnnotation, onUpdateAnnotation} = this.props
const newTime = this.eventToTimestamp(e)
if (this.state.gatherMode === 'startTime') {
onUpdateAnnotation({
...tempAnnotation,
startTime: newTime,
endTime: newTime,
})
} else {
onUpdateAnnotation({...tempAnnotation, endTime: newTime})
}
}
handleMouseUp = e => {
const {
tempAnnotation,
onUpdateAnnotation,
addAnnotationAsync,
onAddingAnnotationSuccess,
onMouseLeaveTempAnnotation,
} = this.props
const createUrl = this.context.source.links.annotations
const upTime = this.eventToTimestamp(e)
const downTime = tempAnnotation.startTime
const [startTime, endTime] = [downTime, upTime].sort()
const newAnnotation = {...tempAnnotation, startTime, endTime}
onUpdateAnnotation(newAnnotation)
addAnnotationAsync(createUrl, {...newAnnotation, id: uuid.v4()})
onAddingAnnotationSuccess()
onMouseLeaveTempAnnotation()
this.setState({
class NewAnnotation extends Component<Props, State> {
public wrapperRef: React.RefObject<HTMLDivElement>
constructor(props: Props) {
super(props)
this.wrapperRef = React.createRef<HTMLDivElement>()
this.state = {
isMouseOver: false,
gatherMode: 'startTime',
})
}
handleMouseOver = e => {
this.setState({isMouseOver: true})
this.handleMouseMove(e)
this.props.onMouseEnterTempAnnotation()
}
handleMouseLeave = () => {
this.setState({isMouseOver: false})
this.props.onMouseLeaveTempAnnotation()
}
handleClickOutside = () => {
const {onDismissAddingAnnotation, isTempHovering} = this.props
if (!isTempHovering) {
onDismissAddingAnnotation()
}
}
renderTimestamp(time) {
const timestamp = `${new Date(+time)}`
return (
<div className="new-annotation-tooltip">
<span className="new-annotation-helper">Click or Drag to Annotate</span>
<span className="new-annotation-timestamp">{timestamp}</span>
</div>
)
}
render() {
public render() {
const {
dygraph,
isTempHovering,
@ -123,7 +50,6 @@ class NewAnnotation extends Component {
staticLegendHeight,
} = this.props
const {isMouseOver} = this.state
const crosshairOne = Math.max(-1000, dygraph.toDomXCoord(startTime))
const crosshairTwo = dygraph.toDomXCoord(endTime)
const crosshairHeight = `calc(100% - ${staticLegendHeight +
@ -154,7 +80,7 @@ class NewAnnotation extends Component {
className={classnames('new-annotation', {
hover: isTempHovering,
})}
ref={el => (this.wrapper = el)}
ref={this.wrapperRef}
onMouseMove={this.handleMouseMove}
onMouseOver={this.handleMouseOver}
onMouseLeave={this.handleMouseLeave}
@ -185,29 +111,98 @@ class NewAnnotation extends Component {
</div>
)
}
}
const {bool, func, number, shape, string} = PropTypes
private clampWithinGraphTimerange = (timestamp: number): number => {
const [xRangeStart] = this.props.dygraph.xAxisRange()
return Math.max(xRangeStart, timestamp)
}
NewAnnotation.contextTypes = {
source: shape({
links: shape({
annotations: string,
}),
}),
}
private eventToTimestamp = ({
pageX: pxBetweenMouseAndPage,
}: MouseEvent<HTMLDivElement>): number => {
const {
left: pxBetweenGraphAndPage,
} = this.wrapperRef.current.getBoundingClientRect()
const graphXCoordinate = pxBetweenMouseAndPage - pxBetweenGraphAndPage
const timestamp = this.props.dygraph.toDataXCoord(graphXCoordinate)
const clamped = this.clampWithinGraphTimerange(timestamp)
return clamped
}
NewAnnotation.propTypes = {
dygraph: shape({}).isRequired,
isTempHovering: bool,
tempAnnotation: schema.annotation.isRequired,
addAnnotationAsync: func.isRequired,
onDismissAddingAnnotation: func.isRequired,
onAddingAnnotationSuccess: func.isRequired,
onUpdateAnnotation: func.isRequired,
onMouseEnterTempAnnotation: func.isRequired,
onMouseLeaveTempAnnotation: func.isRequired,
staticLegendHeight: number,
private handleMouseDown = (e: MouseEvent<HTMLDivElement>) => {
const startTime = this.eventToTimestamp(e)
this.props.onUpdateAnnotation({...this.props.tempAnnotation, startTime})
this.setState({gatherMode: 'endTime'})
}
private handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
if (this.props.isTempHovering === false) {
return
}
const {tempAnnotation, onUpdateAnnotation} = this.props
const newTime = this.eventToTimestamp(e)
if (this.state.gatherMode === 'startTime') {
onUpdateAnnotation({
...tempAnnotation,
startTime: newTime,
endTime: newTime,
})
} else {
onUpdateAnnotation({...tempAnnotation, endTime: newTime})
}
}
private handleMouseUp = (e: MouseEvent<HTMLDivElement>) => {
const {
tempAnnotation,
onUpdateAnnotation,
addAnnotationAsync,
onAddingAnnotationSuccess,
onMouseLeaveTempAnnotation,
source,
} = this.props
const createUrl = source.links.annotations
const upTime = this.eventToTimestamp(e)
const downTime = tempAnnotation.startTime
const [startTime, endTime] = [downTime, upTime].sort()
const newAnnotation = {...tempAnnotation, startTime, endTime}
onUpdateAnnotation(newAnnotation)
addAnnotationAsync(createUrl, {...newAnnotation, id: uuid.v4()})
onAddingAnnotationSuccess()
onMouseLeaveTempAnnotation()
this.setState({
isMouseOver: false,
gatherMode: 'startTime',
})
}
private handleMouseOver = (e: MouseEvent<HTMLDivElement>) => {
this.setState({isMouseOver: true})
this.handleMouseMove(e)
this.props.onMouseEnterTempAnnotation()
}
private handleMouseLeave = () => {
this.setState({isMouseOver: false})
this.props.onMouseLeaveTempAnnotation()
}
private renderTimestamp(time: number): JSX.Element {
const timestamp = `${new Date(time)}`
return (
<div className="new-annotation-tooltip">
<span className="new-annotation-helper">Click or Drag to Annotate</span>
<span className="new-annotation-timestamp">{timestamp}</span>
</div>
)
}
}
const mdtp = {

View File

@ -1,7 +1,7 @@
export interface AnnotationInterface {
id: string
startTime: string
endTime: string
startTime: number
endTime: number
text: string
type: string
}

View File

@ -495,6 +495,7 @@ export declare class DygraphClass {
// tslint:disable-next-line:variable-name
public width_: number
public graphDiv: HTMLElement
constructor(
container: HTMLElement | string,