@@ -12,7 +12,7 @@ import {
12
12
useIcon ,
13
13
wrapperToControlItem ,
14
14
} from "lowcoder-design" ;
15
- import { ReactNode , useCallback , useMemo , useRef , useState } from "react" ;
15
+ import { memo , ReactNode , useCallback , useMemo , useRef , useState } from "react" ;
16
16
import styled from "styled-components" ;
17
17
import Popover from "antd/es/popover" ;
18
18
import { CloseIcon , SearchIcon } from "icons" ;
@@ -149,8 +149,8 @@ const IconRow = styled.div`
149
149
` ;
150
150
151
151
const IconItemContainer = styled . div `
152
- width: 60px ;
153
- height: 60px ;
152
+ width: 120px ;
153
+ height: 120px ;
154
154
display: flex;
155
155
flex-direction: column;
156
156
align-items: center;
@@ -213,7 +213,7 @@ const IconScoutSearchParams: SearchParams = {
213
213
sort : 'relevant' ,
214
214
} ;
215
215
216
- const columnNum = 8 ;
216
+ const columnNum = 4 ;
217
217
218
218
export const IconPicker = ( props : {
219
219
assetType : string ;
@@ -232,6 +232,10 @@ export const IconPicker = (props: {
232
232
const [ searchResults , setSearchResults ] = useState < Array < any > > ( [ ] ) ;
233
233
const { subscriptions } = useSimpleSubscriptionContext ( ) ;
234
234
235
+ const [ page , setPage ] = useState ( 1 ) ;
236
+ const [ hasMore , setHasMore ] = useState ( true ) ;
237
+
238
+
235
239
const mediaPackSubscription = subscriptions . find (
236
240
sub => sub . product === SubscriptionProductsEnum . MEDIAPACKAGE && sub . status === 'active'
237
241
) ;
@@ -246,26 +250,35 @@ export const IconPicker = (props: {
246
250
} , [ ]
247
251
) ;
248
252
249
- const fetchResults = async ( query : string ) => {
253
+ const fetchResults = async ( query : string , pageNum : number = 1 ) => {
250
254
setLoading ( true ) ;
255
+
251
256
const freeResult = await searchAssets ( {
252
257
...IconScoutSearchParams ,
253
258
asset : props . assetType ,
254
259
price : 'free' ,
255
260
query,
261
+ page : pageNum ,
256
262
} ) ;
263
+
257
264
const premiumResult = await searchAssets ( {
258
265
...IconScoutSearchParams ,
259
266
asset : props . assetType ,
260
267
price : 'premium' ,
261
268
query,
269
+ page : pageNum ,
262
270
} ) ;
271
+
272
+ const combined = [ ...freeResult . data , ...premiumResult . data ] ;
273
+ const isLastPage = combined . length < IconScoutSearchParams . per_page * 2 ;
274
+
275
+ setSearchResults ( prev =>
276
+ pageNum === 1 ? combined : [ ...prev , ...combined ]
277
+ ) ;
278
+ setHasMore ( ! isLastPage ) ;
263
279
setLoading ( false ) ;
264
-
265
- console . log ( "freeResult" , freeResult , "premiumResult" , premiumResult )
266
-
267
- setSearchResults ( [ ...freeResult . data , ...premiumResult . data ] ) ;
268
280
} ;
281
+
269
282
270
283
const downloadAsset = async (
271
284
uuid : string ,
@@ -316,64 +329,119 @@ export const IconPicker = (props: {
316
329
const debouncedFetchResults = useMemo ( ( ) => debounce ( fetchResults , 700 ) , [ ] ) ;
317
330
318
331
const rowRenderer = useCallback (
319
- ( p : ListRowProps ) => (
320
- < IconRow key = { p . key } style = { p . style } >
321
- { searchResults
322
- . slice ( p . index * columnNum , ( p . index + 1 ) * columnNum )
323
- . map ( ( icon ) => (
324
- < IconItemContainer
325
- key = { icon . uuid }
326
- tabIndex = { 0 }
327
- onClick = { ( ) => {
328
- // check if premium content then show subscription popup
329
- // TODO: if user has subscription then skip this if block
330
- if ( ! mediaPackSubscription ) {
331
- CustomModal . confirm ( {
332
- title : trans ( "iconScout.buySubscriptionTitle" ) ,
333
- content : trans ( "iconScout.buySubscriptionContent" ) ,
334
- onConfirm : ( ) => {
335
- window . open ( SUBSCRIPTION_SETTING , "_blank" ) ;
336
- } ,
337
- confirmBtnType : "primary" ,
338
- okText : trans ( "iconScout.buySubscriptionButton" ) ,
339
- } )
340
- return ;
341
- }
342
-
343
- fetchDownloadUrl (
344
- icon . uuid ,
345
- props . assetType === AssetType . ICON ? icon . urls . png_64 : icon . urls . thumb ,
346
- ) ;
347
- } }
332
+ ( { index , key , style } : ListRowProps ) => {
333
+ const icons = searchResults . slice ( index * columnNum , ( index + 1 ) * columnNum ) ;
334
+
335
+ return (
336
+ < IconRow key = { key } style = { style } >
337
+ { icons . map ( ( icon ) => (
338
+ < Popover
339
+ key = { icon . uuid + '-popover' }
340
+ content = {
341
+ < div style = { { maxWidth : 400 } } >
342
+ { props . assetType === AssetType . LOTTIE ? (
343
+ < video
344
+ src = { icon . urls . thumb }
345
+ autoPlay
346
+ loop
347
+ muted
348
+ style = { { width : "100%" , height : "auto" , borderRadius : 6 } }
349
+ />
350
+ ) : (
351
+ < img
352
+ src = { props . assetType === AssetType . ICON ? icon . urls . png_128 : icon . urls . thumb }
353
+ alt = ""
354
+ style = { { width : "100%" , height : "auto" , borderRadius : 6 } }
355
+ />
356
+ ) }
357
+ </ div >
358
+ }
359
+ placement = "right"
360
+ mouseEnterDelay = { 0.2 }
348
361
>
349
- < Badge
350
- count = { icon . price !== 0 ? < CrownFilled style = { { color : "#e7b549" } } /> : undefined }
351
- size = 'small'
362
+ < IconItemContainer
363
+ key = { icon . uuid }
364
+ tabIndex = { 0 }
365
+ onClick = { ( ) => {
366
+ if ( ! mediaPackSubscription ) {
367
+ CustomModal . confirm ( {
368
+ title : trans ( "iconScout.buySubscriptionTitle" ) ,
369
+ content : trans ( "iconScout.buySubscriptionContent" ) ,
370
+ onConfirm : ( ) => {
371
+ window . open ( SUBSCRIPTION_SETTING , "_blank" ) ;
372
+ } ,
373
+ confirmBtnType : "primary" ,
374
+ okText : trans ( "iconScout.buySubscriptionButton" ) ,
375
+ } ) ;
376
+ return ;
377
+ }
378
+
379
+ fetchDownloadUrl (
380
+ icon . uuid ,
381
+ props . assetType === AssetType . ICON ? icon . urls . png_64 : icon . urls . thumb ,
382
+ ) ;
383
+ } }
352
384
>
353
- < IconWrapper $isPremium = { icon . price !== 0 } >
354
- { props . assetType === AssetType . ICON && (
355
- < StyledPreviewIcon src = { icon . urls . png_64 } />
356
- ) }
357
- { props . assetType === AssetType . ILLUSTRATION && (
358
- < StyledPreviewIcon src = { icon . urls . thumb } />
359
- ) }
360
- { props . assetType === AssetType . LOTTIE && (
361
- < StyledPreviewLotte src = { icon . urls . thumb } autoPlay />
362
- ) }
363
- </ IconWrapper >
364
- </ Badge >
365
- </ IconItemContainer >
385
+ < Badge
386
+ count = {
387
+ icon . price !== 0 ? (
388
+ < CrownFilled style = { { color : "#e7b549" } } />
389
+ ) : undefined
390
+ }
391
+ size = "small"
392
+ >
393
+ < IconWrapper $isPremium = { icon . price !== 0 } >
394
+ { props . assetType === AssetType . ICON && (
395
+ < StyledPreviewIcon src = { icon . urls . png_64 } />
396
+ ) }
397
+ { props . assetType === AssetType . ILLUSTRATION && (
398
+ < StyledPreviewIcon src = { icon . urls . thumb } />
399
+ ) }
400
+ { props . assetType === AssetType . LOTTIE && (
401
+ < StyledPreviewLotte src = { icon . urls . thumb } autoPlay />
402
+ ) }
403
+ </ IconWrapper >
404
+ </ Badge >
405
+ </ IconItemContainer >
406
+ </ Popover >
366
407
) ) }
367
- </ IconRow >
368
- ) , [ searchResults ]
408
+ </ IconRow >
409
+ ) ;
410
+ } ,
411
+ [ columnNum , mediaPackSubscription , props . assetType , fetchDownloadUrl ]
369
412
) ;
413
+
370
414
371
415
const popupTitle = useMemo ( ( ) => {
372
416
if ( props . assetType === AssetType . ILLUSTRATION ) return trans ( "iconScout.searchImage" ) ;
373
417
if ( props . assetType === AssetType . LOTTIE ) return trans ( "iconScout.searchAnimation" ) ;
374
418
return trans ( "iconScout.searchIcon" ) ;
375
419
} , [ props . assetType ] ) ;
376
420
421
+ const MemoizedIconList = memo ( ( {
422
+ searchResults,
423
+ rowRenderer,
424
+ onScroll,
425
+ columnNum,
426
+ } : {
427
+ searchResults : any [ ] ;
428
+ rowRenderer : ( props : ListRowProps ) => React . ReactNode ;
429
+ onScroll : ( params : { clientHeight : number ; scrollHeight : number ; scrollTop : number } ) => void ;
430
+ columnNum : number ;
431
+ } ) => {
432
+ return (
433
+ < IconList
434
+ width = { 550 }
435
+ height = { 400 }
436
+ rowHeight = { 140 }
437
+ rowCount = { Math . ceil ( searchResults . length / columnNum ) }
438
+ rowRenderer = { rowRenderer }
439
+ onScroll = { onScroll }
440
+ />
441
+ ) ;
442
+ } ) ;
443
+
444
+
377
445
return (
378
446
< Popover
379
447
trigger = { 'click' }
@@ -418,12 +486,28 @@ export const IconPicker = (props: {
418
486
) }
419
487
{ ! loading && Boolean ( searchText ) && Boolean ( searchResults ?. length ) && (
420
488
< IconListWrapper >
489
+
421
490
< IconList
422
491
width = { 550 }
423
492
height = { 400 }
424
- rowHeight = { 80 }
493
+ rowHeight = { 140 }
425
494
rowCount = { Math . ceil ( searchResults . length / columnNum ) }
426
495
rowRenderer = { rowRenderer }
496
+ onScroll = { ( {
497
+ clientHeight,
498
+ scrollHeight,
499
+ scrollTop,
500
+ } : {
501
+ clientHeight : number ;
502
+ scrollHeight : number ;
503
+ scrollTop : number ;
504
+ } ) => {
505
+ if ( hasMore && ! loading && scrollHeight - scrollTop <= clientHeight + 10 ) {
506
+ const nextPage = page + 1 ;
507
+ setPage ( nextPage ) ;
508
+ fetchResults ( searchText , nextPage ) ;
509
+ }
510
+ } }
427
511
/>
428
512
</ IconListWrapper >
429
513
) }
@@ -451,7 +535,7 @@ export const IconPicker = (props: {
451
535
/>
452
536
</ ButtonWrapper >
453
537
) : (
454
- < BlockGrayLabel label = { trans ( "iconControl.selectIcon " ) } />
538
+ < BlockGrayLabel label = { props . assetType === AssetType . LOTTIE ? trans ( "iconControl.searchAnimation" ) : props . assetType === AssetType . ILLUSTRATION ? trans ( "iconControl.searchIllustration" ) : trans ( "iconControl.searchIcon ") } />
455
539
) }
456
540
</ TacoButton >
457
541
</ Popover >
0 commit comments