Skip to content

Commit bd324b1

Browse files
committed
Revert "fix: use css optimization instead of VirtualElement"
This reverts commit 34e1349.
1 parent e1c1c26 commit bd324b1

File tree

2 files changed

+197
-2
lines changed

2 files changed

+197
-2
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'
2+
import { InView } from 'react-intersection-observer'
3+
4+
interface IElementMeasurements {
5+
width: string | number
6+
clientHeight: number
7+
marginLeft: string | number | undefined
8+
marginRight: string | number | undefined
9+
marginTop: string | number | undefined
10+
marginBottom: string | number | undefined
11+
id: string | undefined
12+
}
13+
14+
const OPTIMIZE_PERIOD = 5000
15+
const IDLE_CALLBACK_TIMEOUT = 100
16+
17+
/**
18+
* This is a component that allows optimizing the amount of elements present in the DOM through replacing them
19+
* with placeholders when they aren't visible in the viewport.
20+
*
21+
* @export
22+
* @param {(React.PropsWithChildren<{
23+
* initialShow?: boolean
24+
* placeholderHeight?: number
25+
* _debug?: boolean
26+
* placeholderClassName?: string
27+
* width?: string | number
28+
* margin?: string
29+
* id?: string | undefined
30+
* className?: string
31+
* }>)} {
32+
* initialShow,
33+
* placeholderHeight,
34+
* placeholderClassName,
35+
* width,
36+
* margin,
37+
* id,
38+
* className,
39+
* children,
40+
* }
41+
* @return {*} {(JSX.Element | null)}
42+
*/
43+
export function VirtualElement({
44+
initialShow,
45+
placeholderHeight,
46+
placeholderClassName,
47+
width,
48+
margin,
49+
id,
50+
className,
51+
children,
52+
}: React.PropsWithChildren<{
53+
initialShow?: boolean
54+
placeholderHeight?: number
55+
_debug?: boolean
56+
placeholderClassName?: string
57+
width?: string | number
58+
margin?: string
59+
id?: string | undefined
60+
className?: string
61+
}>): JSX.Element | null {
62+
const [inView, setInView] = useState(initialShow ?? false)
63+
const [isShowingChildren, setIsShowingChildren] = useState(inView)
64+
const [measurements, setMeasurements] = useState<IElementMeasurements | null>(null)
65+
const [ref, setRef] = useState<HTMLDivElement | null>(null)
66+
const [childRef, setChildRef] = useState<HTMLElement | null>(null)
67+
68+
const isMeasured = !!measurements
69+
70+
const styleObj = useMemo<React.CSSProperties>(
71+
() => ({
72+
width: width ?? measurements?.width ?? 'auto',
73+
height: (measurements?.clientHeight ?? placeholderHeight ?? '0') + 'px',
74+
marginTop: measurements?.marginTop,
75+
marginLeft: measurements?.marginLeft,
76+
marginRight: measurements?.marginRight,
77+
marginBottom: measurements?.marginBottom,
78+
}),
79+
[width, measurements, placeholderHeight]
80+
)
81+
82+
const onVisibleChanged = useCallback((visible: boolean) => {
83+
setInView(visible)
84+
}, [])
85+
86+
useEffect(() => {
87+
if (inView === true) {
88+
setIsShowingChildren(true)
89+
return
90+
}
91+
92+
let idleCallback: number | undefined
93+
const optimizeTimeout = window.setTimeout(() => {
94+
idleCallback = window.requestIdleCallback(
95+
() => {
96+
if (childRef) {
97+
setMeasurements(measureElement(childRef))
98+
}
99+
setIsShowingChildren(false)
100+
},
101+
{
102+
timeout: IDLE_CALLBACK_TIMEOUT,
103+
}
104+
)
105+
}, OPTIMIZE_PERIOD)
106+
107+
return () => {
108+
if (idleCallback) {
109+
window.cancelIdleCallback(idleCallback)
110+
}
111+
112+
window.clearTimeout(optimizeTimeout)
113+
}
114+
}, [childRef, inView])
115+
116+
const showPlaceholder = !isShowingChildren && (!initialShow || isMeasured)
117+
118+
useLayoutEffect(() => {
119+
if (!ref || showPlaceholder) return
120+
121+
const el = ref?.firstElementChild
122+
if (!el || el.classList.contains('virtual-element-placeholder') || !(el instanceof HTMLElement)) return
123+
124+
setChildRef(el)
125+
126+
let idleCallback: number | undefined
127+
const refreshSizingTimeout = window.setTimeout(() => {
128+
idleCallback = window.requestIdleCallback(
129+
() => {
130+
setMeasurements(measureElement(el))
131+
},
132+
{
133+
timeout: IDLE_CALLBACK_TIMEOUT,
134+
}
135+
)
136+
}, 1000)
137+
138+
return () => {
139+
if (idleCallback) {
140+
window.cancelIdleCallback(idleCallback)
141+
}
142+
window.clearTimeout(refreshSizingTimeout)
143+
}
144+
}, [ref, showPlaceholder])
145+
146+
return (
147+
<InView
148+
threshold={0}
149+
rootMargin={margin || '50% 0px 50% 0px'}
150+
onChange={onVisibleChanged}
151+
className={className}
152+
as="div"
153+
>
154+
<div ref={setRef}>
155+
{showPlaceholder ? (
156+
<div
157+
id={measurements?.id ?? id}
158+
className={`virtual-element-placeholder ${placeholderClassName}`}
159+
style={styleObj}
160+
></div>
161+
) : (
162+
children
163+
)}
164+
</div>
165+
</InView>
166+
)
167+
}
168+
169+
function measureElement(el: HTMLElement): IElementMeasurements | null {
170+
const style = window.getComputedStyle(el)
171+
const clientRect = el.getBoundingClientRect()
172+
173+
return {
174+
width: style.width || 'auto',
175+
clientHeight: clientRect.height,
176+
marginTop: style.marginTop || undefined,
177+
marginBottom: style.marginBottom || undefined,
178+
marginLeft: style.marginLeft || undefined,
179+
marginRight: style.marginRight || undefined,
180+
id: el.id,
181+
}
182+
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
RundownLayoutRundownHeader,
5353
RundownLayoutFilterBase,
5454
} from '@sofie-automation/meteor-lib/dist/collections/RundownLayouts'
55+
import { VirtualElement } from '../lib/VirtualElement.js'
5556
import { SEGMENT_TIMELINE_ELEMENT_ID } from './SegmentTimeline/SegmentTimeline.js'
5657
import { OffsetPosition } from '../utils/positions.js'
5758
import { MeteorCall } from '../lib/meteorApi.js'
@@ -932,6 +933,7 @@ const RundownViewContent = translateWithTracker<IPropsWithReady & ITrackedProps,
932933
return null
933934
}
934935

936+
let globalIndex = 0
935937
const rundowns = this.props.matchedSegments.map((m) => m.rundown._id)
936938

937939
return this.props.matchedSegments.map((rundownAndSegments, rundownIndex, rundownArray) => {
@@ -975,7 +977,18 @@ const RundownViewContent = translateWithTracker<IPropsWithReady & ITrackedProps,
975977

976978
return (
977979
<ErrorBoundary key={unprotectString(segment._id)}>
978-
<div style={{ contentVisibility: 'auto', containIntrinsicSize: '0 500px' }}>
980+
<VirtualElement
981+
className={ClassNames({
982+
'segment-timeline-wrapper--hidden': segment.isHidden,
983+
'segment-timeline-wrapper--shelf': segment.showShelf,
984+
})}
985+
id={SEGMENT_TIMELINE_ELEMENT_ID + segment._id}
986+
margin={'100% 0px 100% 0px'}
987+
initialShow={globalIndex++ < window.innerHeight / 260}
988+
placeholderHeight={260}
989+
placeholderClassName="placeholder-shimmer-element segment-timeline-placeholder"
990+
width="auto"
991+
>
979992
{this.renderSegmentComponent(
980993
segment,
981994
segmentIndex,
@@ -990,7 +1003,7 @@ const RundownViewContent = translateWithTracker<IPropsWithReady & ITrackedProps,
9901003
rundownAndSegments.segmentIdsBeforeEachSegment[segmentIndex],
9911004
rundownIdsBefore
9921005
)}
993-
</div>
1006+
</VirtualElement>
9941007
</ErrorBoundary>
9951008
)
9961009
}

0 commit comments

Comments
 (0)