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, scrolling: 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.scrolling) { if (this.props.viewer.currentSection !== prevProps.viewer.currentSection) { this.updateScrollPosition(this.props.viewer.currentSection.start_ts, true) } else { this.updateScrollPosition(this.props.play_ts) } } } 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.scrolling) return const { paragraphs, windowHeight, currentParagraph } = this.state const scrollTop = this.containerRef.current.scrollTop 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 }) if (insideParagraph && nextParagraph) { // console.log(nextParagraph.scrollTop) if (!floatInRange(scrollTop, nextParagraph.scrollTop, scrollTop + windowHeight) || forceScroll) { const now = Date.now() // suppress double-scrolling // suppress scroll if transcript is open and we've scrolled more than one screen-length away if (!this.props.viewer.transcript || ( now - this.lastScroll > 1200 && Math.abs(scrollTop - nextParagraph.scrollTop) < this.state.windowHeight )) { this.lastScroll = now this.scrollToParagraph(scrollTop, nextParagraph.scrollTop) this.setState({ currentParagraph: nextParagraph, scrolling: true }) } else { this.setState({ currentParagraph: nextParagraph }) } } else { this.setState({ currentParagraph: nextParagraph }) } } } scrollToParagraph(scrollFrom, scrollTo) { // suppress if already scrolling if (this.state.scrolling) return // console.log('scrolling!', scrollFrom, scrollTo) oktween.add({ from: { scrollTop: scrollFrom }, to: { scrollTop: scrollTo }, duration: 1000, easing: oktween.easing.quad_in_out, update: obj => { this.containerRef.current.scrollTo(0, obj.scrollTop) }, finished: tween => { this.containerRef.current.scrollTo(0, tween.obj.scrollTop) this.setState({ scrolling: false }) } }) } 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 (
) } } 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)