@map-colonies/react-components 3.11.0 → 3.12.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/cesium-map/layers/imagery.layer.js +9 -5
  3. package/dist/cesium-map/layers-manager.d.ts +10 -2
  4. package/dist/cesium-map/layers-manager.js +19 -1
  5. package/dist/cesium-map/map-legend/MapLegend.css +135 -0
  6. package/dist/cesium-map/map-legend/MapLegend.d.ts +15 -0
  7. package/dist/cesium-map/map-legend/MapLegend.js +57 -0
  8. package/dist/cesium-map/map-legend/MapLegendList.d.ts +13 -0
  9. package/dist/cesium-map/map-legend/MapLegendList.js +43 -0
  10. package/dist/cesium-map/map-legend/MapLegendSidebar.d.ts +16 -0
  11. package/dist/cesium-map/map-legend/MapLegendSidebar.js +20 -0
  12. package/dist/cesium-map/map-legend/MapLegendToggle.d.ts +7 -0
  13. package/dist/cesium-map/map-legend/MapLegendToggle.js +20 -0
  14. package/dist/cesium-map/map-legend/index.d.ts +3 -0
  15. package/dist/cesium-map/map-legend/index.js +14 -0
  16. package/dist/cesium-map/map.css +6 -1
  17. package/dist/cesium-map/map.d.ts +16 -1
  18. package/dist/cesium-map/map.js +51 -21
  19. package/dist/cesium-map/settings/settings.css +5 -2
  20. package/package.json +3 -3
  21. package/src/lib/cesium-map/layers/imagery.layer.tsx +12 -7
  22. package/src/lib/cesium-map/layers-manager.ts +35 -2
  23. package/src/lib/cesium-map/map-legend/MapLegend.css +135 -0
  24. package/src/lib/cesium-map/map-legend/MapLegend.tsx +91 -0
  25. package/src/lib/cesium-map/map-legend/MapLegendList.tsx +46 -0
  26. package/src/lib/cesium-map/map-legend/MapLegendSidebar.tsx +55 -0
  27. package/src/lib/cesium-map/map-legend/MapLegendToggle.tsx +31 -0
  28. package/src/lib/cesium-map/map-legend/index.tsx +3 -0
  29. package/src/lib/cesium-map/map-legend/legends-sidebar.stories.tsx +201 -0
  30. package/src/lib/cesium-map/map.css +6 -1
  31. package/src/lib/cesium-map/map.tsx +86 -20
  32. package/src/lib/cesium-map/settings/settings.css +5 -2
@@ -50,6 +50,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
51
  exports.CesiumMap = exports.useCesiumMap = exports.CesiumViewer = void 0;
52
52
  var react_1 = __importStar(require("react"));
53
+ var react_dom_1 = require("react-dom");
53
54
  var resium_1 = require("resium");
54
55
  var cesium_1 = require("cesium");
55
56
  var lodash_1 = require("lodash");
@@ -59,6 +60,7 @@ var projections_1 = require("../utils/projections");
59
60
  var coordinates_tracker_tool_1 = require("./tools/coordinates-tracker.tool");
60
61
  var scale_tracker_tool_1 = require("./tools/scale-tracker.tool");
61
62
  var settings_1 = require("./settings/settings");
63
+ var map_legend_1 = require("./map-legend");
62
64
  var layers_manager_1 = __importDefault(require("./layers-manager"));
63
65
  var map_types_1 = require("./map.types");
64
66
  require("./map.css");
@@ -85,18 +87,20 @@ var useCesiumMap = function () {
85
87
  };
86
88
  exports.useCesiumMap = useCesiumMap;
87
89
  var CesiumMap = function (props) {
88
- var _a, _b, _c, _d, _e, _f, _g, _h;
90
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
89
91
  var ref = react_1.useRef(null);
90
- var _j = react_1.useState(), mapViewRef = _j[0], setMapViewRef = _j[1];
91
- var _k = react_1.useState(), projection = _k[0], setProjection = _k[1];
92
- var _l = react_1.useState(), showMousePosition = _l[0], setShowMousePosition = _l[1];
93
- var _m = react_1.useState(), showScale = _m[0], setShowScale = _m[1];
94
- var _o = react_1.useState(), locale = _o[0], setLocale = _o[1];
95
- var _p = react_1.useState(), cameraState = _p[0], setCameraState = _p[1];
96
- var _q = react_1.useState(), sceneModes = _q[0], setSceneModes = _q[1];
97
- var _r = react_1.useState(), baseMaps = _r[0], setBaseMaps = _r[1];
98
- var _s = react_1.useState(false), showImageryMenu = _s[0], setShowImageryMenu = _s[1];
99
- var _t = react_1.useState(undefined), imageryMenuPosition = _t[0], setImageryMenuPosition = _t[1];
92
+ var _p = react_1.useState(), mapViewRef = _p[0], setMapViewRef = _p[1];
93
+ var _q = react_1.useState(), projection = _q[0], setProjection = _q[1];
94
+ var _r = react_1.useState(), showMousePosition = _r[0], setShowMousePosition = _r[1];
95
+ var _s = react_1.useState(), showScale = _s[0], setShowScale = _s[1];
96
+ var _t = react_1.useState(), locale = _t[0], setLocale = _t[1];
97
+ var _u = react_1.useState(), cameraState = _u[0], setCameraState = _u[1];
98
+ var _v = react_1.useState(), sceneModes = _v[0], setSceneModes = _v[1];
99
+ var _w = react_1.useState([]), legendsList = _w[0], setLegendsList = _w[1];
100
+ var _x = react_1.useState(), baseMaps = _x[0], setBaseMaps = _x[1];
101
+ var _y = react_1.useState(false), showImageryMenu = _y[0], setShowImageryMenu = _y[1];
102
+ var _z = react_1.useState(undefined), imageryMenuPosition = _z[0], setImageryMenuPosition = _z[1];
103
+ var _0 = react_1.useState(false), isLegendsSidebarOpen = _0[0], setIsLegendsSidebarOpen = _0[1];
100
104
  var viewerProps = __assign({ fullscreenButton: true, timeline: false, animation: false, baseLayerPicker: false, geocoder: false, navigationHelpButton: false, homeButton: false, sceneModePicker: false }, props);
101
105
  var getImageryMenuStyle = function (x, y, menuWidth, menuHeight, menuDynamicHeightIncrement) {
102
106
  var container = mapViewRef.container;
@@ -114,7 +118,6 @@ var CesiumMap = function (props) {
114
118
  var _a;
115
119
  if (ref.current) {
116
120
  var viewer = ref.current.cesiumElement;
117
- viewer.layersManager = new layers_manager_1.default(viewer);
118
121
  if (props.imageryContextMenu) {
119
122
  viewer.screenSpaceEventHandler.setInputAction(function (evt) {
120
123
  // console.log('RIGHT click', evt.position);
@@ -126,6 +129,15 @@ var CesiumMap = function (props) {
126
129
  }
127
130
  setMapViewRef((_a = ref.current) === null || _a === void 0 ? void 0 : _a.cesiumElement);
128
131
  }, [ref, props.imageryContextMenu]);
132
+ react_1.useEffect(function () {
133
+ var _a;
134
+ if (mapViewRef) {
135
+ mapViewRef.layersManager = new layers_manager_1.default(mapViewRef, (_a = props.legends) === null || _a === void 0 ? void 0 : _a.mapLegendsExtractor, function () {
136
+ var _a;
137
+ setLegendsList((_a = mapViewRef === null || mapViewRef === void 0 ? void 0 : mapViewRef.layersManager) === null || _a === void 0 ? void 0 : _a.legendsList);
138
+ });
139
+ }
140
+ }, [mapViewRef]);
129
141
  react_1.useEffect(function () {
130
142
  var _a;
131
143
  setSceneModes((_a = props.sceneModes) !== null && _a !== void 0 ? _a : [
@@ -243,25 +255,43 @@ var CesiumMap = function (props) {
243
255
  });
244
256
  }
245
257
  }, [props.zoom, props.center, mapViewRef]);
246
- return (react_1.default.createElement(resium_1.Viewer, __assign({ full: true, ref: ref }, viewerProps),
258
+ var bindCustomToolsToViewer = react_1.useCallback(function () {
259
+ return (mapViewRef &&
260
+ react_dom_1.createPortal(react_1.default.createElement(react_1.default.Fragment, null,
261
+ react_1.default.createElement(box_1.Box, { className: "sideToolsContainer" },
262
+ react_1.default.createElement(settings_1.CesiumSettings, { sceneModes: sceneModes, baseMaps: baseMaps, locale: locale }),
263
+ react_1.default.createElement(map_legend_1.MapLegendToggle, { onClick: function () { return setIsLegendsSidebarOpen(!isLegendsSidebarOpen); } })),
264
+ react_1.default.createElement(box_1.Box, { className: "toolsContainer" },
265
+ showMousePosition === true ? (react_1.default.createElement(coordinates_tracker_tool_1.CoordinatesTrackerTool, { projection: projection })) : (react_1.default.createElement(react_1.default.Fragment, null)),
266
+ showScale === true ? react_1.default.createElement(scale_tracker_tool_1.ScaleTrackerTool, { locale: locale }) : react_1.default.createElement(react_1.default.Fragment, null))), document.querySelector('.cesium-viewer')));
267
+ }, [
268
+ baseMaps,
269
+ locale,
270
+ mapViewRef,
271
+ projection,
272
+ sceneModes,
273
+ showMousePosition,
274
+ showScale,
275
+ isLegendsSidebarOpen,
276
+ ]);
277
+ return (react_1.default.createElement(resium_1.Viewer, __assign({ className: "viewer", full: true, ref: ref }, viewerProps),
247
278
  react_1.default.createElement(MapViewProvider, { value: mapViewRef },
279
+ react_1.default.createElement(map_legend_1.MapLegendSidebar, { title: (_a = props.legends) === null || _a === void 0 ? void 0 : _a.title, isOpen: isLegendsSidebarOpen, toggleSidebar: function () {
280
+ return setIsLegendsSidebarOpen(!isLegendsSidebarOpen);
281
+ }, noLegendsText: (_b = props.legends) === null || _b === void 0 ? void 0 : _b.emptyText, legends: (_d = (_c = props.legends) === null || _c === void 0 ? void 0 : _c.legendsList) !== null && _d !== void 0 ? _d : legendsList, actionsTexts: (_e = props.legends) === null || _e === void 0 ? void 0 : _e.actionsTexts }),
248
282
  props.children,
249
- react_1.default.createElement(box_1.Box, { className: "sideToolsContainer" },
250
- react_1.default.createElement(settings_1.CesiumSettings, { sceneModes: sceneModes, baseMaps: baseMaps, locale: locale })),
251
- react_1.default.createElement(box_1.Box, { className: "toolsContainer" },
252
- showMousePosition === true ? (react_1.default.createElement(coordinates_tracker_tool_1.CoordinatesTrackerTool, { projection: projection })) : (react_1.default.createElement(react_1.default.Fragment, null)),
253
- showScale === true ? react_1.default.createElement(scale_tracker_tool_1.ScaleTrackerTool, { locale: locale }) : react_1.default.createElement(react_1.default.Fragment, null)),
283
+ bindCustomToolsToViewer(),
254
284
  props.imageryContextMenu &&
255
285
  showImageryMenu &&
256
286
  imageryMenuPosition &&
257
287
  react_1.default.cloneElement(props.imageryContextMenu, {
258
- data: (_a = mapViewRef === null || mapViewRef === void 0 ? void 0 : mapViewRef.layersManager) === null || _a === void 0 ? void 0 : _a.findLayerByPOI(imageryMenuPosition.x, imageryMenuPosition.y),
288
+ data: (_f = mapViewRef === null || mapViewRef === void 0 ? void 0 : mapViewRef.layersManager) === null || _f === void 0 ? void 0 : _f.findLayerByPOI(imageryMenuPosition.x, imageryMenuPosition.y),
259
289
  position: {
260
290
  x: imageryMenuPosition.x,
261
291
  y: imageryMenuPosition.y,
262
292
  },
263
- style: getImageryMenuStyle(imageryMenuPosition.x, imageryMenuPosition.y, (_c = (_b = props.imageryContextMenuSize) === null || _b === void 0 ? void 0 : _b.width) !== null && _c !== void 0 ? _c : DEFAULT_WIDTH, (_e = (_d = props.imageryContextMenuSize) === null || _d === void 0 ? void 0 : _d.height) !== null && _e !== void 0 ? _e : DEFAULT_HEIGHT, (_g = (_f = props.imageryContextMenuSize) === null || _f === void 0 ? void 0 : _f.dynamicHeightIncrement) !== null && _g !== void 0 ? _g : DEFAULT_DYNAMIC_HEIGHT_INCREMENT),
264
- size: (_h = props.imageryContextMenuSize) !== null && _h !== void 0 ? _h : {
293
+ style: getImageryMenuStyle(imageryMenuPosition.x, imageryMenuPosition.y, (_h = (_g = props.imageryContextMenuSize) === null || _g === void 0 ? void 0 : _g.width) !== null && _h !== void 0 ? _h : DEFAULT_WIDTH, (_k = (_j = props.imageryContextMenuSize) === null || _j === void 0 ? void 0 : _j.height) !== null && _k !== void 0 ? _k : DEFAULT_HEIGHT, (_m = (_l = props.imageryContextMenuSize) === null || _l === void 0 ? void 0 : _l.dynamicHeightIncrement) !== null && _m !== void 0 ? _m : DEFAULT_DYNAMIC_HEIGHT_INCREMENT),
294
+ size: (_o = props.imageryContextMenuSize) !== null && _o !== void 0 ? _o : {
265
295
  height: DEFAULT_HEIGHT,
266
296
  width: DEFAULT_WIDTH,
267
297
  },
@@ -3,7 +3,8 @@
3
3
  gap: 8px;
4
4
  }
5
5
 
6
- .settingsIconContainer {
6
+ .settingsIconContainer,
7
+ .mapLegendToggleContainer {
7
8
  fill: #fff;
8
9
  width: 40px;
9
10
  height: 40px;
@@ -12,7 +13,8 @@
12
13
  border-radius: 4px;
13
14
  }
14
15
 
15
- .settingsIconContainer:hover {
16
+ .settingsIconContainer:hover,
17
+ .mapLegendToggleContainer:hover {
16
18
  background: #48b;
17
19
  border-color: #aef;
18
20
  }
@@ -35,6 +37,7 @@
35
37
  .settingsDialogPortal .mdc-dialog__container {
36
38
  align-items: unset;
37
39
  }
40
+
38
41
  .settingsDialogPortal .mdc-dialog .mdc-dialog__surface {
39
42
  max-height: unset;
40
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@map-colonies/react-components",
3
- "version": "3.11.0",
3
+ "version": "3.12.0",
4
4
  "module": "dist/index.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@date-io/date-fns": "^1.3.13",
20
20
  "@here/quantized-mesh-decoder": "^1.2.8",
21
- "@map-colonies/react-core": "^3.3.2",
21
+ "@map-colonies/react-core": "^3.3.3",
22
22
  "@material-ui/core": "^4.11.0",
23
23
  "@material-ui/icons": "^4.9.1",
24
24
  "@material-ui/pickers": "^3.2.10",
@@ -93,7 +93,7 @@
93
93
  "jest-enzyme": "^7.1.2",
94
94
  "react-test-renderer": "^16.13.1"
95
95
  },
96
- "gitHead": "52e983d1180b9fc19719b68e4bbc8745cf5d4581",
96
+ "gitHead": "fc727564af113a9fbfc599f1c7720dce840e8e83",
97
97
  "jest": {
98
98
  "coverageReporters": [
99
99
  "text",
@@ -18,13 +18,18 @@ export const CesiumImageryLayer: React.FC<RCesiumImageryLayerProps> = (
18
18
  useLayoutEffect(() => {
19
19
  mapViewer.layersManager?.addMetaToLayer(
20
20
  meta,
21
- (layer: ImageryLayer, idx: number): boolean => {
22
- if (meta !== undefined) {
23
- // eslint-disable-next-line
24
- return (layer as any)._imageryProvider._resource._url === meta.url;
25
- }
26
- return false;
27
- }
21
+ /* eslint-disable */
22
+ meta.searchLayerPredicate ??
23
+ ((layer: ImageryLayer, idx: number): boolean => {
24
+ if (meta !== undefined) {
25
+ return (
26
+ (layer as any)._imageryProvider._resource._url ===
27
+ meta.options.url
28
+ );
29
+ }
30
+ return false;
31
+ })
32
+ /* eslint-enable */
28
33
  );
29
34
  }, [meta, mapViewer]);
30
35
 
@@ -4,6 +4,7 @@ import {
4
4
  UrlTemplateImageryProvider,
5
5
  WebMapServiceImageryProvider,
6
6
  WebMapTileServiceImageryProvider,
7
+ Event,
7
8
  } from 'cesium';
8
9
  import { get } from 'lodash';
9
10
  import { Feature, Point, Polygon } from 'geojson';
@@ -17,6 +18,7 @@ import {
17
18
  import { CesiumViewer } from './map';
18
19
  import { IBaseMap } from './settings/settings';
19
20
  import { pointToGeoJSON } from './tools/geojson/point.geojson';
21
+ import { IMapLegend } from './map-legend';
20
22
 
21
23
  const INC = 1;
22
24
  const DEC = -1;
@@ -48,15 +50,38 @@ export interface IVectorLayer {
48
50
  url: string;
49
51
  }
50
52
 
53
+ export type LegendExtractor = (
54
+ layers: (any & {
55
+ meta: any;
56
+ })[]
57
+ ) => IMapLegend[];
51
58
  class LayerManager {
52
59
  public mapViewer: CesiumViewer;
53
60
 
54
61
  private readonly layers: ICesiumImageryLayer[];
55
-
56
- public constructor(mapViewer: CesiumViewer) {
62
+ public legendsList: IMapLegend[];
63
+ public layerUpdated: Event;
64
+ private readonly legendsExtractor?: LegendExtractor;
65
+
66
+ public constructor(
67
+ mapViewer: CesiumViewer,
68
+ legendsExtractor?: LegendExtractor,
69
+ onLayersUpdate?: () => void
70
+ ) {
57
71
  this.mapViewer = mapViewer;
58
72
  // eslint-disable-next-line
59
73
  this.layers = (this.mapViewer.imageryLayers as any)._layers;
74
+ this.legendsList = [];
75
+ this.legendsExtractor = legendsExtractor;
76
+ this.layerUpdated = new Event();
77
+ if (onLayersUpdate) {
78
+ this.layerUpdated.addEventListener(onLayersUpdate, this);
79
+ }
80
+
81
+ this.mapViewer.imageryLayers.layerRemoved.addEventListener(() => {
82
+ this.setLegends();
83
+ this.layerUpdated.raiseEvent();
84
+ });
60
85
  }
61
86
 
62
87
  /* eslint-disable */
@@ -67,6 +92,8 @@ class LayerManager {
67
92
  const layer = this.layers.find(layerPredicate);
68
93
  if (layer) {
69
94
  layer.meta = meta;
95
+ this.setLegends();
96
+ this.layerUpdated.raiseEvent();
70
97
  }
71
98
  }
72
99
  /* eslint-enable */
@@ -287,6 +314,12 @@ class LayerManager {
287
314
  });
288
315
  }
289
316
 
317
+ private setLegends(): void {
318
+ if (typeof this.legendsExtractor !== 'undefined') {
319
+ this.legendsList = this.legendsExtractor(this.layers);
320
+ }
321
+ }
322
+
290
323
  private getBaseLayersCount(): number {
291
324
  const baseLayers = this.layers.filter((layer) => {
292
325
  const parentId = get(layer.meta, 'parentBasetMapId') as string;
@@ -0,0 +1,135 @@
1
+ .mapLegendSidebarContainer {
2
+ width: 340px;
3
+ height: 100%;
4
+ position: relative;
5
+ overflow-y: auto;
6
+ overflow-x: hidden;
7
+ font-size: 16px;
8
+ }
9
+
10
+ /* Disable drawer animation */
11
+
12
+ .mapLegendSidebarContainer.mdc-drawer--animate {
13
+ transform: translateX(0) !important;
14
+ transition: none !important;
15
+ }
16
+
17
+ .mapLegendSidebarContainer.mdc-drawer--dismissible {
18
+ position: relative;
19
+ }
20
+
21
+ div.MuiPaper-root {
22
+ transition: none !important;
23
+ }
24
+
25
+ .mapLegendCloseBtn {
26
+ position: absolute;
27
+ top: 0;
28
+ right: 0;
29
+ cursor: pointer;
30
+ width: 50px;
31
+ height: 50px;
32
+ padding: 20px;
33
+ font-size: 24px;
34
+ }
35
+
36
+ body[dir='rtl'] .mapLegendCloseBtn {
37
+ right: unset;
38
+ left: 0;
39
+ }
40
+
41
+ .mapLegendToggleContainer {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ }
46
+
47
+ .mapLegendIcon {
48
+ width: 2.5rem;
49
+ height: 2.5rem;
50
+ }
51
+
52
+ .sidebarHeaderContainer {
53
+ text-align: center;
54
+ margin-top: 40px;
55
+ }
56
+
57
+ .sidebarTitle {
58
+ font-size: 1.5rem;
59
+ font-weight: bold;
60
+ }
61
+
62
+ .mapLegend {
63
+ display: flex;
64
+ flex-direction: column;
65
+ align-items: center;
66
+ justify-content: center;
67
+ gap: 4px;
68
+ padding: 10px 14px;
69
+ }
70
+
71
+ .layerNameContainer {
72
+ width: 90%;
73
+ display: flex;
74
+ justify-content: center;
75
+ align-items: center;
76
+ }
77
+
78
+ .layerName {
79
+ overflow: hidden;
80
+ text-overflow: ellipsis;
81
+ white-space: nowrap;
82
+ }
83
+
84
+ .legendImg {
85
+ width: 90%;
86
+ height: 150px;
87
+ border: 1px solid black;
88
+ border-radius: 5px;
89
+ object-fit: cover;
90
+ cursor: pointer;
91
+ }
92
+
93
+ .legendActionsContainer {
94
+ display: flex;
95
+ flex-direction: row;
96
+ align-self: center;
97
+ }
98
+
99
+ .legendAction:not(:last-child)::after {
100
+ content: '|';
101
+ margin: 0px 4px;
102
+ }
103
+
104
+ .mapLegendsList {
105
+ display: flex;
106
+ flex-direction: column;
107
+ padding: 20px 0px;
108
+ width: 100%;
109
+ height: 90%;
110
+ }
111
+
112
+ .legendAction {
113
+ text-decoration: underline;
114
+ cursor: pointer;
115
+ font-size: 1.1rem;
116
+ transition: all 0.1s ease-in-out;
117
+ width: max-content;
118
+ }
119
+
120
+ .legendAction:hover {
121
+ filter: brightness(1.2);
122
+ }
123
+
124
+ .noLegendsContainer {
125
+ width: 100%;
126
+ height: 100%;
127
+ display: flex;
128
+ flex-direction: column;
129
+ justify-content: center;
130
+ align-items: center;
131
+ }
132
+
133
+ .noLegendsMsg {
134
+ text-align: center;
135
+ }
@@ -0,0 +1,91 @@
1
+ import { Tooltip } from '@map-colonies/react-core';
2
+ import React, { useCallback } from 'react';
3
+ import { Box } from '../../box';
4
+ import './MapLegend.css';
5
+
6
+ export interface IMapLegend {
7
+ layer?: string;
8
+ legendDoc?: string;
9
+ legendImg?: string;
10
+ legend?: Record<string, unknown>[];
11
+ }
12
+ interface MapLegendProps {
13
+ legend: IMapLegend;
14
+ docText?: string;
15
+ imgText?: string;
16
+ }
17
+
18
+ export const MapLegend: React.FC<MapLegendProps> = ({
19
+ legend: { legendImg, legendDoc, layer },
20
+ docText,
21
+ imgText,
22
+ }) => {
23
+ const handleLegendImgOpen = useCallback(() => {
24
+ // Open image in a new tab.
25
+ window.open(legendImg, '_blank');
26
+ }, [legendImg]);
27
+
28
+ const handleLegendDocOpen = useCallback(() => {
29
+ // Open doc in a new tab.
30
+ window.open(legendDoc, '_blank');
31
+ }, [legendDoc]);
32
+
33
+ const renderLayerName = useCallback(() => {
34
+ const MAX_LAYER_NAME_LENGTH = 15;
35
+
36
+ const layerNameContainer = (
37
+ <Box className="layerNameContainer">
38
+ <h3
39
+ style={{ maxWidth: `${MAX_LAYER_NAME_LENGTH}ch` }}
40
+ className="layerName"
41
+ >
42
+ {layer}
43
+ </h3>
44
+ </Box>
45
+ );
46
+
47
+ if ((layer ?? '').length > MAX_LAYER_NAME_LENGTH) {
48
+ return <Tooltip content={layer}>{layerNameContainer}</Tooltip>;
49
+ }
50
+
51
+ return layerNameContainer;
52
+ }, [layer]);
53
+
54
+ const renderLinks = useCallback(() => {
55
+ return [
56
+ typeof legendImg === 'string' && (
57
+ <a
58
+ className="legendAction"
59
+ href={legendImg}
60
+ target="_blank"
61
+ referrerPolicy="noreferrer"
62
+ >
63
+ {imgText}
64
+ </a>
65
+ ),
66
+ typeof legendDoc === 'string' && (
67
+ <a
68
+ className="legendAction"
69
+ href={legendDoc}
70
+ target="_blank"
71
+ referrerPolicy="noreferrer"
72
+ >
73
+ {docText}
74
+ </a>
75
+ ),
76
+ ];
77
+ }, [legendImg, imgText, legendDoc, docText]);
78
+
79
+ return (
80
+ <Box className="mapLegend">
81
+ {renderLayerName()}
82
+ <img
83
+ alt="Map Legend"
84
+ className="legendImg"
85
+ src={legendImg}
86
+ onClick={handleLegendImgOpen}
87
+ />
88
+ <Box className="legendActionsContainer">{renderLinks()}</Box>
89
+ </Box>
90
+ );
91
+ };
@@ -0,0 +1,46 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Box } from '../../box';
3
+ import { IMapLegend, MapLegend } from './MapLegend';
4
+ import './MapLegend.css';
5
+
6
+ interface MapLegendListProps {
7
+ legends: IMapLegend[];
8
+ actionsTexts: {
9
+ docText: string;
10
+ imgText: string;
11
+ };
12
+ noLegendsText: string;
13
+ }
14
+
15
+ export const MapLegendList: React.FC<MapLegendListProps> = ({
16
+ legends,
17
+ actionsTexts: { docText, imgText },
18
+ noLegendsText,
19
+ }) => {
20
+ const handleNoLegends = useCallback(() => {
21
+ return (
22
+ <Box className="noLegendsContainer">
23
+ <h2 className="noLegendsMsg">{noLegendsText}</h2>
24
+ </Box>
25
+ );
26
+ }, [noLegendsText]);
27
+
28
+ const renderList = useCallback(() => {
29
+ if (!legends.length) {
30
+ return handleNoLegends();
31
+ }
32
+
33
+ return legends.map((legend, i) => {
34
+ return (
35
+ <MapLegend
36
+ key={`${legend.layer as string}_${i}`}
37
+ legend={legend}
38
+ docText={docText}
39
+ imgText={imgText}
40
+ />
41
+ );
42
+ });
43
+ }, [legends]);
44
+
45
+ return <Box className="mapLegendsList">{renderList()}</Box>;
46
+ };
@@ -0,0 +1,55 @@
1
+ import {
2
+ Icon,
3
+ Drawer,
4
+ DrawerHeader,
5
+ DrawerTitle,
6
+ DrawerContent,
7
+ } from '@map-colonies/react-core';
8
+ import React from 'react';
9
+ import { Box } from '../../box';
10
+ import { IMapLegend } from './MapLegend';
11
+ import './MapLegend.css';
12
+ import { MapLegendList } from './MapLegendList';
13
+
14
+ interface MapLegendSidebarProps {
15
+ isOpen: boolean;
16
+ toggleSidebar: () => void;
17
+ title?: string;
18
+ noLegendsText?: string;
19
+ legends?: IMapLegend[];
20
+ actionsTexts?: { docText: string; imgText: string };
21
+ }
22
+
23
+ export const MapLegendSidebar: React.FC<MapLegendSidebarProps> = ({
24
+ isOpen,
25
+ toggleSidebar,
26
+ title = 'Map Legends',
27
+ noLegendsText = 'No legends to display...',
28
+ legends = [],
29
+ actionsTexts = { docText: 'Docs', imgText: 'View Image' },
30
+ }) => {
31
+ return isOpen ? (
32
+ <Drawer
33
+ className="mapLegendSidebarContainer"
34
+ modal={false}
35
+ dismissible={true}
36
+ open={isOpen}
37
+ >
38
+ <DrawerHeader className="sidebarHeaderContainer">
39
+ <DrawerTitle className="sidebarTitle">{title}</DrawerTitle>
40
+ </DrawerHeader>
41
+ <DrawerContent className="sidebarContent">
42
+ <Icon
43
+ onClick={toggleSidebar}
44
+ className="mapLegendCloseBtn"
45
+ icon={{ icon: 'close', size: 'small' }}
46
+ />
47
+ <MapLegendList
48
+ noLegendsText={noLegendsText}
49
+ legends={legends}
50
+ actionsTexts={actionsTexts}
51
+ />
52
+ </DrawerContent>
53
+ </Drawer>
54
+ ) : null;
55
+ };
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { Icon } from '@map-colonies/react-core';
3
+ import { Box } from '../../box';
4
+ import './MapLegend.css';
5
+
6
+ interface MapLegendProps {
7
+ onClick: () => void;
8
+ }
9
+
10
+ const legendIcon = (
11
+ <svg
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ enable-background="new 0 0 24 24"
14
+ height="24"
15
+ width="24"
16
+ viewBox="0 0 612 612"
17
+ >
18
+ <g xmlns="http://www.w3.org/2000/svg">
19
+ <path d="M322.4,173.9l-129,16.2l-4.6,21.4l25.3,4.7c16.5,3.9,19.8,9.9,16.2,26.4l-41.5,195.3c-10.9,50.5,5.9,74.3,45.5,74.3 c30.7,0,66.3-14.2,82.5-33.6l4.9-23.4c-11.3,9.9-27.7,13.9-38.6,13.9c-15.5,0-21.1-10.9-17.1-30L322.4,173.9z" />
20
+ <circle cx="270.1" cy="56.3" r="56.3" />
21
+ </g>
22
+ </svg>
23
+ );
24
+
25
+ export const MapLegendToggle: React.FC<MapLegendProps> = ({ onClick }) => {
26
+ return (
27
+ <Box onClick={onClick} className="mapLegendToggleContainer">
28
+ <Icon icon={legendIcon} className="mapLegendIcon" />
29
+ </Box>
30
+ );
31
+ };
@@ -0,0 +1,3 @@
1
+ export * from './MapLegendToggle';
2
+ export * from './MapLegendSidebar';
3
+ export type { IMapLegend } from './MapLegend';