@@ -9,19 +9,27 @@ import {
99 LuFolderSearch ,
1010 LuLink ,
1111 LuList ,
12+ LuPause ,
13+ LuPlay ,
1214 LuSearch ,
15+ LuStepForward ,
1316} from "react-icons/lu" ;
1417import {
1518 Accordion ,
1619 Alert ,
1720 Box ,
21+ Button ,
22+ ButtonGroup ,
23+ Card ,
24+ Heading ,
1825 HStack ,
1926 Icon ,
2027 SkeletonText ,
28+ Stack ,
2129 type UseFileUploadReturn ,
2230} from "@chakra-ui/react" ;
23- import { useQuery } from "@tanstack/react-query" ;
24- import type { StacCatalog , StacCollection , StacItem } from "stac-ts" ;
31+ import { useInfiniteQuery , useQuery } from "@tanstack/react-query" ;
32+ import type { StacCatalog , StacCollection , StacItem , StacLink } from "stac-ts" ;
2533import Assets from "./assets" ;
2634import Catalogs from "./catalogs" ;
2735import CollectionSearch from "./collection-search" ;
@@ -37,6 +45,7 @@ import type { BBox2D } from "../types/map";
3745import type {
3846 DatetimeBounds ,
3947 StacAssets ,
48+ StacCollections ,
4049 StacSearch ,
4150 StacValue ,
4251} from "../types/stac" ;
@@ -46,6 +55,7 @@ export default function Panel({
4655 value,
4756 error,
4857 catalogs,
58+ setCollections,
4959 collections,
5060 filteredCollections,
5161 items,
@@ -62,6 +72,7 @@ export default function Panel({
6272 value : StacValue | undefined ;
6373 error : Error | undefined ;
6474 catalogs : StacCatalog [ ] | undefined ;
75+ setCollections : ( collections : StacCollection [ ] | undefined ) => void ;
6576 collections : StacCollection [ ] | undefined ;
6677 filteredCollections : StacCollection [ ] | undefined ;
6778 items : StacItem [ ] | undefined ;
@@ -76,15 +87,8 @@ export default function Panel({
7687 setDatetimeBounds : ( bounds : DatetimeBounds | undefined ) => void ;
7788} ) {
7889 const [ search , setSearch ] = useState < StacSearch > ( ) ;
79- const rootHref = value ?. links ?. find ( ( link ) => link . rel === "root" ) ?. href ;
80- const rootData = useQuery < StacValue > ( {
81- queryKey : [ "stac-value" , rootHref ] ,
82- enabled : ! ! rootHref ,
83- queryFn : ( ) => fetchStac ( rootHref ) ,
84- } ) ;
85- const searchLinks = rootData . data ?. links ?. filter (
86- ( link ) => link . rel === "search"
87- ) ;
90+ const [ numberOfCollections , setNumberOfCollections ] = useState < number > ( ) ;
91+ const [ fetchAllCollections , setFetchAllCollections ] = useState ( false ) ;
8892 const { links, assets, properties } = useMemo ( ( ) => {
8993 if ( value ) {
9094 if ( value . type === "Feature" ) {
@@ -101,50 +105,165 @@ export default function Panel({
101105 return { links : undefined , assets : undefined , properties : undefined } ;
102106 }
103107 } , [ value ] ) ;
108+ const { rootLink, collectionsLink, nextLink, prevLink, filteredLinks } =
109+ useMemo ( ( ) => {
110+ let rootLink : StacLink | undefined = undefined ;
111+ let collectionsLink : StacLink | undefined = undefined ;
112+ let nextLink : StacLink | undefined = undefined ;
113+ let prevLink : StacLink | undefined = undefined ;
114+ const filteredLinks = [ ] ;
115+ if ( links ) {
116+ for ( const link of links ) {
117+ switch ( link . rel ) {
118+ case "root" :
119+ rootLink = link ;
120+ break ;
121+ case "data" :
122+ collectionsLink = link ;
123+ break ;
124+ case "next" :
125+ nextLink = link ;
126+ break ;
127+ case "previous" :
128+ prevLink = link ;
129+ break ;
130+ }
131+ // We already show children and items in their own pane
132+ if ( link . rel !== "child" && link . rel !== "item" )
133+ filteredLinks . push ( link ) ;
134+ }
135+ }
136+ return { rootLink, collectionsLink, nextLink, prevLink, filteredLinks } ;
137+ } , [ links ] ) ;
138+ const rootData = useQuery < StacValue | undefined > ( {
139+ queryKey : [ "stac-value" , rootLink ?. href ] ,
140+ enabled : ! ! rootLink ,
141+ queryFn : ( ) => rootLink && fetchStac ( rootLink . href ) ,
142+ } ) ;
143+ const searchLinks = useMemo ( ( ) => {
144+ return rootData . data ?. links ?. filter ( ( link ) => link . rel === "search" ) ;
145+ } , [ rootData . data ] ) ;
146+ const collectionsResult = useInfiniteQuery ( {
147+ queryKey : [ "stac-collections" , collectionsLink ?. href ] ,
148+ queryFn : async ( { pageParam } ) => {
149+ if ( pageParam ) {
150+ return await fetch ( pageParam ) . then ( ( response ) => {
151+ if ( response . ok ) return response . json ( ) ;
152+ else
153+ throw new Error (
154+ `Error while fetching collections from ${ pageParam } `
155+ ) ;
156+ } ) ;
157+ } else {
158+ return null ;
159+ }
160+ } ,
161+ initialPageParam : collectionsLink ?. href ,
162+ getNextPageParam : ( lastPage : StacCollections | null ) =>
163+ lastPage ?. links ?. find ( ( link ) => link . rel == "next" ) ?. href ,
164+ enabled : ! ! collectionsLink ,
165+ } ) ;
166+ useEffect ( ( ) => {
167+ setCollections (
168+ collectionsResult . data ?. pages . flatMap ( ( page ) => page ?. collections || [ ] )
169+ ) ;
170+ if ( collectionsResult . data ?. pages . at ( 0 ) ?. numberMatched )
171+ setNumberOfCollections ( collectionsResult . data ?. pages [ 0 ] ?. numberMatched ) ;
172+ } , [ collectionsResult . data , setCollections ] ) ;
173+ useEffect ( ( ) => {
174+ if (
175+ fetchAllCollections &&
176+ ! collectionsResult . isFetching &&
177+ collectionsResult . hasNextPage
178+ )
179+ collectionsResult . fetchNextPage ( ) ;
180+ } , [ fetchAllCollections , collectionsResult ] ) ;
181+ useEffect ( ( ) => {
182+ setFetchAllCollections ( false ) ;
183+ setNumberOfCollections ( undefined ) ;
184+ } , [ value ] ) ;
104185
105186 // Handled by the value
106187 if ( properties ?. description ) delete properties [ "description" ] ;
107- const thumbnailAsset =
108- assets &&
109- ( ( Object . keys ( assets ) . includes ( "thumbnail" ) && assets [ "thumbnail" ] ) ||
110- Object . values ( assets ) . find ( ( asset ) =>
111- asset . roles ?. includes ( "thumbnail" )
112- ) ) ;
113- const nextLink = links ?. find ( ( link ) => link . rel === "next" ) ;
114- const prevLink = links ?. find ( ( link ) => link . rel === "previous" ) ;
115- // We already provide linked children and items in their own pane.
116- const filteredLinks = links ?. filter (
117- ( link ) => link . rel !== "child" && link . rel !== "item"
118- ) ;
188+ const thumbnailAsset = useMemo ( ( ) => {
189+ return (
190+ assets &&
191+ ( ( Object . keys ( assets ) . includes ( "thumbnail" ) && assets [ "thumbnail" ] ) ||
192+ Object . values ( assets ) . find ( ( asset ) =>
193+ asset . roles ?. includes ( "thumbnail" )
194+ ) )
195+ ) ;
196+ } , [ assets ] ) ;
119197
120198 useEffect ( ( ) => {
121199 setItems ( undefined ) ;
122200 } , [ search , setItems ] ) ;
123201
124202 return (
125203 < Box p = { 4 } overflow = { "scroll" } maxH = { "80dvh" } >
126- { ( href && value && (
127- < Value
128- value = { value }
129- thumbnailAsset = { thumbnailAsset }
130- href = { href }
131- setHref = { setHref }
132- nextLink = { nextLink }
133- prevLink = { prevLink }
134- />
135- ) ) ||
136- ( error && (
137- < Alert . Root status = { "error" } >
138- < Alert . Indicator />
139- < Alert . Content >
140- < Alert . Title > Error while fetching STAC value</ Alert . Title >
141- < Alert . Description > { error . toString ( ) } </ Alert . Description >
142- </ Alert . Content >
143- </ Alert . Root >
204+ < Stack gap = { 4 } >
205+ { ( href && value && (
206+ < Value
207+ value = { value }
208+ thumbnailAsset = { thumbnailAsset }
209+ href = { href }
210+ setHref = { setHref }
211+ nextLink = { nextLink }
212+ prevLink = { prevLink }
213+ />
144214 ) ) ||
145- ( href && < SkeletonText /> ) || (
146- < Introduction setHref = { setHref } fileUpload = { fileUpload } />
215+ ( error && (
216+ < Alert . Root status = { "error" } >
217+ < Alert . Indicator />
218+ < Alert . Content >
219+ < Alert . Title > Error while fetching STAC value</ Alert . Title >
220+ < Alert . Description > { error . toString ( ) } </ Alert . Description >
221+ </ Alert . Content >
222+ </ Alert . Root >
223+ ) ) ||
224+ ( href && < SkeletonText /> ) || (
225+ < Introduction setHref = { setHref } fileUpload = { fileUpload } />
226+ ) }
227+
228+ { collectionsResult . hasNextPage && (
229+ < Card . Root size = { "sm" } variant = { "outline" } >
230+ < Card . Header >
231+ < Heading size = { "sm" } > Collection pagination</ Heading >
232+ </ Card . Header >
233+ < Card . Body >
234+ < ButtonGroup size = { "xs" } variant = { "surface" } >
235+ < Button
236+ disabled = { fetchAllCollections || collectionsResult . isFetching }
237+ onClick = { ( ) => {
238+ if (
239+ ! collectionsResult . isFetching &&
240+ collectionsResult . hasNextPage
241+ )
242+ collectionsResult . fetchNextPage ( ) ;
243+ } }
244+ >
245+ Fetch more collections < LuStepForward />
246+ </ Button >
247+ < Button
248+ onClick = { ( ) =>
249+ setFetchAllCollections ( ( previous ) => ! previous )
250+ }
251+ >
252+ { ( fetchAllCollections && (
253+ < >
254+ Pause fetching collections < LuPause />
255+ </ >
256+ ) ) || (
257+ < >
258+ Fetch all collections < LuPlay />
259+ </ >
260+ ) }
261+ </ Button >
262+ </ ButtonGroup >
263+ </ Card . Body >
264+ </ Card . Root >
147265 ) }
266+ </ Stack >
148267
149268 { value && (
150269 < Accordion . Root multiple size = { "sm" } variant = { "enclosed" } mt = { 4 } >
@@ -164,7 +283,7 @@ export default function Panel({
164283 < >
165284 Collections{ " " }
166285 { ( filteredCollections &&
167- `(${ filteredCollections ?. length } /${ collections . length } )` ) ||
286+ `(${ filteredCollections ?. length } /${ numberOfCollections || collections . length } )` ) ||
168287 `(${ collections . length } )` }
169288 </ >
170289 }
0 commit comments