Spaces:
Running
Running
| import React, { forwardRef, useImperativeHandle, useState, useEffect, useRef } from 'react'; | |
| import { Section } from '../../types/Conversation'; | |
| import SectionItem from './SectionItem'; | |
| export interface ScrollingSectionsProps { | |
| sections: Section[]; | |
| onSectionChange?: (index: number) => void; | |
| displayProp: string; | |
| showProgress?: boolean; | |
| displayBeforeAndAfter?: boolean; | |
| startAtTop?: boolean; | |
| onComplete?: () => void; | |
| } | |
| export interface ScrollingSectionsRef { | |
| play: () => void; | |
| pause: () => void; | |
| goToSection: (index: number) => void; | |
| } | |
| const ScrollingSections = forwardRef<ScrollingSectionsRef, ScrollingSectionsProps>( | |
| ({ sections, onSectionChange, displayProp, showProgress = true, displayBeforeAndAfter = true, startAtTop = false, onComplete }, ref) => { | |
| const [activeIndex, setActiveIndex] = useState(0); | |
| const [isPlaying, setIsPlaying] = useState(false); | |
| const [progress, setProgress] = useState(0); | |
| const progressIntervalRef = useRef<number | null>(null); | |
| const sectionTimeoutRef = useRef<number | null>(null); | |
| const [elapsedTime, setElapsedTime] = useState(0); // in milliseconds | |
| const startProgress = () => { | |
| if (!isPlaying) return; | |
| const currentDuration = sections[activeIndex]?.duration || 10; | |
| const totalDurationMs = currentDuration * 1000; | |
| const startTime = Date.now() - elapsedTime; | |
| if (progressIntervalRef.current) { | |
| window.clearInterval(progressIntervalRef.current); | |
| } | |
| progressIntervalRef.current = window.setInterval(() => { | |
| const now = Date.now(); | |
| const newElapsed = now - startTime; | |
| const newProgress = (newElapsed / totalDurationMs) * 100; | |
| if (newProgress >= 100) { | |
| window.clearInterval(progressIntervalRef.current!); | |
| setProgress(100); | |
| setElapsedTime(0); | |
| goToNextSection(); | |
| } else { | |
| setProgress(newProgress); | |
| setElapsedTime(newElapsed); | |
| } | |
| }, 100); | |
| }; | |
| const goToNextSection = () => { | |
| setActiveIndex((prev) => (prev + 1) % sections.length); | |
| onSectionChange?.((activeIndex + 1) % sections.length); | |
| if ((activeIndex + 1) % sections.length === 0) { | |
| pause(); | |
| onComplete?.(); | |
| } | |
| }; | |
| const goToSection = (index: number) => { | |
| if (index >= 0 && index < sections.length) { | |
| setActiveIndex(index); | |
| setProgress(0); | |
| setElapsedTime(0); // reset elapsed time | |
| onSectionChange?.(index); | |
| } | |
| }; | |
| const play = () => { | |
| setIsPlaying(true); | |
| }; | |
| const pause = () => { | |
| setIsPlaying(false); | |
| if (progressIntervalRef.current) { | |
| window.clearInterval(progressIntervalRef.current); | |
| } | |
| if (sectionTimeoutRef.current) { | |
| window.clearTimeout(sectionTimeoutRef.current); | |
| } | |
| }; | |
| useImperativeHandle(ref, () => ({ | |
| play, | |
| pause, | |
| goToSection | |
| })); | |
| useEffect(() => { | |
| if (isPlaying) { | |
| startProgress(); | |
| } | |
| return () => { | |
| if (progressIntervalRef.current) { | |
| window.clearInterval(progressIntervalRef.current); | |
| } | |
| if (sectionTimeoutRef.current) { | |
| window.clearTimeout(sectionTimeoutRef.current); | |
| } | |
| }; | |
| }, [activeIndex, isPlaying]); | |
| const getSectionPosition = (index: number) => { | |
| if (index === activeIndex) return 'active'; | |
| if (index === activeIndex - 1 && displayBeforeAndAfter) return 'before'; | |
| if (index === (activeIndex + 1) % sections.length && displayBeforeAndAfter) return 'after'; | |
| return null; | |
| }; | |
| if (!sections || sections.length === 0) { | |
| return <div style={{ textAlign: 'center', padding: '1.5rem' }}>No sections available</div>; | |
| } | |
| return ( | |
| <div className="sections-container"> | |
| <div className={startAtTop ? 'sections-viewport' : 'sections-viewport topmargin'}> | |
| {sections.map((section, index) => { | |
| const position = getSectionPosition(index); | |
| if (position) { | |
| return ( | |
| <SectionItem | |
| key={index} | |
| section={section} | |
| position={position} | |
| displayProp={displayProp} | |
| /> | |
| ); | |
| } | |
| return null; | |
| })} | |
| </div> | |
| {showProgress && ( | |
| <div className="progress-container"> | |
| <div | |
| className="progress-bar" | |
| style={{ width: `${progress}%` }} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| ); | |
| export default ScrollingSections; |