@@ -12,7 +12,7 @@ import {
1212 useIcon ,
1313 wrapperToControlItem ,
1414} from "lowcoder-design" ;
15- import { ReactNode , useCallback , useMemo , useRef , useState } from "react" ;
15+ import { memo , ReactNode , useCallback , useMemo , useRef , useState } from "react" ;
1616import styled from "styled-components" ;
1717import Popover from "antd/es/popover" ;
1818import { CloseIcon , SearchIcon } from "icons" ;
@@ -149,8 +149,8 @@ const IconRow = styled.div`
149149` ;
150150
151151const IconItemContainer = styled . div `
152- width: 60px ;
153- height: 60px ;
152+ width: 120px ;
153+ height: 120px ;
154154 display: flex;
155155 flex-direction: column;
156156 align-items: center;
@@ -213,7 +213,7 @@ const IconScoutSearchParams: SearchParams = {
213213 sort : 'relevant' ,
214214} ;
215215
216- const columnNum = 8 ;
216+ const columnNum = 4 ;
217217
218218export const IconPicker = ( props : {
219219 assetType : string ;
@@ -232,6 +232,10 @@ export const IconPicker = (props: {
232232 const [ searchResults , setSearchResults ] = useState < Array < any > > ( [ ] ) ;
233233 const { subscriptions } = useSimpleSubscriptionContext ( ) ;
234234
235+ const [ page , setPage ] = useState ( 1 ) ;
236+ const [ hasMore , setHasMore ] = useState ( true ) ;
237+
238+
235239 const mediaPackSubscription = subscriptions . find (
236240 sub => sub . product === SubscriptionProductsEnum . MEDIAPACKAGE && sub . status === 'active'
237241 ) ;
@@ -246,26 +250,35 @@ export const IconPicker = (props: {
246250 } , [ ]
247251 ) ;
248252
249- const fetchResults = async ( query : string ) => {
253+ const fetchResults = async ( query : string , pageNum : number = 1 ) => {
250254 setLoading ( true ) ;
255+
251256 const freeResult = await searchAssets ( {
252257 ...IconScoutSearchParams ,
253258 asset : props . assetType ,
254259 price : 'free' ,
255260 query,
261+ page : pageNum ,
256262 } ) ;
263+
257264 const premiumResult = await searchAssets ( {
258265 ...IconScoutSearchParams ,
259266 asset : props . assetType ,
260267 price : 'premium' ,
261268 query,
269+ page : pageNum ,
262270 } ) ;
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 ) ;
263279 setLoading ( false ) ;
264-
265- console . log ( "freeResult" , freeResult , "premiumResult" , premiumResult )
266-
267- setSearchResults ( [ ...freeResult . data , ...premiumResult . data ] ) ;
268280 } ;
281+
269282
270283 const downloadAsset = async (
271284 uuid : string ,
@@ -316,64 +329,119 @@ export const IconPicker = (props: {
316329 const debouncedFetchResults = useMemo ( ( ) => debounce ( fetchResults , 700 ) , [ ] ) ;
317330
318331 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 }
348361 >
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+ } }
352384 >
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 >
366407 ) ) }
367- </ IconRow >
368- ) , [ searchResults ]
408+ </ IconRow >
409+ ) ;
410+ } ,
411+ [ columnNum , mediaPackSubscription , props . assetType , fetchDownloadUrl ]
369412 ) ;
413+
370414
371415 const popupTitle = useMemo ( ( ) => {
372416 if ( props . assetType === AssetType . ILLUSTRATION ) return trans ( "iconScout.searchImage" ) ;
373417 if ( props . assetType === AssetType . LOTTIE ) return trans ( "iconScout.searchAnimation" ) ;
374418 return trans ( "iconScout.searchIcon" ) ;
375419 } , [ props . assetType ] ) ;
376420
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+
377445 return (
378446 < Popover
379447 trigger = { 'click' }
@@ -418,12 +486,28 @@ export const IconPicker = (props: {
418486 ) }
419487 { ! loading && Boolean ( searchText ) && Boolean ( searchResults ?. length ) && (
420488 < IconListWrapper >
489+
421490 < IconList
422491 width = { 550 }
423492 height = { 400 }
424- rowHeight = { 80 }
493+ rowHeight = { 140 }
425494 rowCount = { Math . ceil ( searchResults . length / columnNum ) }
426495 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+ } }
427511 />
428512 </ IconListWrapper >
429513 ) }
@@ -451,7 +535,7 @@ export const IconPicker = (props: {
451535 />
452536 </ ButtonWrapper >
453537 ) : (
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 ") } />
455539 ) }
456540 </ TacoButton >
457541 </ Popover >
0 commit comments