Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d537eb0
add ZoomBox with zoomLevel
clarasb Aug 1, 2025
3bffd99
zoom level - round decimal digits
clarasb Aug 1, 2025
7c5f09a
add ZoomBox.stories.tsx
clarasb Aug 1, 2025
50f15fd
add DatasetLevel
clarasb Aug 12, 2025
40b9941
update ZoomBox
clarasb Aug 12, 2025
9a99488
update CHANGES.md
clarasb Aug 12, 2025
bda8644
update ZoomBox.tsx
clarasb Aug 12, 2025
dcaaa05
update ZoomBox.tsx
clarasb Aug 12, 2025
1257ebf
add dataset levels info to Info-Panel
clarasb Aug 13, 2025
25821b4
change position and layout of box
clarasb Aug 15, 2025
b9e01c3
add visibility control for zoomBox
clarasb Aug 18, 2025
7f2e7c3
update styling - remove shadow
clarasb Aug 18, 2025
ebd5577
Apply suggestions from code review
clarasb Sep 18, 2025
f3aa1ec
create ZoomBox directory, update Settings Dialog and add translations
clarasb Sep 18, 2025
820afc8
add getDatasetLevelSelector
clarasb Sep 19, 2025
38e22f1
add zoomLevel to controlState
clarasb Sep 23, 2025
ef8bd91
fix typing
clarasb Sep 23, 2025
c3dec9c
add description to getDatasetLevel()
clarasb Sep 23, 2025
3d0f422
save DatasetZLevel in controlState
clarasb Oct 1, 2025
2194032
Merge branch 'main' into clarasb-287-add_zoom_level
clarasb Oct 1, 2025
6781643
rename ZoomBox to ZoomInfoBox and extend the information on dataset l…
clarasb Oct 13, 2025
fb0edca
update setZoomLevel and add setDatasetZLevel
clarasb Oct 13, 2025
eff2a4b
add setDatasetZLevel to hook dependency
clarasb Oct 13, 2025
ab7f31a
rename showZoomBox to showZoomInfoBox
clarasb Oct 14, 2025
5eab41d
formatting
clarasb Oct 14, 2025
d842ea7
update description for getDatasetLevel
clarasb Oct 14, 2025
b0db37a
add zoom information box to documentation
clarasb Oct 22, 2025
7de1aee
Merge branch 'main' into clarasb-287-add_zoom_level
clarasb Oct 22, 2025
cd1f58c
update ZoomInfoBox.stories.tsx
clarasb Oct 22, 2025
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
9 changes: 8 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
### Fixes

* Applied workaround for a bug in `html-to-image` libary that causes an
issuesfor the export of screenshorts (charts and map) in the Firefox
issue for the export of screenshorts (charts and map) in the Firefox
browser.

### New Features
Expand All @@ -30,6 +30,13 @@
Window. If this feature is configured, an `About` window can be opened
with a button in the header and it will be shown initially while
data is loading. (#508)

* A zoom-level indicator was added to the map. This box displays the current
zoom level of the map and the dataset resolution level used for displaying it.
The visibility of this feature can be controlled in the settings. The initial visibility
can be set in `config.json` (`"branding":{ "showZoomBox": true, ...`),
the default is `false`. In addition, the total number of dataset levels has been
added to the metadata in the Info panel. (#287)

## Changes in version 1.6.1

Expand Down
16 changes: 15 additions & 1 deletion src/actions/controlActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,19 @@ export function updateUserColorBars(

////////////////////////////////////////////////////////////////////////////////

export const SET_ZOOM_LEVEL = "SET_ZOOM_LEVEL";

export interface SetZoomLevel {
type: typeof SET_ZOOM_LEVEL;
zoomLevel: number | undefined;
}

export function setZoomLevel(zoomLevel: number | undefined): SetZoomLevel {
return { type: SET_ZOOM_LEVEL, zoomLevel };
}

////////////////////////////////////////////////////////////////////////////////

export type ControlAction =
| SelectDataset
| UpdateDatasetPlaceGroup
Expand Down Expand Up @@ -860,4 +873,5 @@ export type ControlAction =
| SetMapPointInfoBoxEnabled
| SetVariableCompareMode
| UpdateVariableSplitPos
| FlyTo;
| FlyTo
| SetZoomLevel;
1 change: 1 addition & 0 deletions src/components/InfoPanel/DatasetInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const DatasetInfoCard: React.FC<DatasetInfoContentProps> = ({
dataset.bbox.map((x) => getLabelForValue(x, 3)).join(", "),
],
[i18n.get("Spatial reference system"), dataset.spatialRef],
[i18n.get("Levels"), dataset.resolutions.length],
];
content = (
<InfoCardContent>
Expand Down
10 changes: 10 additions & 0 deletions src/components/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,16 @@ const SettingsDialog: React.FC<SettingsDialogProps> = ({
updateSettings={updateSettings}
/>
</SettingsSubPanel>
<SettingsSubPanel
label={i18n.get("Number of resolution levels")}
value={getOnOff(settings.showZoomBox)}
>
<ToggleSetting
propertyName={"showZoomBox"}
settings={settings}
updateSettings={updateSettings}
/>
</SettingsSubPanel>
<SettingsSubPanel label={i18n.get("On dataset selection")}>
<TextField
variant="standard"
Expand Down
14 changes: 14 additions & 0 deletions src/components/Viewer/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ interface ViewerProps {
variableSplitPos?: number;
onMapRef?: (map: OlMap | null) => void;
importUserPlacesFromText?: (text: string) => void;
setZoomLevel?: (zoomLevel: number | undefined) => void;
zoomBox?: MapElement;
}

export default function Viewer({
Expand Down Expand Up @@ -158,6 +160,8 @@ export default function Viewer({
imageSmoothing,
variableSplitPos,
onMapRef,
zoomBox,
setZoomLevel,
}: ViewerProps) {
theme = useTheme();

Expand Down Expand Up @@ -279,6 +283,14 @@ export default function Viewer({
}
};

const handleMapZoom = (event: OlMapBrowserEvent<UIEvent>) => {
const zoomLevel = event.target.getZoom();

if (setZoomLevel) {
setZoomLevel(zoomLevel);
}
};

const handleDrawEnd = (event: DrawEvent) => {
if (map !== null && addDrawnUserPlace && mapInteraction !== "Select") {
const feature = event.feature;
Expand Down Expand Up @@ -356,6 +368,7 @@ export default function Viewer({
<Map
id={mapId}
onClick={(event) => handleMapClick(event)}
onZoom={(event) => handleMapZoom(event)}
onMapRef={handleMapRef}
mapObjects={MAP_OBJECTS}
isStale={true}
Expand Down Expand Up @@ -426,6 +439,7 @@ export default function Viewer({
{mapPointInfoBox}
{mapControlActions}
{mapSplitter}
{zoomBox}
<ScaleLine bar={false} />
</Map>
</ErrorBoundary>
Expand Down
30 changes: 30 additions & 0 deletions src/components/ZoomBox/ZoomBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Meta, StoryObj } from "@storybook/react";
import ZoomBox from "./ZoomBox";

const meta: Meta = {
component: ZoomBox,
title: "ZoomBox",
parameters: {
// Optional parameter to center the component in the Canvas.
// More info: https://storybook.js.org/docs/configure/story-layout
layout: "centered",
},
// This component will have an automatically generated Autodocs entry:
// https://storybook.js.org/docs/writing-docs/autodocs
tags: ["autodocs"],
} satisfies Meta<typeof ZoomBox>;

// noinspection JSUnusedGlobalSymbols
export default meta;

type Story = StoryObj<typeof meta>;

// noinspection JSUnusedGlobalSymbols
export const Default: Story = {
args: {
style: {},
zoomLevel: 10,
datasetLevel: 3,
visibility: true,
},
};
104 changes: 104 additions & 0 deletions src/components/ZoomBox/ZoomBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2019-2025 by xcube team and contributors
* Permissions are hereby granted under the terms of the MIT License:
* https://opensource.org/licenses/MIT.
*/

import { CSSProperties } from "react";
import { alpha } from "@mui/material/styles";
import Box from "@mui/material/Box";
import Divider from "@mui/material/Divider";
import Typography from "@mui/material/Typography";

import { getLabelForValue } from "@/util/label";
import { makeStyles } from "@/util/styles";
import { getBorderStyle } from "@/components/ColorBarLegend/style";

const styles = makeStyles({
container: (theme) => ({
position: "absolute",
zIndex: 1000,
border: getBorderStyle(theme),
borderRadius: "4px",
backgroundColor: alpha(theme.palette.background.default, 0.85),
minWidth: "120px",
paddingLeft: theme.spacing(1.5),
paddingRight: theme.spacing(1.5),
paddingBottom: theme.spacing(0.5),
paddingTop: theme.spacing(0.5),
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
gap: 1,
}),
title: {
fontSize: "0.8rem",
fontWeight: "normal",
wordBreak: "break-word",
wordWrap: "break-word",
},
subTitle: {
fontSize: "0.7rem",
fontWeight: "lighter",
wordBreak: "break-word",
wordWrap: "break-word",
},
});

interface ZoomBoxProps {
style: CSSProperties;
zoomLevel: number | undefined;
datasetLevel: number | undefined;
visibility: boolean;
}

export default function ZoomBox({
style,
zoomLevel,
datasetLevel,
visibility,
}: ZoomBoxProps): JSX.Element | null {
if (!visibility) {
return null;
}

return (
<div>
<Box
//className="ol-unselectable ol-control"
sx={styles.container}
style={style}
>
<Box>
<Typography sx={styles.title} variant="subtitle1" color="textPrimary">
{"Zoom"}
</Typography>
<Typography
sx={styles.subTitle}
variant="subtitle2"
color="textPrimary"
>
{zoomLevel !== undefined
? getLabelForValue(zoomLevel, 4)
: "no zoom level"}
</Typography>
</Box>
<Divider orientation="vertical" flexItem />
<Box>
<Typography sx={styles.title} variant="subtitle1" color="textPrimary">
{"Level"}
</Typography>
<Typography
sx={styles.subTitle}
variant="subtitle2"
color="textPrimary"
>
{datasetLevel !== undefined
? getLabelForValue(datasetLevel, 4)
: "no dataset level"}
</Typography>
</Box>
</Box>
</div>
);
}
9 changes: 9 additions & 0 deletions src/components/ZoomBox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2019-2025 by xcube team and contributors
* Permissions are hereby granted under the terms of the MIT License:
* https://opensource.org/licenses/MIT.
*/

import ZoomBox from "./ZoomBox";

export default ZoomBox;
19 changes: 18 additions & 1 deletion src/components/ol/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface MapProps extends OlMapOptions {
children?: React.ReactNode;
mapObjects?: { [id: string]: OlBaseObject };
onClick?: (event: OlMapBrowserEvent<UIEvent>) => void;
onZoom?: (event: OlMapBrowserEvent<UIEvent>) => void;
onMapRef?: (map: OlMap | null) => void;
isStale?: boolean;
onDropFiles?: (files: File[]) => void;
Expand All @@ -53,6 +54,7 @@ const DEFAULT_CONTAINER_STYLE: React.CSSProperties = {
export class Map extends React.Component<MapProps, MapState> {
private readonly contextValue: MapContext;
private clickEventsKey: OlEventsKey | null = null;
private zoomEventsKey: OlEventsKey | null = null;

constructor(props: MapProps) {
super(props);
Expand All @@ -78,6 +80,7 @@ export class Map extends React.Component<MapProps, MapState> {
const mapDiv = this.contextValue.mapDiv!;

let map: OlMap | null = null;
let view: OlView | null = null;
if (this.props.isStale) {
const mapObject = this.contextValue.mapObjects[id];
if (mapObject instanceof OlMap) {
Expand All @@ -86,6 +89,10 @@ export class Map extends React.Component<MapProps, MapState> {
if (this.clickEventsKey) {
map.un("click", this.clickEventsKey.listener);
}
view = map?.getView();
if (this.zoomEventsKey) {
view.un("change:resolution", this.zoomEventsKey.listener);
}
}
}

Expand All @@ -104,11 +111,13 @@ export class Map extends React.Component<MapProps, MapState> {
});
}

view = map?.getView();

this.contextValue.map = map;
this.contextValue.mapObjects[id] = map;

this.clickEventsKey = map.on("click", this.handleClick);

this.zoomEventsKey = view.on("change:resolution", this.handleZoom);
//map.set('objectId', this.props.id);
map.updateSize();

Expand Down Expand Up @@ -182,6 +191,7 @@ export class Map extends React.Component<MapProps, MapState> {
const mapOptions = { ...this.props };
delete mapOptions["children"];
delete mapOptions["onClick"];
delete mapOptions["onZoom"];
delete mapOptions["onDropFiles"];
return mapOptions;
}
Expand All @@ -193,6 +203,13 @@ export class Map extends React.Component<MapProps, MapState> {
}
};

private handleZoom = (event: OlEvent) => {
const onZoom = this.props.onZoom;
if (onZoom) {
onZoom(event as OlMapBrowserEvent<UIEvent>);
}
};

private handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
if (this.props.onDropFiles) {
event.preventDefault();
Expand Down
5 changes: 4 additions & 1 deletion src/connected/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ import {
} from "@/actions/dataActions";
import _Viewer from "@/components/Viewer";
import { userPlaceGroupsSelector } from "@/selectors/dataSelectors";
import { selectPlace } from "@/actions/controlActions";
import { selectPlace, setZoomLevel } from "@/actions/controlActions";
import ColorBarLegend from "./ColorBarLegend";
import ColorBarLegend2 from "./ColorBarLegend2";
import MapSplitter from "./MapSplitter";
import MapPointInfoBox from "./MapPointInfoBox";
import MapControlActions from "./MapControlActions";
import ZoomBox from "./ZoomBox";

interface OwnProps {
onMapRef?: (map: OlMap | null) => void;
Expand Down Expand Up @@ -68,6 +69,7 @@ const mapStateToProps = (state: AppState, ownProps: OwnProps) => {
imageSmoothing: imageSmoothingSelector(state),
variableSplitPos: state.controlState.variableSplitPos,
onMapRef: ownProps.onMapRef,
zoomBox: <ZoomBox />,
};
};

Expand All @@ -76,6 +78,7 @@ const mapDispatchToProps = {
addDrawnUserPlace,
importUserPlacesFromText,
selectPlace,
setZoomLevel,
};

const Viewer = connect(mapStateToProps, mapDispatchToProps)(_Viewer);
Expand Down
Loading