@opengeoweb/soundings 14.5.1 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js CHANGED
@@ -7,7 +7,7 @@ import { selectorMemoizationOptions, uiSelectors, uiTypes, uiActions, genericSel
7
7
  import { ParseISOTimeRangeDuration, parseISO8601IntervalToDateInterval, webmapUtils } from '@opengeoweb/webmap';
8
8
  import { MapFeatureClass } from '@opengeoweb/webmap-react';
9
9
  import { ReactSortable } from 'react-sortablejs';
10
- import { ArrowDropDownDown, DragHandle, Layers, Sources } from '@opengeoweb/theme';
10
+ import { ArrowDropDownDown, DragHandle, Layers, Sources, Visibility, VisibilityOff } from '@opengeoweb/theme';
11
11
  import * as d3 from 'd3';
12
12
  import i18n from 'i18next';
13
13
  export { default as i18n } from 'i18next';
@@ -683,8 +683,7 @@ const drawSoundingsTooltip = ({
683
683
  return getParamHtml(dataSet.hoveredDewpoint, index, '°C', index === interpolatedData.length - 1, dataSet.color);
684
684
  }).join(''));
685
685
  // Wind
686
- windRefd3?.style('display', 'block').style('left', `${width + margin.left + margin.right - 55}px`) // -55 to account for width of element
687
- .style('top', `${pressureY}px`).html(() => {
686
+ windRefd3?.style('display', 'block').style('left', `${width + margin.left + margin.right}px`).style('top', `${pressureY}px`).html(() => {
688
687
  const windSpeedStrings = interpolatedData.map((dataSet, index) => {
689
688
  return getParamHtml(dataSet.hoveredWindSpeed, index, 'm/s', index === interpolatedData.length - 1, dataSet.color, true);
690
689
  });
@@ -1870,6 +1869,19 @@ const soundingsStyle = {
1870
1869
  }
1871
1870
  };
1872
1871
  const WIND_BARB_OFFSET_X = 35;
1872
+ /**
1873
+ * Check if the sounding data is effectively empty.
1874
+ * Data can be empty in two ways:
1875
+ * 1. No sources selected: data = []
1876
+ * 2. No locations selected: data = [[], []] (sources exist but all have empty arrays)
1877
+ */
1878
+ const isDataEmpty = data => {
1879
+ if (!data || data.length === 0) {
1880
+ return true;
1881
+ }
1882
+ // Check if all inner arrays are empty (no locations selected)
1883
+ return data.every(features => features.length === 0);
1884
+ };
1873
1885
  const WindBarbs = ({
1874
1886
  d3GraphId,
1875
1887
  data,
@@ -1951,7 +1963,8 @@ const WindBarbs = ({
1951
1963
  columnOffsetX: index * WIND_BARB_OFFSET_X
1952
1964
  });
1953
1965
  });
1954
- tooltipDataRef.current = constructTooltipData(data);
1966
+ // Clear or update tooltip data based on whether we have actual data
1967
+ tooltipDataRef.current = isDataEmpty(data) ? null : constructTooltipData(data);
1955
1968
  // Update previous inner height after drawing so animation uses the prior value
1956
1969
  prevInnerHeightRef.current = height;
1957
1970
  // eslint-disable-next-line consistent-return, @typescript-eslint/explicit-function-return-type
@@ -1964,6 +1977,11 @@ const WindBarbs = ({
1964
1977
  windRef.current && (windRef.current.style.display = 'none');
1965
1978
  return;
1966
1979
  }
1980
+ // Hide tooltip when data is empty (all sources/locations removed)
1981
+ if (isDataEmpty(data)) {
1982
+ windRef.current.style.display = 'none';
1983
+ return;
1984
+ }
1967
1985
  const rect = svgRectRef.current ?? svgRef.current.getBoundingClientRect();
1968
1986
  if (clientY == null || clientY < rect.top || clientY > rect.bottom) {
1969
1987
  windRef.current.style.display = 'none';
@@ -1991,15 +2009,12 @@ const WindBarbs = ({
1991
2009
  tooltipData: tooltipDataRef.current,
1992
2010
  diagramType
1993
2011
  });
1994
- }, [clientY, showTooltip, diagramType]);
1995
- useEffect(() => {
1996
- // Reset the tooltips when filteredData changes
1997
- if (windRef.current) {
1998
- windRef.current.style.display = 'none';
1999
- }
2000
- }, [data]);
2012
+ }, [clientY, showTooltip, diagramType, data]);
2001
2013
  return jsxs(Box, {
2002
2014
  ref: canvasRef,
2015
+ sx: {
2016
+ position: 'relative'
2017
+ },
2003
2018
  children: [jsx("svg", {
2004
2019
  ref: svgRef
2005
2020
  }), jsx(Box, {
@@ -2258,21 +2273,19 @@ const SoundingsAccordion = React.forwardRef(({
2258
2273
  });
2259
2274
 
2260
2275
  const SortableAccordions = ({
2276
+ soundingPanelsInOrder,
2261
2277
  data,
2262
2278
  size,
2263
2279
  chosenDiagramType,
2264
2280
  setChosenDiagramType,
2265
- viewRef
2281
+ viewRef,
2282
+ onOrderChange
2266
2283
  }) => {
2267
- const [accordions, setAccordions] = React.useState([{
2268
- id: 'soundings'
2269
- }, {
2270
- id: 'windBarbs'
2271
- }]);
2272
2284
  const [soundingsIsClosed, setSoundingsIsClosed] = React.useState(false);
2273
2285
  const [previousHeight, setPreviousHeight] = React.useState(size);
2274
2286
  const createPanel = React.useCallback(id => {
2275
- switch (id) {
2287
+ const normalizedId = id.toLowerCase().replace(/\s+/g, '');
2288
+ switch (normalizedId) {
2276
2289
  case 'soundings':
2277
2290
  return jsx(SoundingsAccordion, {
2278
2291
  accordionIsOpen: true,
@@ -2286,7 +2299,7 @@ const SortableAccordions = ({
2286
2299
  setSoundingsIsClosed: setSoundingsIsClosed,
2287
2300
  size: size,
2288
2301
  children: jsx(Soundings, {
2289
- d3GraphId: id,
2302
+ d3GraphId: normalizedId,
2290
2303
  data: data,
2291
2304
  diagramType: chosenDiagramType,
2292
2305
  svgDimensions: {
@@ -2301,13 +2314,13 @@ const SortableAccordions = ({
2301
2314
  }
2302
2315
  })
2303
2316
  });
2304
- case 'windBarbs':
2317
+ case 'windbarbs':
2305
2318
  return jsx(SoundingsAccordion, {
2306
2319
  maxWidth: 200,
2307
2320
  name: "Wind Barbs",
2308
2321
  children: jsx(WindBarbs, {
2309
2322
  animateResize: Boolean(soundingsIsClosed && previousHeight),
2310
- d3GraphId: id,
2323
+ d3GraphId: normalizedId,
2311
2324
  data: data,
2312
2325
  diagramType: chosenDiagramType,
2313
2326
  svgDimensions: {
@@ -2339,18 +2352,31 @@ const SortableAccordions = ({
2339
2352
  direction: "horizontal",
2340
2353
  draggable: ".sortable-item",
2341
2354
  handle: ".drag-handle",
2342
- list: accordions,
2343
- setList: setAccordions,
2355
+ list: soundingPanelsInOrder.map(panel => ({
2356
+ ...panel
2357
+ })),
2358
+ setList: list => onOrderChange(list.map(({
2359
+ id,
2360
+ name,
2361
+ isDisplayed
2362
+ }) => ({
2363
+ id,
2364
+ name,
2365
+ isDisplayed
2366
+ }))),
2344
2367
  style: {
2345
2368
  display: 'flex',
2346
2369
  gap: 4,
2347
2370
  width: '100%'
2348
2371
  },
2349
2372
  tag: "div",
2350
- children: accordions.map(({
2373
+ children: soundingPanelsInOrder.filter(entry => entry.isDisplayed).flatMap(({
2351
2374
  id
2352
2375
  }) => {
2353
2376
  const panel = createPanel(id);
2377
+ if (!panel) {
2378
+ return [];
2379
+ }
2354
2380
  return panel ? jsx(React.Fragment, {
2355
2381
  children: React.cloneElement(panel, {
2356
2382
  accordionId: id,
@@ -3029,7 +3055,11 @@ const useGetCollectionLocations = (collections, enabled) => {
3029
3055
  const initialSoundingsState = {
3030
3056
  selectedSources: {},
3031
3057
  selectedLocations: {},
3032
- selectedTime: new Date().toISOString()
3058
+ selectedTime: new Date().toISOString(),
3059
+ soundingPanels: {
3060
+ order: [],
3061
+ entities: {}
3062
+ }
3033
3063
  };
3034
3064
  const slice = createSlice({
3035
3065
  initialState: initialSoundingsState,
@@ -3057,6 +3087,20 @@ const slice = createSlice({
3057
3087
  selectedTime
3058
3088
  } = payloadAction.payload;
3059
3089
  draft.selectedTime = selectedTime;
3090
+ },
3091
+ addSoundingPanelsFromPreset: (draft, payloadAction) => {
3092
+ const {
3093
+ order,
3094
+ entities
3095
+ } = payloadAction.payload;
3096
+ draft.soundingPanels.order = order;
3097
+ draft.soundingPanels.entities = entities;
3098
+ },
3099
+ changeSoundingPanelOrder: (draft, payloadAction) => {
3100
+ const {
3101
+ newOrderOfIds
3102
+ } = payloadAction.payload;
3103
+ draft.soundingPanels.order = newOrderOfIds;
3060
3104
  }
3061
3105
  }
3062
3106
  });
@@ -3112,6 +3156,16 @@ const getSelectedSourcesAmount = createSelector(getSelectedSources, activeSource
3112
3156
  const getSelectedSourcesIds = createSelector(getSelectedSources, activeSources => {
3113
3157
  return activeSources.map(source => source.collectionId);
3114
3158
  }, selectorMemoizationOptions);
3159
+ const getSoundingPanelsInOrder = createSelector(getSoundingsStore, store => {
3160
+ const {
3161
+ order = [],
3162
+ entities = {}
3163
+ } = store.soundingPanels || {
3164
+ order: [],
3165
+ entities: {}
3166
+ };
3167
+ return order.map(id => entities[id]);
3168
+ }, selectorMemoizationOptions);
3115
3169
 
3116
3170
  var MenuOptions;
3117
3171
  (function (MenuOptions) {
@@ -3512,6 +3566,13 @@ const SoundingsConnect = ({
3512
3566
  data,
3513
3567
  isPending
3514
3568
  } = useMultipleSources(sourceSelections, locationSelections, selectedTime);
3569
+ const soundingPanelsInOrder = useSelector(store => getSoundingPanelsInOrder(store));
3570
+ const changeOrder = newOrder => {
3571
+ const orderedIds = newOrder.map(collection => collection.id);
3572
+ dispatch(soundingsActions.changeSoundingPanelOrder({
3573
+ newOrderOfIds: orderedIds
3574
+ }));
3575
+ };
3515
3576
  return jsxs(Fragment, {
3516
3577
  children: [!!error && jsx(AlertBanner, {
3517
3578
  shouldClose: true,
@@ -3568,8 +3629,10 @@ const SoundingsConnect = ({
3568
3629
  children: jsx(SortableAccordions, {
3569
3630
  chosenDiagramType: chosenDiagramType,
3570
3631
  data: data,
3632
+ onOrderChange: changeOrder,
3571
3633
  setChosenDiagramType: setChosenDiagramType,
3572
3634
  size: size,
3635
+ soundingPanelsInOrder: soundingPanelsInOrder,
3573
3636
  viewRef: viewRef
3574
3637
  })
3575
3638
  })
@@ -4005,27 +4068,114 @@ const LocationsMenuConnect = ({
4005
4068
  });
4006
4069
  };
4007
4070
 
4071
+ var SoundingManagerDummyPreset = {
4072
+ order: [
4073
+ "soundings",
4074
+ "windbarbs"
4075
+ ],
4076
+ entities: {
4077
+ soundings: {
4078
+ id: "soundings",
4079
+ name: "Soundings",
4080
+ isDisplayed: true
4081
+ },
4082
+ windbarbs: {
4083
+ id: "windbarbs",
4084
+ name: "Wind Barbs",
4085
+ isDisplayed: true
4086
+ }
4087
+ }
4088
+ };
4089
+ var preset = {
4090
+ SoundingManagerDummyPreset: SoundingManagerDummyPreset
4091
+ };
4092
+
4093
+ const SoundingManagerListItem = ({
4094
+ name,
4095
+ isDisplayed
4096
+ }) => {
4097
+ return jsx(Card, {
4098
+ className: "sortable-item",
4099
+ sx: {
4100
+ margin: '4px 0 2px',
4101
+ backgroundColor: isDisplayed ? 'white' : 'rgb(232, 232, 232)'
4102
+ },
4103
+ children: jsxs(Grid, {
4104
+ alignItems: "center",
4105
+ container: true,
4106
+ direction: "row",
4107
+ justifyContent: "flex-start",
4108
+ sx: {
4109
+ height: '32px'
4110
+ },
4111
+ children: [jsx(IconButton, {
4112
+ "aria-label": "drag-handle",
4113
+ className: "drag-handle",
4114
+ size: "small",
4115
+ sx: {
4116
+ width: '24px',
4117
+ height: '24px',
4118
+ paddingRight: '8px',
4119
+ paddingLeft: '8px'
4120
+ },
4121
+ children: jsx(DragHandle, {
4122
+ fontSize: "small"
4123
+ })
4124
+ }), jsx(Box, {
4125
+ sx: {
4126
+ display: 'flex',
4127
+ alignItems: 'center'
4128
+ },
4129
+ children: isDisplayed ? jsx(Visibility, {}) : jsx(VisibilityOff, {})
4130
+ }), jsxs(Box, {
4131
+ sx: {
4132
+ paddingRight: '8px',
4133
+ paddingLeft: '8px'
4134
+ },
4135
+ children: [" ", name, " "]
4136
+ })]
4137
+ })
4138
+ });
4139
+ };
4140
+
4008
4141
  const SoundingsManager = ({
4009
4142
  isOpen,
4010
4143
  onClose,
4011
4144
  onMouseDown,
4012
- order
4145
+ order,
4146
+ soundingPanelsInOrder,
4147
+ onOrderChange
4013
4148
  }) => {
4149
+ const sortableList = React.useMemo(() => soundingPanelsInOrder.map(collection => ({
4150
+ ...collection
4151
+ })), [soundingPanelsInOrder]);
4014
4152
  return jsx(ToolContainerDraggable, {
4015
4153
  isOpen: isOpen,
4016
4154
  minWidth: 300,
4017
4155
  onClose: onClose,
4018
4156
  onMouseDown: onMouseDown,
4019
4157
  order: order,
4020
- startPosition: {
4021
- top: 40,
4022
- left: 50
4023
- },
4024
- startSize: {
4025
- width: 320
4026
- },
4027
- title: "Sounding Manager",
4028
- children: "Soundings Manager"
4158
+ title: "Soundings Manager",
4159
+ children: jsx(ReactSortable, {
4160
+ animation: 200,
4161
+ direction: "vertical",
4162
+ draggable: ".sortable-item",
4163
+ handle: ".drag-handle",
4164
+ list: sortableList,
4165
+ setList: onOrderChange,
4166
+ style: {
4167
+ padding: '5px'
4168
+ },
4169
+ tag: "div",
4170
+ children: sortableList.map(({
4171
+ id,
4172
+ name,
4173
+ isDisplayed
4174
+ }) => jsx(SoundingManagerListItem, {
4175
+ isDisplayed: isDisplayed,
4176
+ name: name
4177
+ }, id))
4178
+ })
4029
4179
  });
4030
4180
  };
4031
4181
 
@@ -4036,11 +4186,25 @@ const SoundingsManagerConnect = () => {
4036
4186
  dialogOrder,
4037
4187
  setDialogOrder
4038
4188
  } = useSetupDialog(uiTypes.DialogTypes.SoundingsManager);
4189
+ const dispatch = useDispatch();
4190
+ React.useEffect(() => {
4191
+ const data = preset.SoundingManagerDummyPreset;
4192
+ dispatch(soundingsActions.addSoundingPanelsFromPreset(data));
4193
+ }, [dispatch]);
4194
+ const soundingPanelsInOrder = useSelector(store => getSoundingPanelsInOrder(store));
4195
+ const changeOrder = newOrder => {
4196
+ const orderedIds = newOrder.map(collection => collection.id);
4197
+ dispatch(soundingsActions.changeSoundingPanelOrder({
4198
+ newOrderOfIds: orderedIds
4199
+ }));
4200
+ };
4039
4201
  return jsx(SoundingsManager, {
4040
4202
  isOpen: isDialogOpen,
4041
4203
  onClose: onCloseDialog,
4042
4204
  onMouseDown: setDialogOrder,
4043
- order: dialogOrder
4205
+ onOrderChange: changeOrder,
4206
+ order: dialogOrder,
4207
+ soundingPanelsInOrder: soundingPanelsInOrder
4044
4208
  });
4045
4209
  };
4046
4210
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeoweb/soundings",
3
- "version": "14.5.1",
3
+ "version": "15.0.0",
4
4
  "description": "GeoWeb soundings library for the opengeoweb project",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,24 +8,24 @@
8
8
  "url": "git@gitlab.com:opengeoweb/opengeoweb.git"
9
9
  },
10
10
  "dependencies": {
11
- "@opengeoweb/api": "14.5.1",
11
+ "@opengeoweb/api": "15.0.0",
12
12
  "@tanstack/react-query": "^5.85.5",
13
13
  "react": "^18.3.1",
14
14
  "d3": "^7.9.0",
15
15
  "react-i18next": "^15.1.1",
16
16
  "i18next": "^25.0.1",
17
- "@opengeoweb/shared": "14.5.1",
17
+ "@opengeoweb/shared": "15.0.0",
18
18
  "@mui/material": "^7.0.1",
19
- "@opengeoweb/store": "14.5.1",
19
+ "@opengeoweb/store": "15.0.0",
20
20
  "react-redux": "^9.2.0",
21
- "@opengeoweb/theme": "14.5.1",
21
+ "@opengeoweb/theme": "15.0.0",
22
22
  "@reduxjs/toolkit": "^2.6.1",
23
- "@opengeoweb/time-slider": "14.5.1",
23
+ "@opengeoweb/time-slider": "15.0.0",
24
24
  "react-window": "^1.8.10",
25
- "@opengeoweb/webmap": "14.5.1",
25
+ "@opengeoweb/webmap": "15.0.0",
26
26
  "lodash": "^4.17.21",
27
27
  "@types/geojson": "^7946.0.14",
28
- "@opengeoweb/webmap-react": "14.5.1",
28
+ "@opengeoweb/webmap-react": "15.0.0",
29
29
  "react-resizable-layout": "^0.7.3",
30
30
  "react-sortablejs": "^6.1.4"
31
31
  },
@@ -1,11 +1,18 @@
1
1
  import React from 'react';
2
2
  import type { SoundingFeature } from '../../api/edr-api/types';
3
+ import type { SoundingPanel } from '../../store/types';
3
4
  interface SortableAccordionsProps {
5
+ soundingPanelsInOrder: SoundingPanel[];
4
6
  data: SoundingFeature[][];
5
7
  size: number;
6
8
  chosenDiagramType: 'SKEWT' | 'BIJVOET' | 'EMAGRAM';
7
9
  setChosenDiagramType: (data: 'SKEWT' | 'BIJVOET' | 'EMAGRAM') => void;
8
10
  viewRef: React.RefObject<HTMLDivElement>;
11
+ onOrderChange: (newOrder: {
12
+ id: string;
13
+ name: string;
14
+ isDisplayed: boolean;
15
+ }[]) => void;
9
16
  }
10
- export declare const SortableAccordions: ({ data, size, chosenDiagramType, setChosenDiagramType, viewRef, }: SortableAccordionsProps) => React.ReactElement;
17
+ export declare const SortableAccordions: ({ soundingPanelsInOrder, data, size, chosenDiagramType, setChosenDiagramType, viewRef, onOrderChange, }: SortableAccordionsProps) => React.ReactElement;
11
18
  export {};
@@ -0,0 +1,4 @@
1
+ export declare const SoundingManagerListItem: ({ name, isDisplayed, }: {
2
+ name: string;
3
+ isDisplayed: boolean;
4
+ }) => React.ReactElement;
@@ -1,8 +1,16 @@
1
+ import React from 'react';
2
+ import type { SoundingPanel } from '../../store/types';
1
3
  interface SoundingsManagerProps {
2
4
  isOpen: boolean;
3
5
  onClose: () => void;
4
6
  onMouseDown: () => void;
5
7
  order: number;
8
+ soundingPanelsInOrder: SoundingPanel[];
9
+ onOrderChange: (newOrder: {
10
+ id: string;
11
+ name: string;
12
+ isDisplayed: boolean;
13
+ }[]) => void;
6
14
  }
7
- export declare const SoundingsManager: ({ isOpen, onClose, onMouseDown, order, }: SoundingsManagerProps) => React.ReactElement;
15
+ export declare const SoundingsManager: ({ isOpen, onClose, onMouseDown, order, soundingPanelsInOrder, onOrderChange, }: SoundingsManagerProps) => React.ReactElement;
8
16
  export {};
@@ -1 +1,2 @@
1
+ import React from 'react';
1
2
  export declare const SoundingsManagerConnect: () => React.ReactElement;
@@ -1,8 +1,12 @@
1
1
  import type { Draft, PayloadAction } from '@reduxjs/toolkit';
2
- import type { SoundingsStore, UpdateLocationPayload, UpdateSourcePayload, UpdateTimePayload } from './types';
2
+ import type { SoundingPanels, SoundingsStore, UpdateLocationPayload, UpdateSourcePayload, UpdateTimePayload } from './types';
3
3
  export declare const initialSoundingsState: SoundingsStore;
4
4
  export declare const soundingsReducer: import("redux").Reducer<SoundingsStore>, soundingsActions: import("@reduxjs/toolkit").CaseReducerActions<{
5
5
  updateSelectedSource: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<UpdateSourcePayload>) => void;
6
6
  updateSelectedLocation: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<UpdateLocationPayload>) => void;
7
7
  updateSelectedTime: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<UpdateTimePayload>) => void;
8
+ addSoundingPanelsFromPreset: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<SoundingPanels>) => void;
9
+ changeSoundingPanelOrder: (draft: Draft<SoundingsStore>, payloadAction: PayloadAction<{
10
+ newOrderOfIds: string[];
11
+ }>) => void;
8
12
  }, "soundings">;
@@ -0,0 +1 @@
1
+ export {};
@@ -165,3 +165,24 @@ export declare const getSelectedSourcesIds: ((state: SoundingsModuleState) => st
165
165
  memoize: typeof import("reselect").weakMapMemoize;
166
166
  argsMemoize: typeof import("reselect").weakMapMemoize;
167
167
  };
168
+ export declare const getSoundingPanelsInOrder: ((state: SoundingsModuleState) => import("./types").SoundingPanel[]) & {
169
+ clearCache: () => void;
170
+ resultsCount: () => number;
171
+ resetResultsCount: () => void;
172
+ } & {
173
+ resultFunc: (resultFuncArgs_0: SoundingsStore) => import("./types").SoundingPanel[];
174
+ memoizedResultFunc: ((resultFuncArgs_0: SoundingsStore) => import("./types").SoundingPanel[]) & {
175
+ clearCache: () => void;
176
+ resultsCount: () => number;
177
+ resetResultsCount: () => void;
178
+ };
179
+ lastResult: () => import("./types").SoundingPanel[];
180
+ dependencies: [(store: SoundingsModuleState) => SoundingsStore];
181
+ recomputations: () => number;
182
+ resetRecomputations: () => void;
183
+ dependencyRecomputations: () => number;
184
+ resetDependencyRecomputations: () => void;
185
+ } & {
186
+ memoize: typeof import("reselect").weakMapMemoize;
187
+ argsMemoize: typeof import("reselect").weakMapMemoize;
188
+ };
@@ -27,6 +27,15 @@ export interface UpdateTimePayload {
27
27
  }
28
28
  /** [lon, lat] */
29
29
  export type Coordinates = [number, number];
30
+ export interface SoundingPanel {
31
+ id: string;
32
+ name: string;
33
+ isDisplayed: boolean;
34
+ }
35
+ export interface SoundingPanels {
36
+ order: string[];
37
+ entities: Record<string, SoundingPanel>;
38
+ }
30
39
  export interface SoundingsStore {
31
40
  /** key: sourceId */
32
41
  selectedSources: Record<string, SourceSelection>;
@@ -34,6 +43,7 @@ export interface SoundingsStore {
34
43
  selectedLocations: Record<string, string | Coordinates | undefined>;
35
44
  /** ISO date, e.g., 2025-01-02T12:00:00.000Z */
36
45
  selectedTime: string;
46
+ soundingPanels: SoundingPanels;
37
47
  }
38
48
  export interface SoundingsModuleState {
39
49
  soundings: SoundingsStore;