Skip to content

Commit 20160e8

Browse files
authored
fix: segment scrolling (#1681)
* fix: unable to scroll segment once past expected duration * chore: convert to functional component
2 parents ba286b8 + d3e9fb2 commit 20160e8

File tree

3 files changed

+60
-105
lines changed

3 files changed

+60
-105
lines changed

packages/webui/src/client/lib/rundownTiming.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import { Settings } from '../lib/Settings.js'
2525
import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
2626
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
2727
import { CountdownType } from '@sofie-automation/blueprints-integration'
28-
import { isLoopDefined, isEntirePlaylistLooping, isLoopRunning } from '../lib/RundownResolver.js'
28+
import { isLoopDefined, isEntirePlaylistLooping, isLoopRunning, PartExtended } from '../lib/RundownResolver.js'
29+
import { RundownUtils } from './rundown.js'
2930

3031
// Minimum duration that a part can be assigned. Used by gap parts to allow them to "compress" to indicate time running out.
3132
const MINIMAL_NONZERO_DURATION = 1
@@ -784,23 +785,21 @@ export interface RundownTimingContext {
784785
*/
785786
export function computeSegmentDuration(
786787
timingDurations: RundownTimingContext,
787-
partIds: PartId[],
788+
parts: PartExtended[],
788789
display?: boolean
789790
): number {
790-
const partDurations = timingDurations.partDurations
791-
792-
if (partDurations === undefined) return 0
793-
794-
return partIds.reduce((memo, partId) => {
795-
const pId = unprotectString(partId)
796-
let partDuration = 0
797-
if (partDurations && partDurations[pId] !== undefined) {
798-
partDuration = partDurations[pId]
799-
}
800-
if (!partDuration && display) {
801-
partDuration = Settings.defaultDisplayDuration
802-
}
803-
return memo + partDuration
791+
const partDisplayDurations = timingDurations?.partDisplayDurations
792+
793+
if (!partDisplayDurations) return RundownUtils.getSegmentDuration(parts, display)
794+
795+
return parts.reduce((memo, partExtended) => {
796+
// total += durations.partDurations ? durations.partDurations[item._id] : (item.duration || item.renderedDuration || 1)
797+
const partInstanceTimingId = getPartInstanceTimingId(partExtended.instance)
798+
const duration = Math.max(
799+
partExtended.instance.timings?.duration || partExtended.renderedDuration || 0,
800+
partDisplayDurations?.[partInstanceTimingId] || (display ? Settings.defaultDisplayDuration : 0)
801+
)
802+
return memo + duration
804803
}, 0)
805804
}
806805

packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx

Lines changed: 44 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react'
1+
import React, { useState, useRef, useEffect } from 'react'
22
import { WithTranslation, withTranslation } from 'react-i18next'
33

44
import ClassNames from 'classnames'
@@ -36,7 +36,7 @@ import { wrapPartToTemporaryInstance } from '@sofie-automation/meteor-lib/dist/c
3636

3737
import { SegmentTimelineSmallPartFlag } from './SmallParts/SegmentTimelineSmallPartFlag.js'
3838
import { UIStateStorage } from '../../lib/UIStateStorage.js'
39-
import { getPartInstanceTimingId, RundownTimingContext } from '../../lib/rundownTiming.js'
39+
import { computeSegmentDuration, getPartInstanceTimingId, RundownTimingContext } from '../../lib/rundownTiming.js'
4040
import { IOutputLayer, ISourceLayer, NoteSeverity, UserEditingType } from '@sofie-automation/blueprints-integration'
4141
import { SegmentTimelineZoomButtons } from './SegmentTimelineZoomButtons.js'
4242
import { SegmentViewMode } from '../SegmentContainer/SegmentViewModes.js'
@@ -51,7 +51,6 @@ import {
5151
TimingTickResolution,
5252
TimingDataResolution,
5353
WithTiming,
54-
RundownTimingProviderContext,
5554
} from '../RundownView/RundownTiming/withTiming.js'
5655
import { SegmentTimeAnchorTime } from '../RundownView/RundownTiming/SegmentTimeAnchorTime.js'
5756
import { logger } from '../../lib/logging.js'
@@ -121,100 +120,61 @@ interface IStateHeader {
121120
// isSelected: boolean
122121
}
123122

124-
interface IZoomPropsHeader {
123+
interface SegmentTimelineZoomProps extends IProps {
125124
onZoomDblClick: (e: React.MouseEvent) => void
126125
timelineWidth: number
126+
timingDurations: RundownTimingContext
127127
}
128-
interface IZoomStateHeader {
129-
totalSegmentDuration: number
128+
129+
function computeSegmentDurationFromProps(props: SegmentTimelineZoomProps): number {
130+
return computeSegmentDuration(props.timingDurations, props.parts, true)
130131
}
131132

132-
const SegmentTimelineZoom = class SegmentTimelineZoom extends React.Component<
133-
IProps & IZoomPropsHeader,
134-
IZoomStateHeader
135-
> {
136-
static contextType = RundownTimingProviderContext
137-
declare context: React.ContextType<typeof RundownTimingProviderContext>
133+
function SegmentTimelineZoom(props: SegmentTimelineZoomProps): JSX.Element {
134+
const [totalSegmentDuration, setTotalSegmentDuration] = useState(() => computeSegmentDurationFromProps(props))
138135

139-
constructor(props: IProps & IZoomPropsHeader, context: any) {
140-
super(props, context)
141-
this.state = {
142-
totalSegmentDuration: 10,
143-
}
144-
}
136+
// Store the props into a ref so that the checkTimingChange can access the latest props without needing to be re-created on every render
137+
const propsRef = useRef(props)
138+
propsRef.current = props
145139

146-
componentDidMount(): void {
147-
this.checkTimingChange()
148-
window.addEventListener(RundownTiming.Events.timeupdateHighResolution, this.onTimeupdate)
149-
}
150-
151-
componentWillUnmount(): void {
152-
window.removeEventListener(RundownTiming.Events.timeupdateHighResolution, this.onTimeupdate)
153-
}
154-
155-
onTimeupdate = () => {
156-
if (!this.props.isLiveSegment) {
157-
this.checkTimingChange()
158-
}
159-
}
160-
161-
checkTimingChange = () => {
162-
const total = this.calculateSegmentDuration()
163-
if (total !== this.state.totalSegmentDuration) {
164-
this.setState({
165-
totalSegmentDuration: total,
166-
})
140+
useEffect(() => {
141+
const onTimeupdate = () => {
142+
if (!propsRef.current.isLiveSegment) {
143+
setTotalSegmentDuration(computeSegmentDurationFromProps(propsRef.current))
144+
}
167145
}
168-
}
169146

170-
calculateSegmentDuration(): number {
171-
let total = 0
172-
if (this.context?.durations) {
173-
const durations = this.context.durations
174-
this.props.parts.forEach((partExtended) => {
175-
// total += durations.partDurations ? durations.partDurations[item._id] : (item.duration || item.renderedDuration || 1)
176-
const partInstanceTimingId = getPartInstanceTimingId(partExtended.instance)
177-
const duration = Math.max(
178-
partExtended.instance.timings?.duration || partExtended.renderedDuration || 0,
179-
durations.partDisplayDurations?.[partInstanceTimingId] || Settings.defaultDisplayDuration
180-
)
181-
total += duration
182-
})
183-
} else {
184-
total = RundownUtils.getSegmentDuration(this.props.parts, true)
147+
window.addEventListener(RundownTiming.Events.timeupdateHighResolution, onTimeupdate)
148+
return () => {
149+
window.removeEventListener(RundownTiming.Events.timeupdateHighResolution, onTimeupdate)
185150
}
186-
return total
187-
}
151+
}, [])
188152

189-
getSegmentDuration(): number {
190-
return this.props.isLiveSegment ? this.calculateSegmentDuration() : this.state.totalSegmentDuration
191-
}
153+
const segmentDuration = props.isLiveSegment ? computeSegmentDurationFromProps(props) : totalSegmentDuration
192154

193-
render(): JSX.Element {
194-
return (
195-
<div
196-
className={ClassNames('segment-timeline__zoom-area-container', {
197-
hidden:
198-
this.props.scrollLeft === 0 &&
199-
(this.props.showingAllSegment || this.props.timeScale === this.props.maxTimeScale) &&
200-
!this.props.isLiveSegment,
201-
})}
202-
>
203-
<div className="segment-timeline__zoom-area" onDoubleClick={(e) => this.props.onZoomDblClick(e)}>
204-
<SegmentTimelineZoomControls
205-
scrollLeft={this.props.scrollLeft}
206-
scrollWidth={this.props.timelineWidth / this.props.timeScale}
207-
onScroll={this.props.onScroll}
208-
segmentDuration={this.getSegmentDuration()}
209-
liveLineHistorySize={this.props.liveLineHistorySize}
210-
timeScale={this.props.timeScale}
211-
maxTimeScale={this.props.maxTimeScale}
212-
onZoomChange={this.props.onZoomChange}
213-
/>
214-
</div>
155+
return (
156+
<div
157+
className={ClassNames('segment-timeline__zoom-area-container', {
158+
hidden:
159+
props.scrollLeft === 0 &&
160+
(props.showingAllSegment || props.timeScale === props.maxTimeScale) &&
161+
!props.isLiveSegment,
162+
})}
163+
>
164+
<div className="segment-timeline__zoom-area" onDoubleClick={props.onZoomDblClick}>
165+
<SegmentTimelineZoomControls
166+
scrollLeft={props.scrollLeft}
167+
scrollWidth={props.timelineWidth / props.timeScale}
168+
onScroll={props.onScroll}
169+
segmentDuration={segmentDuration}
170+
liveLineHistorySize={props.liveLineHistorySize}
171+
timeScale={props.timeScale}
172+
maxTimeScale={props.maxTimeScale}
173+
onZoomChange={props.onZoomChange}
174+
/>
215175
</div>
216-
)
217-
}
176+
</div>
177+
)
218178
}
219179

220180
export const SEGMENT_TIMELINE_ELEMENT_ID = 'rundown__segment__'

packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,7 @@ const SegmentTimelineContainerContent = withResolvedSegment(
407407
0,
408408
Math.min(
409409
scrollLeft,
410-
(computeSegmentDuration(
411-
this.context.durations,
412-
this.props.parts.map((i) => i.instance.part._id),
413-
true
414-
) || 1) -
410+
(computeSegmentDuration(this.context.durations, this.props.parts, true) || 1) -
415411
LIVELINE_HISTORY_SIZE / this.state.timeScale
416412
)
417413
),

0 commit comments

Comments
 (0)