1
1
using System ;
2
+ using System . Collections . Concurrent ;
2
3
using System . Collections . Generic ;
3
4
using System . Linq ;
5
+ using System . Threading ;
6
+ using System . Threading . Tasks ;
4
7
using Microsoft . AspNetCore . Components . Rendering ;
5
8
6
9
namespace Microsoft . AspNetCore . Components . Virtualization
7
10
{
8
11
public abstract class VirtualizeBase < TItem > : ComponentBase
9
12
{
13
+ private readonly ConcurrentQueue < TItem > _loadedItems = new ConcurrentQueue < TItem > ( ) ;
14
+
15
+ private readonly SemaphoreSlim _fetchSemaphore = new SemaphoreSlim ( 1 ) ;
16
+
10
17
private int _itemsAbove ;
11
18
12
19
private int _itemsVisible ;
13
20
14
21
private int _itemsBelow ;
15
22
23
+ private IEnumerable < TItem > LoadedItems => Items ?? ( IEnumerable < TItem > ) _loadedItems ;
24
+
25
+ private int ItemCount => Items ? . Count ?? _loadedItems . Count ;
26
+
16
27
protected ElementReference TopSpacer { get ; private set ; }
17
28
18
29
protected ElementReference BottomSpacer { get ; private set ; }
19
30
20
31
[ Parameter ]
21
32
public ICollection < TItem > Items { get ; set ; } = default ! ;
22
33
34
+ [ Parameter ]
35
+ public Func < Range , Task < IEnumerable < TItem > > > ItemsProvider { get ; set ; } = default ! ;
36
+
23
37
[ Parameter ]
24
38
public float ItemSize { get ; set ; }
25
39
40
+ [ Parameter ]
41
+ public int InitialItemsCount { get ; set ; }
42
+
26
43
[ Parameter ]
27
44
public RenderFragment < TItem > ? ChildContent { get ; set ; }
28
45
29
46
protected override void OnParametersSet ( )
30
47
{
31
- if ( Items == null )
48
+ if ( ItemSize <= 0f )
32
49
{
33
50
throw new InvalidOperationException (
34
- $ "Parameter '{ nameof ( Items ) } ' must be specified and non-null .") ;
51
+ $ "Parameter '{ nameof ( ItemSize ) } ' must be specified and greater than zero .") ;
35
52
}
36
53
37
- if ( ItemSize <= 0f )
54
+ if ( Items != null )
55
+ {
56
+ if ( ItemsProvider != null )
57
+ {
58
+ throw new InvalidOperationException (
59
+ $ "{ GetType ( ) } cannot have both '{ nameof ( Items ) } ' and '{ nameof ( ItemsProvider ) } ' parameters.") ;
60
+ }
61
+
62
+ _itemsBelow = Items . Count ;
63
+ }
64
+ else if ( ItemsProvider != null )
65
+ {
66
+ _itemsBelow = 0 ;
67
+ }
68
+ else
38
69
{
39
70
throw new InvalidOperationException (
40
- $ "Parameter '{ nameof ( ItemSize ) } ' must be specified and greater than zero.") ;
71
+ $ "{ GetType ( ) } requires either the '{ nameof ( Items ) } ' or '{ nameof ( ItemsProvider ) } ' parameter to " +
72
+ $ "be specified and non-null.") ;
41
73
}
42
-
43
- _itemsBelow = Items . Count ;
44
74
}
45
75
46
76
protected void UpdateTopSpacer ( float spacerSize , float containerSize )
47
- => CalculateSpacerItemDistribution ( spacerSize , containerSize , out _itemsAbove , out _itemsBelow ) ;
77
+ {
78
+ CalculateSpacerItemDistribution ( spacerSize , containerSize , out _itemsAbove , out _itemsBelow ) ;
79
+ Console . WriteLine ( $ "Above: { _itemsAbove } , Visible: { _itemsVisible } ") ;
80
+ }
48
81
49
82
protected void UpdateBottomSpacer ( float spacerSize , float containerSize )
50
- => CalculateSpacerItemDistribution ( spacerSize , containerSize , out _itemsBelow , out _itemsAbove ) ;
83
+ {
84
+ CalculateSpacerItemDistribution ( spacerSize , containerSize , out _itemsBelow , out _itemsAbove ) ;
85
+ Console . WriteLine ( $ "Above: { _itemsAbove } , Visible: { _itemsVisible } ") ;
86
+
87
+ if ( ItemsProvider != null && _itemsAbove + _itemsVisible >= _loadedItems . Count )
88
+ {
89
+ FetchItems ( _itemsAbove + _itemsVisible + InitialItemsCount ) ;
90
+ }
91
+ }
51
92
52
93
private void CalculateSpacerItemDistribution ( float spacerSize , float containerSize , out int itemsInThisSpacer , out int itemsInOtherSpacer )
53
94
{
54
- _itemsVisible = ( int ) ( containerSize / ItemSize ) + 1 ; // TODO: Custom number of "padding" elements?
55
- itemsInThisSpacer = ( int ) ( spacerSize / ItemSize ) ;
56
- itemsInOtherSpacer = Items . Count - itemsInThisSpacer - _itemsVisible ;
95
+ _itemsVisible = Math . Max ( 0 , ( int ) Math . Ceiling ( containerSize / ItemSize ) + 2 ) ;
96
+ itemsInThisSpacer = Math . Max ( 0 , ( int ) Math . Floor ( spacerSize / ItemSize ) - 1 ) ;
97
+ itemsInOtherSpacer = Math . Max ( 0 , ItemCount - itemsInThisSpacer - _itemsVisible ) ;
57
98
58
99
StateHasChanged ( ) ;
59
100
}
@@ -63,30 +104,58 @@ private string GetSpacerStyle(int itemsInSpacer)
63
104
return $ "height: { itemsInSpacer * ItemSize } px;";
64
105
}
65
106
107
+ private void FetchItems ( int newItemCount )
108
+ {
109
+ var currentScheduler = TaskScheduler . FromCurrentSynchronizationContext ( ) ;
110
+
111
+ _fetchSemaphore . WaitAsync ( ) . ContinueWith ( t =>
112
+ {
113
+ if ( _loadedItems . Count >= newItemCount )
114
+ {
115
+ _fetchSemaphore . Release ( ) ;
116
+ return ;
117
+ }
118
+
119
+ ItemsProvider ( _loadedItems . Count ..newItemCount ) . ContinueWith ( t =>
120
+ {
121
+ foreach ( var item in t . Result )
122
+ {
123
+ _loadedItems . Enqueue ( item ) ;
124
+ }
125
+
126
+ StateHasChanged ( ) ;
127
+
128
+ _fetchSemaphore . Release ( ) ;
129
+ } , currentScheduler ) ;
130
+ } ) ;
131
+ }
132
+
66
133
protected override void BuildRenderTree ( RenderTreeBuilder builder )
67
134
{
135
+ _itemsBelow = Math . Max ( 1 , ItemCount - ( _itemsVisible + _itemsAbove ) ) ;
136
+
68
137
builder . OpenElement ( 0 , "div" ) ;
69
138
builder . AddAttribute ( 1 , "key" , "top-spacer" ) ;
70
139
builder . AddAttribute ( 2 , "style" , GetSpacerStyle ( _itemsAbove ) ) ;
71
140
builder . AddElementReferenceCapture ( 3 , elementReference => TopSpacer = elementReference ) ;
72
141
builder . CloseElement ( ) ;
73
142
143
+ builder . OpenRegion ( 4 ) ;
144
+
74
145
if ( ChildContent != null )
75
146
{
76
- builder . AddContent ( 4 , new RenderFragment ( builder =>
147
+ foreach ( var item in LoadedItems . Skip ( _itemsAbove ) . Take ( _itemsVisible ) )
77
148
{
78
- foreach ( var item in Items . Skip ( _itemsAbove ) . Take ( _itemsVisible ) )
79
- {
80
- ChildContent ( item ) ? . Invoke ( builder ) ;
81
- }
82
- } ) ) ;
149
+ ChildContent ( item ) ? . Invoke ( builder ) ;
150
+ }
83
151
}
84
152
153
+ builder . CloseRegion ( ) ;
154
+
85
155
builder . OpenElement ( 5 , "div" ) ;
86
156
builder . AddAttribute ( 6 , "key" , "bottom-spacer" ) ;
87
157
builder . AddAttribute ( 7 , "style" , GetSpacerStyle ( _itemsBelow ) ) ;
88
- builder . AddElementReferenceCapture ( 8 , elementReference =>
89
- BottomSpacer = elementReference ) ;
158
+ builder . AddElementReferenceCapture ( 8 , elementReference => BottomSpacer = elementReference ) ;
90
159
builder . CloseElement ( ) ;
91
160
}
92
161
}
0 commit comments