Skip to content

Commit ab29128

Browse files
committed
fix(webui): More fixes [copilot]
1 parent 57e0f40 commit ab29128

4 files changed

Lines changed: 30 additions & 16 deletions

File tree

packages/webui/src/client/lib/VirtualElement.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ export function VirtualElement({
7676
const isTransitioning = useRef(false)
7777

7878
const isCurrentlyObserving = useRef(false)
79+
const isMountedRef = useRef(true)
80+
81+
useEffect(() => {
82+
return () => {
83+
isMountedRef.current = false
84+
}
85+
}, [])
7986

8087
const styleObj = useMemo<React.CSSProperties>(
8188
() => ({
@@ -91,11 +98,13 @@ export function VirtualElement({
9198
)
9299

93100
const handleResize = useCallback(() => {
101+
if (!isMountedRef.current) return
94102
if (ref) {
95103
// Show children during measurement
96104
setIsShowingChildren(true)
97105

98106
requestAnimationFrame(() => {
107+
if (!isMountedRef.current) return
99108
const measurements = measureElement(ref, placeholderHeight)
100109
if (measurements) {
101110
setMeasurements(measurements)
@@ -296,6 +305,7 @@ export function VirtualElement({
296305
}
297306
idleCallback = window.requestIdleCallback(
298307
() => {
308+
if (!isMountedRef.current) return
299309
// Measure the entire wrapper element instead of just the childRef
300310
if (ref) {
301311
const measurements = measureElement(ref, placeholderHeight)
@@ -495,6 +505,7 @@ export class ElementObserverManager {
495505
this.observedElements.delete(element)
496506
this.resizeObserver.unobserve(element)
497507
this.pruneDetachedObservedElements()
508+
this.mutationObserver.disconnect()
498509

499510
if (this.observedElements.size === 0) {
500511
if (this.pendingReconnectFrame) {
@@ -511,7 +522,6 @@ export class ElementObserverManager {
511522
this.pendingReconnectFrame = undefined
512523

513524
// MutationObserver has no per-element unobserve, so we reconnect once per frame.
514-
this.mutationObserver.disconnect()
515525
this.pruneDetachedObservedElements()
516526

517527
if (this.observedElements.size === 0) {
@@ -520,6 +530,7 @@ export class ElementObserverManager {
520530
}
521531

522532
this.observedElements.forEach((_, el) => {
533+
if (!document.contains(el)) return
523534
this.mutationObserver.observe(el, {
524535
childList: true,
525536
subtree: true,

packages/webui/src/client/ui/ClockView/CameraScreen/index.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,13 @@ export function CameraScreen({ playlist, studioId }: Readonly<IProps>): JSX.Elem
148148

149149
useEffect(() => {
150150
const getContainerEl = () => document.querySelector('#render-target > .container-fluid')
151-
const containerEl = getContainerEl()
152-
if (containerEl instanceof HTMLElement) containerEl.classList.remove('header-clear')
151+
const currentContainerEl = getContainerEl()
152+
if (currentContainerEl instanceof HTMLElement) currentContainerEl.classList.remove('header-clear')
153153

154154
return () => {
155-
if (containerEl instanceof HTMLElement && containerEl.isConnected) {
156-
containerEl.classList.add('header-clear')
157-
return
158-
}
159-
160-
const currentContainerEl = getContainerEl()
161-
if (currentContainerEl instanceof HTMLElement) {
162-
currentContainerEl.classList.add('header-clear')
155+
const cleanupContainerEl = getContainerEl()
156+
if (cleanupContainerEl instanceof HTMLElement) {
157+
cleanupContainerEl.classList.add('header-clear')
163158
}
164159
}
165160
}, [])

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const PreviewPopUp = React.forwardRef<
6262
anchor?.getBoundingClientRect().y ?? 0
6363
),
6464
})
65+
const anchorYRef = useRef(anchor?.getBoundingClientRect().y ?? 0)
6566
const { styles, attributes, update } = usePopper(
6667
trackMouse ? virtualElement.current : anchor,
6768
popperEl,
@@ -74,13 +75,14 @@ export const PreviewPopUp = React.forwardRef<
7475
updateRef.current = update
7576
}, [update])
7677

78+
useEffect(() => {
79+
anchorYRef.current = anchor?.getBoundingClientRect().y ?? 0
80+
}, [anchor])
81+
7782
useEffect(() => {
7883
if (trackMouse) {
7984
const listener = ({ clientX: x }: MouseEvent) => {
80-
virtualElement.current.getBoundingClientRect = generateGetBoundingClientRect(
81-
x,
82-
anchor?.getBoundingClientRect().y ?? 0
83-
)
85+
virtualElement.current.getBoundingClientRect = generateGetBoundingClientRect(x, anchorYRef.current)
8486
// If update is available, call it to reposition the popper:
8587
if (updateRef.current) {
8688
updateRef.current().catch((e) => console.error(e))
@@ -92,7 +94,7 @@ export const PreviewPopUp = React.forwardRef<
9294
document.removeEventListener('mousemove', listener)
9395
}
9496
}
95-
}, [trackMouse, anchor])
97+
}, [trackMouse])
9698

9799
useImperativeHandle(ref, () => {
98100
return {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ const SegmentTimelineContainerContent = withResolvedSegment(
151151
intersectionObserver: IntersectionObserver | undefined
152152
mountedTime = 0
153153
nextPartOffset = 0
154+
isUnmounted = false
154155

155156
constructor(props: IProps & ITrackedResolvedSegmentProps) {
156157
super(props)
@@ -185,6 +186,7 @@ const SegmentTimelineContainerContent = withResolvedSegment(
185186
}
186187

187188
componentDidMount(): void {
189+
this.isUnmounted = false
188190
SpeechSynthesiser.init()
189191

190192
this.rundownCurrentPartInstanceId = this.props.playlist.currentPartInfo?.partInstanceId ?? null
@@ -353,6 +355,7 @@ const SegmentTimelineContainerContent = withResolvedSegment(
353355
}
354356

355357
componentWillUnmount(): void {
358+
this.isUnmounted = true
356359
if (this.intersectionObserver && this.state.isLiveSegment && this.props.followLiveSegments) {
357360
if (typeof this.props.onSegmentScroll === 'function') this.props.onSegmentScroll()
358361
}
@@ -512,6 +515,7 @@ const SegmentTimelineContainerContent = withResolvedSegment(
512515
}
513516

514517
onAirLineRefresh = (e: TimingEvent) => {
518+
if (this.isUnmounted) return
515519
this.setState((state) => {
516520
if (state.isLiveSegment && state.currentLivePart) {
517521
const currentLivePartInstance = state.currentLivePart.instance
@@ -555,12 +559,14 @@ const SegmentTimelineContainerContent = withResolvedSegment(
555559
}
556560

557561
visibleChanged = (entries: IntersectionObserverEntry[]) => {
562+
if (this.isUnmounted) return
558563
// Add a small debounce to ensure UI has settled before checking
559564
if (this.visibilityChangeTimeout) {
560565
clearTimeout(this.visibilityChangeTimeout)
561566
}
562567

563568
this.visibilityChangeTimeout = setTimeout(() => {
569+
if (this.isUnmounted) return
564570
if (entries[0].intersectionRatio < 0.99 && !isMaintainingFocus() && Date.now() - this.mountedTime > 2000) {
565571
if (typeof this.props.onSegmentScroll === 'function') this.props.onSegmentScroll()
566572
this.isVisible = false

0 commit comments

Comments
 (0)