Skip to content

Commit b946cf4

Browse files
committed
Implement popover to preview items
1 parent eb74d32 commit b946cf4

File tree

3 files changed

+141
-101
lines changed

3 files changed

+141
-101
lines changed

packages/client/src/pages/CollectionDetail/index.tsx

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ import {
1818
HStack,
1919
VisuallyHidden,
2020
Skeleton,
21-
SkeletonText
21+
SkeletonText,
22+
Popover,
23+
PopoverTrigger,
24+
PopoverArrow,
25+
PopoverBody,
26+
PopoverContent
2227
} from '@chakra-ui/react';
2328
import { useCollection, useStacSearch } from '@developmentseed/stac-react';
2429
import {
2530
CollecticonEllipsisVertical,
31+
CollecticonEye,
2632
CollecticonPencil,
2733
CollecticonTextBlock
2834
} from '@devseed-ui/collecticons-chakra';
@@ -37,6 +43,7 @@ import { zeroPad } from '$utils/format';
3743
import { ButtonWithAuth } from '$components/auth/ButtonWithAuth';
3844
import { DeleteMenuItem } from '$components/DeleteMenuItem';
3945
import SmartLink from '$components/SmartLink';
46+
import { ItemMap } from '$pages/ItemDetail/ItemMap';
4047

4148
const dateFormat: Intl.DateTimeFormatOptions = {
4249
year: 'numeric',
@@ -270,27 +277,55 @@ function CollectionDetail() {
270277
to={`/collections/${id}/items/${item.id}`}
271278
renderMenu={() => {
272279
return (
273-
<Menu placement='bottom-end'>
274-
<MenuButton
275-
as={IconButton}
276-
aria-label='Options'
277-
icon={<CollecticonEllipsisVertical />}
278-
variant='outline'
279-
size='sm'
280-
/>
281-
<MenuList>
282-
<StacBrowserMenuItem
283-
resourcePath={`/collections/${id}/items/${item.id}`}
280+
<Flex gap={2}>
281+
<Menu placement='bottom-end'>
282+
<MenuButton
283+
as={IconButton}
284+
aria-label='Options'
285+
icon={<CollecticonEllipsisVertical />}
286+
variant='outline'
287+
size='sm'
284288
/>
285-
<MenuItem
286-
as={SmartLink}
287-
to={`/collections/${id}/items/${item.id}`}
288-
icon={<CollecticonTextBlock />}
289+
<MenuList>
290+
<StacBrowserMenuItem
291+
resourcePath={`/collections/${id}/items/${item.id}`}
292+
/>
293+
<MenuItem
294+
as={SmartLink}
295+
to={`/collections/${id}/items/${item.id}`}
296+
icon={<CollecticonTextBlock />}
297+
>
298+
View
299+
</MenuItem>
300+
</MenuList>
301+
</Menu>
302+
<Popover placement='top' isLazy>
303+
<PopoverTrigger>
304+
<IconButton
305+
aria-label='Preview'
306+
icon={<CollecticonEye />}
307+
variant='outline'
308+
size='sm'
309+
/>
310+
</PopoverTrigger>
311+
<PopoverContent
312+
boxShadow='sm'
313+
borderColor='base.200'
314+
borderWidth='2px'
289315
>
290-
View
291-
</MenuItem>
292-
</MenuList>
293-
</Menu>
316+
<PopoverArrow bg='base.200' />
317+
<PopoverBody
318+
p={0}
319+
overflow='hidden'
320+
borderRadius='md'
321+
>
322+
<Box h='15rem'>
323+
<ItemMap item={item} reuseMaps />
324+
</Box>
325+
</PopoverBody>
326+
</PopoverContent>
327+
</Popover>
328+
</Flex>
294329
);
295330
}}
296331
/>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React, { useEffect, useMemo, useState } from 'react';
2+
import Map, { Source, Layer, MapRef } from 'react-map-gl/maplibre';
3+
import { StacAsset } from 'stac-ts';
4+
import getBbox from '@turf/bbox';
5+
6+
import { BackgroundTiles } from '$components/Map';
7+
8+
const resultsOutline = {
9+
'line-color': '#C53030',
10+
'line-width': 2
11+
};
12+
13+
const resultsFill = {
14+
'fill-color': '#C53030',
15+
'fill-opacity': 0.1
16+
};
17+
18+
const cogMediaTypes = [
19+
'image/tiff; application=geotiff; profile=cloud-optimized',
20+
'image/vnd.stac.geotiff'
21+
];
22+
23+
export function ItemMap(
24+
props: { item: any } & React.ComponentProps<typeof Map>
25+
) {
26+
const { item, ...rest } = props;
27+
28+
const [map, setMap] = useState<MapRef>();
29+
const setMapRef = (m: MapRef) => setMap(m);
30+
31+
// Fit the map view around the current results bbox
32+
useEffect(() => {
33+
const bounds = item && getBbox(item);
34+
35+
if (map && bounds) {
36+
const [x1, y1, x2, y2] = bounds;
37+
map.fitBounds([x1, y1, x2, y2], { padding: 30, duration: 0 });
38+
}
39+
}, [item, map]);
40+
41+
const previewAsset = useMemo(() => {
42+
if (!item) return;
43+
44+
return Object.values(item.assets).reduce((preview, asset) => {
45+
const { type, href, roles } = asset as StacAsset;
46+
if (cogMediaTypes.includes(type || '')) {
47+
if (!preview) {
48+
return href;
49+
} else {
50+
if (roles && roles.includes('visual')) {
51+
return href;
52+
}
53+
}
54+
}
55+
return preview;
56+
}, undefined);
57+
}, [item]);
58+
59+
return (
60+
<Map ref={setMapRef} {...rest}>
61+
<BackgroundTiles />
62+
{previewAsset && (
63+
<Source
64+
id='preview'
65+
type='raster'
66+
tiles={[
67+
`http://tiles.rdnt.io/tiles/{z}/{x}/{y}@2x?url=${previewAsset}`
68+
]}
69+
tileSize={256}
70+
attribution="Background tiles: © <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap contributors</a>"
71+
>
72+
<Layer id='preview-tiles' type='raster' />
73+
</Source>
74+
)}
75+
<Source id='results' type='geojson' data={item}>
76+
<Layer id='results-line' type='line' paint={resultsOutline} />
77+
{!previewAsset && (
78+
<Layer id='results-fill' type='fill' paint={resultsFill} />
79+
)}
80+
</Source>
81+
</Map>
82+
);
83+
}

packages/client/src/pages/ItemDetail/index.tsx

Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useMemo, useState } from 'react';
1+
import React from 'react';
22
import { useParams } from 'react-router-dom';
33
import {
44
Box,
@@ -16,35 +16,17 @@ import {
1616
Skeleton,
1717
SkeletonText
1818
} from '@chakra-ui/react';
19-
import Map, { Source, Layer, MapRef } from 'react-map-gl/maplibre';
20-
import { StacAsset } from 'stac-ts';
2119
import { useItem } from '@developmentseed/stac-react';
2220
import {
2321
CollecticonEllipsisVertical,
2422
CollecticonTrashBin
2523
} from '@devseed-ui/collecticons-chakra';
26-
import getBbox from '@turf/bbox';
2724

2825
import { usePageTitle } from '../../hooks';
29-
import { BackgroundTiles } from '$components/Map';
3026
import AssetList from './AssetList';
3127
import { InnerPageHeader } from '$components/InnerPageHeader';
3228
import { StacBrowserMenuItem } from '$components/StacBrowserMenuItem';
33-
34-
const resultsOutline = {
35-
'line-color': '#C53030',
36-
'line-width': 2
37-
};
38-
39-
const resultsFill = {
40-
'fill-color': '#C53030',
41-
'fill-opacity': 0.1
42-
};
43-
44-
const cogMediaTypes = [
45-
'image/tiff; application=geotiff; profile=cloud-optimized',
46-
'image/vnd.stac.geotiff'
47-
];
29+
import { ItemMap } from './ItemMap';
4830

4931
const dateFormat: Intl.DateTimeFormatOptions = {
5032
year: 'numeric',
@@ -58,37 +40,6 @@ function ItemDetail() {
5840
const itemResource = `${process.env.REACT_APP_STAC_API}/collections/${collectionId}/items/${itemId}`;
5941
const { item, state } = useItem(itemResource);
6042

61-
const [map, setMap] = useState<MapRef>();
62-
const setMapRef = (m: MapRef) => setMap(m);
63-
64-
// Fit the map view around the current results bbox
65-
useEffect(() => {
66-
const bounds = item && getBbox(item);
67-
68-
if (map && bounds) {
69-
const [x1, y1, x2, y2] = bounds;
70-
map.fitBounds([x1, y1, x2, y2], { padding: 30, duration: 0 });
71-
}
72-
}, [item, map]);
73-
74-
const previewAsset = useMemo(() => {
75-
if (!item) return;
76-
77-
return Object.values(item.assets).reduce((preview, asset) => {
78-
const { type, href, roles } = asset as StacAsset;
79-
if (cogMediaTypes.includes(type || '')) {
80-
if (!preview) {
81-
return href;
82-
} else {
83-
if (roles && roles.includes('visual')) {
84-
return href;
85-
}
86-
}
87-
}
88-
return preview;
89-
}, undefined);
90-
}, [item]);
91-
9243
if (!item || state === 'LOADING') {
9344
return (
9445
<Box p={8}>
@@ -216,36 +167,7 @@ function ItemDetail() {
216167
<VisuallyHidden>Spacial extent</VisuallyHidden>
217168
</Heading>
218169
<Box position='absolute' inset='0'>
219-
<Map ref={setMapRef}>
220-
<BackgroundTiles />
221-
{previewAsset && (
222-
<Source
223-
id='preview'
224-
type='raster'
225-
tiles={[
226-
`http://tiles.rdnt.io/tiles/{z}/{x}/{y}@2x?url=${previewAsset}`
227-
]}
228-
tileSize={256}
229-
attribution="Background tiles: © <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap contributors</a>"
230-
>
231-
<Layer id='preview-tiles' type='raster' />
232-
</Source>
233-
)}
234-
<Source id='results' type='geojson' data={item}>
235-
<Layer
236-
id='results-line'
237-
type='line'
238-
paint={resultsOutline}
239-
/>
240-
{!previewAsset && (
241-
<Layer
242-
id='results-fill'
243-
type='fill'
244-
paint={resultsFill}
245-
/>
246-
)}
247-
</Source>
248-
</Map>
170+
<ItemMap item={item} />
249171
</Box>
250172
</Flex>
251173
</GridItem>

0 commit comments

Comments
 (0)