1- import { useAtomValue , useSetAtom } from "jotai" ;
1+ import { useAtomValue } from "jotai" ;
22import {
3- autoScrollAtom ,
4- currentLeaderSlotAtom ,
3+ currentSlotAtom ,
54 epochAtom ,
5+ isCurrentlyLeaderAtom ,
66 leaderSlotsAtom ,
7+ nextLeaderSlotAtom ,
8+ nextLeaderSlotIndexAtom ,
79 SlotNavFilter ,
810 slotNavFilterAtom ,
9- slotOverrideAtom ,
1011} from "../../atoms" ;
11- import { Box , Flex , Text } from "@radix-ui/themes" ;
12- import type { RefObject } from "react" ;
13- import { memo , useCallback , useEffect , useMemo , useRef , useState } from "react" ;
12+ import { Flex , Text } from "@radix-ui/themes" ;
13+ import { memo , useMemo , type CSSProperties } from "react" ;
1414import styles from "./slotsList.module.css" ;
15- import { slotsListPinnedSlotOffset } from "../../consts" ;
16- import { throttle } from "lodash" ;
17- import SlotsRenderer , { MSlotsPlaceholder } from "./SlotsRenderer" ;
18- import type { ScrollSeekConfiguration , VirtuosoHandle } from "react-virtuoso" ;
19- import { Virtuoso } from "react-virtuoso" ;
2015import ResetLive from "./ResetLive" ;
21- import type { DebouncedState } from "use-debounce" ;
22- import { useDebouncedCallback } from "use-debounce" ;
23- import clsx from "clsx" ;
24- import {
25- getAllSlotsListProps ,
26- getMySlotsListProps ,
27- type SlotsIndexProps ,
28- } from "./utils" ;
29-
30- const computeItemKey = ( slot : number ) => slot ;
31-
32- // Add one future slot to prevent current leader transition from flickering
33- const increaseViewportBy = { top : 24 , bottom : 0 } ;
16+ import VirtualSlotsList from "./VirtualSlotsList" ;
17+ import { slotGroupCssVars , type SlotsIndexProps } from "./const" ;
18+ import { getAllSlotsListProps } from "./allSlotsUtils" ;
19+ import { getMySlotsListProps } from "./mySlotsUtils" ;
3420
3521interface SlotsListProps {
3622 width : number ;
@@ -44,244 +30,90 @@ export default function SlotsList({ width, height }: SlotsListProps) {
4430 if ( ! epoch ) return null ;
4531
4632 return navFilter === SlotNavFilter . MySlots ? (
47- < MySlotsList key = { epoch . epoch } width = { width } height = { height } />
33+ < MMySlotsList key = { epoch . epoch } width = { width } height = { height } />
4834 ) : (
49- < AllSlotsList key = { epoch . epoch } width = { width } height = { height } />
35+ < MAllSlotsList key = { epoch . epoch } width = { width } height = { height } />
5036 ) ;
5137}
5238
53- function InnerSlotsList ( {
39+ const MInnerSlotsList = memo ( function InnerSlotsList ( {
5440 width,
5541 height,
5642 itemsCount,
5743 getSlotAtIndex,
5844 getIndexForSlot,
45+ offsetHelpers,
5946} : SlotsIndexProps & SlotsListProps ) {
60- const listContainerRef = useRef < HTMLDivElement > ( null ) ;
61- const listRef = useRef < VirtuosoHandle > ( null ) ;
62- const visibleStartIndexRef = useRef < number | null > ( null ) ;
63-
64- const [ hideList , setHideList ] = useState ( true ) ;
65- const [ showPlaceholder , setShowPlaceholder ] = useState ( true ) ;
66-
67- useEffect ( ( ) => {
68- // initially hide list to
69- const timeout = setTimeout ( ( ) => {
70- setHideList ( false ) ;
71- } , 100 ) ;
72-
73- return ( ) => clearTimeout ( timeout ) ;
74- } , [ ] ) ;
75-
76- const setSlotOverride = useSetAtom ( slotOverrideAtom ) ;
77-
78- const debouncedScroll = useDebouncedCallback ( ( ) => { } , 100 ) ;
79-
80- const { rangeChanged, scrollSeekConfiguration } = useMemo ( ( ) => {
81- const rangeChangedFn = ( { startIndex } : { startIndex : number } ) => {
82- // account for increaseViewportBy
83- visibleStartIndexRef . current = startIndex + 1 ;
84- } ;
85-
86- const config : ScrollSeekConfiguration = {
87- enter : ( velocity ) => Math . abs ( velocity ) > 1500 ,
88- exit : ( velocity ) => Math . abs ( velocity ) < 500 ,
89- change : ( _ , range ) => rangeChangedFn ( range ) ,
47+ const style : CSSProperties = useMemo ( ( ) => {
48+ return {
49+ ...slotGroupCssVars ,
50+ position : "relative" ,
51+ width,
52+ height,
9053 } ;
91- return { rangeChanged : rangeChangedFn , scrollSeekConfiguration : config } ;
92- } , [ visibleStartIndexRef ] ) ;
93-
94- // Setup user scroll handling
95- useEffect ( ( ) => {
96- if ( ! listContainerRef . current ) return ;
97- const container = listContainerRef . current ;
98-
99- const handleSlotOverride = throttle (
100- ( ) => {
101- if ( visibleStartIndexRef . current === null ) return ;
102-
103- debouncedScroll ( ) ;
104-
105- const slotIndex = Math . min (
106- visibleStartIndexRef . current + slotsListPinnedSlotOffset ,
107- itemsCount - 1 ,
108- ) ;
109-
110- const slot = getSlotAtIndex ( slotIndex ) ;
111- setSlotOverride ( slot ) ;
112- } ,
113- 50 ,
114- { leading : true , trailing : true } ,
115- ) ;
116-
117- const handleScroll = ( ) => {
118- handleSlotOverride ( ) ;
119- } ;
120-
121- container . addEventListener ( "wheel" , handleScroll ) ;
122- container . addEventListener ( "touchmove" , handleScroll ) ;
123-
124- return ( ) => {
125- container . removeEventListener ( "wheel" , handleScroll ) ;
126- container . removeEventListener ( "touchmove" , handleScroll ) ;
127- } ;
128- } , [
129- getSlotAtIndex ,
130- debouncedScroll ,
131- setSlotOverride ,
132- itemsCount ,
133- visibleStartIndexRef ,
134- ] ) ;
135-
136- const getItemContent = useCallback (
137- ( index : number ) => {
138- const leader = getSlotAtIndex ( index ) ;
139- if ( leader == null ) return null ;
140- return < SlotsRenderer leaderSlotForGroup = { leader } /> ;
141- } ,
142- [ getSlotAtIndex ] ,
143- ) ;
144-
145- const totalListHeightChanged = useCallback (
146- ( totalListHeight : number ) => setShowPlaceholder ( totalListHeight >= height ) ,
147- [ height ] ,
148- ) ;
54+ } , [ height , width ] ) ;
14955
15056 return (
151- < Box
152- ref = { listContainerRef }
153- position = "relative"
154- width = { `${ width } px` }
155- height = { `${ height } px` }
156- >
157- < MRtAutoScroll listRef = { listRef } getIndexForSlot = { getIndexForSlot } />
158- < MSlotOverrideScroll
159- listRef = { listRef }
160- getIndexForSlot = { getIndexForSlot }
161- debouncedScroll = { debouncedScroll }
162- />
163- { showPlaceholder && < MSlotsPlaceholder width = { width } height = { height } /> }
57+ < div style = { style } >
16458 < ResetLive />
165- < Virtuoso
166- ref = { listRef }
167- className = { clsx ( styles . slotsList , { [ styles . hidden ] : hideList } ) }
168- width = { width }
169- height = { height }
170- totalCount = { itemsCount }
171- increaseViewportBy = { increaseViewportBy }
172- // height of past slots that the user is most likely to scroll through
173- defaultItemHeight = { 42 }
174- skipAnimationFrameInResizeObserver
175- computeItemKey = { computeItemKey }
176- itemContent = { getItemContent }
177- rangeChanged = { rangeChanged }
178- components = { { ScrollSeekPlaceholder : MScrollSeekPlaceHolder } }
179- scrollSeekConfiguration = { scrollSeekConfiguration }
180- totalListHeightChanged = { totalListHeightChanged }
59+ < VirtualSlotsList
60+ visibleWidth = { width }
61+ visibleHeight = { height }
62+ itemsCount = { itemsCount }
63+ getSlotAtIndex = { getSlotAtIndex }
64+ getIndexForSlot = { getIndexForSlot }
65+ offsetHelpers = { offsetHelpers }
18166 />
182- </ Box >
67+ </ div >
18368 ) ;
184- }
185-
186- // Render nothing when scrolling quickly to improve performance
187- const MScrollSeekPlaceHolder = memo ( function ScrollSeekPlaceholder ( ) {
188- return null ;
18969} ) ;
19070
191- interface RTAutoScrollProps {
192- listRef : RefObject < VirtuosoHandle > ;
193- getIndexForSlot : ( slot : number ) => number | undefined ;
194- }
195- const MRtAutoScroll = memo ( function RTAutoScroll ( {
196- listRef,
197- getIndexForSlot,
198- } : RTAutoScrollProps ) {
199- const currentLeaderSlot = useAtomValue ( currentLeaderSlotAtom ) ;
200- const autoScroll = useAtomValue ( autoScrollAtom ) ;
201-
202- useEffect ( ( ) => {
203- if ( ! autoScroll || currentLeaderSlot === undefined || ! listRef . current )
204- return ;
205-
206- // scroll to new current leader slot
207- const slotIndex = getIndexForSlot ( currentLeaderSlot ) ;
208- const visibleStartIndex = slotIndex
209- ? Math . max ( 0 , slotIndex - slotsListPinnedSlotOffset )
210- : 0 ;
211-
212- listRef . current . scrollToIndex ( {
213- index : visibleStartIndex ,
214- align : "start" ,
215- } ) ;
216- } , [ autoScroll , currentLeaderSlot , getIndexForSlot , listRef ] ) ;
217-
218- return null ;
219- } ) ;
220-
221- interface SlotOverrideScrollProps {
222- listRef : RefObject < VirtuosoHandle > ;
223- getIndexForSlot : ( slot : number ) => number | undefined ;
224- debouncedScroll : DebouncedState < ( ) => void > ;
225- }
226- const MSlotOverrideScroll = memo ( function SlotOverrideScroll ( {
227- listRef,
228- getIndexForSlot,
229- debouncedScroll,
230- } : SlotOverrideScrollProps ) {
231- const rafIdRef = useRef < number | null > ( null ) ;
232- const slotOverride = useAtomValue ( slotOverrideAtom ) ;
233-
234- useEffect ( ( ) => {
235- if (
236- slotOverride === undefined ||
237- ! listRef . current ||
238- debouncedScroll . isPending ( )
239- ) {
240- return ;
241- }
242-
243- const slotIndex = getIndexForSlot ( slotOverride ) ;
244- const targetIndex = slotIndex
245- ? Math . max ( 0 , slotIndex - slotsListPinnedSlotOffset )
246- : 0 ;
247-
248- const prevRafId = rafIdRef . current ;
249- rafIdRef . current = requestAnimationFrame ( ( ) => {
250- if ( prevRafId !== null ) {
251- cancelAnimationFrame ( prevRafId ) ;
252- }
253-
254- listRef . current ?. scrollToIndex ( {
255- index : targetIndex ,
256- align : "start" ,
257- } ) ;
258- } ) ;
259-
260- return ( ) => {
261- if ( rafIdRef . current !== null ) {
262- cancelAnimationFrame ( rafIdRef . current ) ;
263- rafIdRef . current = null ;
264- }
265- } ;
266- } , [ getIndexForSlot , slotOverride , listRef , debouncedScroll ] ) ;
267-
268- return null ;
269- } ) ;
270-
271- function AllSlotsList ( { width, height } : SlotsListProps ) {
71+ const MAllSlotsList = memo ( function AllSlotsList ( {
72+ width,
73+ height,
74+ } : SlotsListProps ) {
27275 const epoch = useAtomValue ( epochAtom ) ;
76+ const currentSlot = useAtomValue ( currentSlotAtom ) ;
77+ const leaderSlots = useAtomValue ( leaderSlotsAtom ) ;
78+ const nextLeaderSlot = useAtomValue ( nextLeaderSlotAtom ) ;
27379
274- const slotsListProps = useMemo ( ( ) => getAllSlotsListProps ( epoch ) , [ epoch ] ) ;
80+ const slotsListProps = useMemo (
81+ ( ) => getAllSlotsListProps ( epoch , currentSlot , leaderSlots , nextLeaderSlot ) ,
82+ [ epoch , currentSlot , leaderSlots , nextLeaderSlot ] ,
83+ ) ;
27584
27685 if ( ! slotsListProps ) return null ;
27786
278- return < InnerSlotsList width = { width } height = { height } { ...slotsListProps } /> ;
279- }
87+ return < MInnerSlotsList width = { width } height = { height } { ...slotsListProps } /> ;
88+ } ) ;
28089
281- function MySlotsList ( { width, height } : SlotsListProps ) {
90+ const MMySlotsList = memo ( function MySlotsList ( {
91+ width,
92+ height,
93+ } : SlotsListProps ) {
28294 const mySlots = useAtomValue ( leaderSlotsAtom ) ;
283-
284- const slotsListProps = useMemo ( ( ) => getMySlotsListProps ( mySlots ) , [ mySlots ] ) ;
95+ const currentSlot = useAtomValue ( currentSlotAtom ) ;
96+ const nextLeaderSlot = useAtomValue ( nextLeaderSlotAtom ) ;
97+ const isCurrentlyLeader = useAtomValue ( isCurrentlyLeaderAtom ) ;
98+ const nextLeaderSlotIndex = useAtomValue ( nextLeaderSlotIndexAtom ) ;
99+
100+ const slotsListProps = useMemo (
101+ ( ) =>
102+ getMySlotsListProps (
103+ mySlots ,
104+ currentSlot ,
105+ nextLeaderSlot ,
106+ isCurrentlyLeader ,
107+ nextLeaderSlotIndex ,
108+ ) ,
109+ [
110+ mySlots ,
111+ currentSlot ,
112+ nextLeaderSlot ,
113+ isCurrentlyLeader ,
114+ nextLeaderSlotIndex ,
115+ ] ,
116+ ) ;
285117
286118 if ( ! slotsListProps ) return null ;
287119
@@ -302,5 +134,5 @@ function MySlotsList({ width, height }: SlotsListProps) {
302134 ) ;
303135 }
304136
305- return < InnerSlotsList width = { width } height = { height } { ...slotsListProps } /> ;
306- }
137+ return < MInnerSlotsList width = { width } height = { height } { ...slotsListProps } /> ;
138+ } ) ;
0 commit comments