import React, { Component } from 'react' // import { Link } from 'react-router-dom' // import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import actions from 'app/actions' import { toArray, floatInRange } from 'app/utils' import oktween from 'app/utils/oktween' import ParagraphList from 'app/views/paragraph/components/paragraph.list' import { transcriptElementLookup } from './components' class Transcript extends Component { state = { built: false, paragraphs: [], windowHeight: 0.0, autoscrolling: false, } constructor(props){ super(props) this.handleClose = this.handleClose.bind(this) this.handleAnnotationClick = this.handleAnnotationClick.bind(this) this.handleParagraphDoubleClick = this.handleParagraphDoubleClick.bind(this) this.handleMouseWheel = this.handleMouseWheel.bind(this) this.containerRef = React.createRef() this.lastScroll = 0 } componentDidMount() { actions.transcript.buildAllParagraphs() this.setState({ built: true, windowHeight: window.innerHeight * 0.6 - 48, }) } componentDidUpdate(prevProps, prevState) { if (this.state.built !== prevState.built) { setTimeout(() => { this.cacheScrollPositions() }, 1000) } if (!this.state.autoscrolling) { if (this.props.viewer.currentSection !== prevProps.viewer.currentSection) { this.updateScrollPosition(this.props.viewer.currentSection.start_ts, true) } else { this.updateScrollPosition(this.props.play_ts, false) } } } cacheScrollPositions() { let paragraphs = toArray(this.containerRef.current.querySelectorAll('.content > div')).map(el => { const isHeading = el.classList.contains('section_heading') const isMedia = el.dataset.media === 'true' const scrollTop = isHeading ? el.offsetTop : el.offsetTop - 16 // 1.5rem const start_ts = parseFloat(el.dataset.startts) let end_ts = parseFloat(el.dataset.endts) if (!start_ts || !end_ts) return null if (end_ts < start_ts) { end_ts = start_ts + 0.5 } return { start_ts, end_ts, scrollTop } }).filter(el => !!el) this.setState({ paragraphs }) } updateScrollPosition(play_ts, forceScroll) { if (this.state.autoscrolling) return const { paragraphs, windowHeight, currentParagraph } = this.state const scrollTop = this.containerRef.current.scrollTop const now = Date.now() if (!forceScroll && currentParagraph && floatInRange(currentParagraph.start_ts, play_ts, currentParagraph.end_ts)) { return } let nextParagraph; const insideParagraph = paragraphs.some(paragraph => { if (floatInRange(paragraph.start_ts, play_ts, paragraph.end_ts)) { nextParagraph = paragraph return true } return false }) // the paragraph updated! if (insideParagraph && nextParagraph) { // first check if we need to scroll. // if we're not being forced and the next paragraph is in view, dont scroll if (!forceScroll && floatInRange(scrollTop, nextParagraph.scrollTop, scrollTop + windowHeight)) { this.setState({ currentParagraph: nextParagraph }) return } // ok, so we wanna scroll. // is the transcript closed? then we can scroll immediately! if (!this.props.viewer.transcript) { this.setState({ currentParagraph: nextParagraph }) this.scrollImmediate(nextParagraph.scrollTop) return } // console.log(nextParagraph.scrollTop) // if the last scroll event was within a second ago, suppress auto-scroll to avoid it bouncing around if ((now - this.lastScroll) < 1200) { this.setState({ currentParagraph: nextParagraph }) return } // before i was checking this criteria.. if the distance we wanna scroll is less than a window height // Math.abs(scrollTop - nextParagraph.scrollTop) < this.state.windowHeight this.lastScroll = now this.scrollToParagraph(scrollTop, nextParagraph.scrollTop) this.setState({ currentParagraph: nextParagraph, autoscrolling: true }) } } scrollToParagraph(scrollFrom, scrollTo) { // suppress if already scrolling if (this.state.autoscrolling) return // console.log('autoscrolling!', scrollFrom, scrollTo) oktween.add({ from: { scrollTop: scrollFrom }, to: { scrollTop: scrollTo }, duration: 1000, easing: oktween.easing.quad_in_out, update: obj => { this.scrollImmediate(obj.scrollTop) this.containerRef.current.scrollTo(0, obj.scrollTop) }, finished: tween => { this.scrollImmediate(tween.obj.scrollTop) this.setState({ autoscrolling: false }) } }) } scrollImmediate(pos) { this.containerRef.current.scrollTo(0, pos) } handleAnnotationClick(e, paragraph, annotation) { if (annotation.type === 'footnote') { return actions.viewer.openFootnote(annotation) } actions.viewer.seekToTimestamp(annotation.start_ts) this.updateScrollPosition(annotation.start_ts + 0.1, annotation.type === 'section_heading') } handleParagraphDoubleClick(e, paragraph) { return } handleClose() { actions.viewer.hideComponent('transcript') } handleMouseWheel(e) { e.stopPropagation() this.lastScroll = Date.now() } render() { const { viewer, paragraphs } = this.props // console.log(paragraphs.map(p => p.annotations.filter(a => a.type === 'footnote')).filter(p => p.length)) return (
) } } /* Download PDF */ const mapStateToProps = state => ({ viewer: state.viewer, play_ts: state.audio.play_ts, paragraphs: state.paragraph.paragraphs, }) const mapDispatchToProps = dispatch => ({ // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), }) export default connect(mapStateToProps, mapDispatchToProps)(Transcript)