Convert Annotation sub-components to TS
parent
bb091cb94e
commit
1d3dda6667
|
@ -12,7 +12,7 @@ interface State {
|
|||
|
||||
@ErrorHandling
|
||||
class SearchBar extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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,
|
|
@ -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 = {
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
|
@ -1,7 +1,7 @@
|
|||
export interface AnnotationInterface {
|
||||
id: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
startTime: number
|
||||
endTime: number
|
||||
text: string
|
||||
type: string
|
||||
}
|
||||
|
|
|
@ -495,6 +495,7 @@ export declare class DygraphClass {
|
|||
|
||||
// tslint:disable-next-line:variable-name
|
||||
public width_: number
|
||||
public graphDiv: HTMLElement
|
||||
|
||||
constructor(
|
||||
container: HTMLElement | string,
|
||||
|
|
Loading…
Reference in New Issue