Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Basemap-switcher] Use a image based basemap switcher #362

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 111 additions & 26 deletions src/packages/basemap-switcher/BasemapSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)
// SPDX-License-Identifier: Apache-2.0
import { Box, Flex, Tooltip, useToken } from "@open-pioneer/chakra-integration";
import {
Box,
Button,
Flex,
Menu,
MenuButton,
MenuList,
Tooltip,
useToken
} from "@open-pioneer/chakra-integration";
import { Image, MenuItem } from "@chakra-ui/react";
import { Layer, MapModel, useMapModel } from "@open-pioneer/map";
import { useIntl } from "open-pioneer:react-hooks";
import { FC, useCallback, useMemo, useRef, useSyncExternalStore } from "react";
Expand Down Expand Up @@ -36,6 +46,19 @@ export interface SelectOption {
layer: Layer | undefined;
}

export interface BasemapOnMapSwitcherImageProps {
selectedImageLabel: ImageLabelSwitchObject;
choosableImageLabel: ImageLabelSwitchObject[];
selectedImageStyle?: { width: string; height: string };
choosableImageStyle?: { width: string; height: string };
}

export interface ImageLabelSwitchObject {
image: string;
label: string;
callBack: () => void;
}

/**
* These are special properties for the BasemapSwitcher.
*/
Expand Down Expand Up @@ -67,6 +90,11 @@ export interface BasemapSwitcherProps extends CommonComponentProps {
* Do not use together with aria-label.
*/
"aria-label"?: string;

/**
* Optional use the image basemap switcher instead of the select basemap switcher.
*/
imageBasemapSwitcher?: BasemapOnMapSwitcherImageProps;
}

/**
Expand All @@ -78,10 +106,18 @@ export const BasemapSwitcher: FC<BasemapSwitcherProps> = (props) => {
mapId,
allowSelectingEmptyBasemap = false,
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledBy
"aria-labelledby": ariaLabelledBy,
imageBasemapSwitcher = {
selectedImageLabel: null,
choosableImageLabel: [],
selectedImageStyle: { width: "10px", height: "10px" },
choosableImageStyle: { width: "60px", height: "40px" }
}
} = props;
const { containerProps } = useCommonComponentProps("basemap-switcher", props);
const emptyBasemapLabel = intl.formatMessage({ id: "emptyBasemapLabel" });
const { selectedImageLabel, choosableImageLabel, selectedImageStyle, choosableImageStyle } =
imageBasemapSwitcher;

const { map } = useMapModel(mapId);
const baseLayers = useBaseLayers(map);
Expand Down Expand Up @@ -116,30 +152,60 @@ export const BasemapSwitcher: FC<BasemapSwitcherProps> = (props) => {
return (
<Box {...containerProps}>
{map ? (
<Select<SelectOption>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it better do have a separate component ImageBaseampSwitcher in the same package.
That would make the code more comprehensible and avoid the problem that some of the component's props only apply to either default or image mode.

aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
className="basemap-switcher-select"
classNamePrefix="react-select"
options={options}
value={selectedLayer}
onChange={(option) => option && activateLayer(option.value)}
isClearable={false}
isSearchable={false}
menuPosition="fixed"
// optionLabel is used by screenreaders
getOptionLabel={(option) =>
option.layer !== undefined
? option.layer.title +
(option.layer.loadState === "error"
? " " + intl.formatMessage({ id: "layerNotAvailable" })
: "")
: emptyBasemapLabel
}
isOptionDisabled={(option) => option?.layer?.loadState === "error"}
components={components}
chakraStyles={chakraStyles}
/>
selectedImageLabel && choosableImageLabel ? (
<Menu>
<Flex>
<Tooltip label={selectedImageLabel.label}>
<MenuButton as={Button}>
<Image
width={selectedImageStyle?.width || "40px"}
height={selectedImageStyle?.height || "60py"}
src={selectedImageLabel.image}
></Image>
</MenuButton>
</Tooltip>
</Flex>
<MenuList display="contents">
{choosableImageLabel.map((imageLabel, index) => {
return (
<BasemapOnMapSwitcherElement
key={imageLabel.label + index}
src={imageLabel.image}
label={imageLabel.label}
callback={imageLabel.callBack}
width={choosableImageStyle?.width}
height={choosableImageStyle?.height}
/>
);
})}
</MenuList>
</Menu>
) : (
<Select<SelectOption>
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
className="basemap-switcher-select"
classNamePrefix="react-select"
options={options}
value={selectedLayer}
onChange={(option) => option && activateLayer(option.value)}
isClearable={false}
isSearchable={false}
menuPosition="fixed"
// optionLabel is used by screenreaders
getOptionLabel={(option) =>
option.layer !== undefined
? option.layer.title +
(option.layer.loadState === "error"
? " " + intl.formatMessage({ id: "layerNotAvailable" })
: "")
: emptyBasemapLabel
}
isOptionDisabled={(option) => option?.layer?.loadState === "error"}
components={components}
chakraStyles={chakraStyles}
/>
)
) : null}
</Box>
);
Expand Down Expand Up @@ -294,3 +360,22 @@ function useChakraStyles() {
return chakraStyles;
}, [dropDownBackground, borderColor]);
}

interface BasemapOnMapSwitcherElementProps {
src: string;
label: string;
callback: () => void;
width?: string;
height?: string;
}

export function BasemapOnMapSwitcherElement(props: BasemapOnMapSwitcherElementProps) {
const { src, label, callback, width, height } = props;
return (
<MenuItem onClick={() => callback()}>
<Tooltip label={label}>
<Image src={src} width={width || "60px"} height={height || "40px"}></Image>
</Tooltip>
</MenuItem>
);
}
63 changes: 63 additions & 0 deletions src/packages/basemap-switcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,69 @@ Example:
</FormControl>
```

## Alternative Usage as image basemap switcher

```jsx
<BasemapSwitcher mapId={MAP_ID} imageBasemapSwitcher={imageBasemapSwitcher}></BasemapSwitcher>
```

Change to the alternative imageBasemapSwitcher.
This basemap switcher can be used if the basemap configurations are well known and should be represented
with an user-friendly image. Instead of the layer title, an image and tooltip can be used to represent
the available choices. The Available choices have to be managed by yourself with the usage of
`BasemapOnMapSwitcherImageProps`.

```jsx
const [imageLabel, setImageLabel] = useState({
image: firstImage, // The image that is shown on the front
label: BASE_MAP_TOOLTIP, // The tooltip that should be shown on hover
callBack: () => {} // The callback that should be called when the image is clicked. In most cases no needed for the front
} as ImageLabelSwitchObject);

const [currentSelection, setCurrentSelection] = useState<ImageLabelSwitchObject[]>([]);

useEffect(() => {
switch (imageLabel.label) {
case BASE_MAP_TOOLTIP: {
setCurrentSelection([
{
image: firstImage, // The image that should be shown on the front
label: BASE_MAP_TOOLTIP, // The tooltip that should be shown on hover
callBack: () => {} // The callback that should be executed when the image is clicked
},
{
image: secondImage,
label: EMPTY_MAP_TOOLTIP,
callBack: () => {
setImageLabel({
image: secondImage, // The image that should be shown on the front
label: EMPTY_MAP_TOOLTIP, // The tooltip that should be shown on hover
callBack: () => {} // The callback that should be executed when the image is clicked
});
}
}
]);
break;
}
case [...]
}
},[imageLabel, map]);

const imageBasemapSwitcher = useMemo(() => {
return {
selectedImageLabel: imageLabel,
choosableImageLabel: currentSelection
};
}, [imageLabel, currentSelection]);

return (
<BasemapSwitcher
mapId={MAP_ID}
imageBasemapSwitcher={imageBasemapSwitcher}
></BasemapSwitcher>
);
```

## License

Apache-2.0 (see `LICENSE` file)
Loading