@map-colonies/react-components 3.11.0 → 3.12.3

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 (40) hide show
  1. package/CHANGELOG.md +290 -234
  2. package/dist/cesium-map/index.d.ts +1 -0
  3. package/dist/cesium-map/index.js +1 -0
  4. package/dist/cesium-map/layers/imagery.layer.js +9 -5
  5. package/dist/cesium-map/layers-manager.d.ts +10 -2
  6. package/dist/cesium-map/layers-manager.js +19 -1
  7. package/dist/cesium-map/map-legend/MapLegend.css +135 -0
  8. package/dist/cesium-map/map-legend/MapLegend.d.ts +15 -0
  9. package/dist/cesium-map/map-legend/MapLegend.js +57 -0
  10. package/dist/cesium-map/map-legend/MapLegendList.d.ts +13 -0
  11. package/dist/cesium-map/map-legend/MapLegendList.js +43 -0
  12. package/dist/cesium-map/map-legend/MapLegendSidebar.d.ts +16 -0
  13. package/dist/cesium-map/map-legend/MapLegendSidebar.js +20 -0
  14. package/dist/cesium-map/map-legend/MapLegendToggle.d.ts +7 -0
  15. package/dist/cesium-map/map-legend/MapLegendToggle.js +20 -0
  16. package/dist/cesium-map/map-legend/index.d.ts +3 -0
  17. package/dist/cesium-map/map-legend/index.js +14 -0
  18. package/dist/cesium-map/map.css +6 -1
  19. package/dist/cesium-map/map.d.ts +14 -1
  20. package/dist/cesium-map/map.js +53 -21
  21. package/dist/cesium-map/proxied.types.d.ts +9 -1
  22. package/dist/cesium-map/proxied.types.js +33 -1
  23. package/dist/cesium-map/settings/settings.css +5 -2
  24. package/package.json +3 -3
  25. package/src/lib/cesium-map/data-sources/drawings.data-source.tsx +0 -1
  26. package/src/lib/cesium-map/index.ts +1 -0
  27. package/src/lib/cesium-map/layers/imagery.layer.tsx +12 -7
  28. package/src/lib/cesium-map/layers-manager.ts +30 -1
  29. package/src/lib/cesium-map/map-legend/MapLegend.css +135 -0
  30. package/src/lib/cesium-map/map-legend/MapLegend.tsx +92 -0
  31. package/src/lib/cesium-map/map-legend/MapLegendList.tsx +47 -0
  32. package/src/lib/cesium-map/map-legend/MapLegendSidebar.tsx +55 -0
  33. package/src/lib/cesium-map/map-legend/MapLegendToggle.tsx +31 -0
  34. package/src/lib/cesium-map/map-legend/index.tsx +3 -0
  35. package/src/lib/cesium-map/map-legend/legends-sidebar.stories.tsx +201 -0
  36. package/src/lib/cesium-map/map.css +6 -1
  37. package/src/lib/cesium-map/map.tsx +85 -20
  38. package/src/lib/cesium-map/proxied.types.ts +19 -7
  39. package/src/lib/cesium-map/settings/settings.css +5 -2
  40. package/src/lib/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx +2 -2
@@ -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.3",
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.4",
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": "33c540f10086a5b076f026b6c8f2d5b800ce4d8f",
97
97
  "jest": {
98
98
  "coverageReporters": [
99
99
  "text",
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
2
2
  import { Cartesian3, Color, Rectangle, PolygonHierarchy } from 'cesium';
3
3
  import { FeatureCollection, GeoJSON } from 'geojson';
4
4
  import { CustomDataSourceProps } from 'resium/dist/types/src/CustomDataSource/CustomDataSource';
5
-
6
5
  import { DrawType } from '../../models';
7
6
  import { CesiumEntity } from '../entities/entity';
8
7
  import { CesiumEntityStaticDescription } from '../entities/entity.description';
@@ -5,3 +5,4 @@ export * from '../utils/projections';
5
5
  export * from './entities';
6
6
  export * from './data-sources';
7
7
  export * from './proxied.types';
8
+ export * from './layers-manager';
@@ -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,34 @@ export interface IVectorLayer {
48
50
  url: string;
49
51
  }
50
52
 
53
+ export type LegendExtractor = (layers: (any & { meta: any })[]) => IMapLegend[];
54
+
51
55
  class LayerManager {
52
56
  public mapViewer: CesiumViewer;
53
57
 
58
+ public legendsList: IMapLegend[];
59
+ public layerUpdated: Event;
54
60
  private readonly layers: ICesiumImageryLayer[];
61
+ private readonly legendsExtractor?: LegendExtractor;
55
62
 
56
- public constructor(mapViewer: CesiumViewer) {
63
+ public constructor(
64
+ mapViewer: CesiumViewer,
65
+ legendsExtractor?: LegendExtractor,
66
+ onLayersUpdate?: () => void
67
+ ) {
57
68
  this.mapViewer = mapViewer;
58
69
  // eslint-disable-next-line
59
70
  this.layers = (this.mapViewer.imageryLayers as any)._layers;
71
+ this.legendsList = [];
72
+ this.legendsExtractor = legendsExtractor;
73
+ this.layerUpdated = new Event();
74
+ if (onLayersUpdate) {
75
+ this.layerUpdated.addEventListener(onLayersUpdate, this);
76
+ }
77
+ this.mapViewer.imageryLayers.layerRemoved.addEventListener(() => {
78
+ this.setLegends();
79
+ this.layerUpdated.raiseEvent();
80
+ });
60
81
  }
61
82
 
62
83
  /* eslint-disable */
@@ -67,6 +88,8 @@ class LayerManager {
67
88
  const layer = this.layers.find(layerPredicate);
68
89
  if (layer) {
69
90
  layer.meta = meta;
91
+ this.setLegends();
92
+ this.layerUpdated.raiseEvent();
70
93
  }
71
94
  }
72
95
  /* eslint-enable */
@@ -287,6 +310,12 @@ class LayerManager {
287
310
  });
288
311
  }
289
312
 
313
+ private setLegends(): void {
314
+ if (typeof this.legendsExtractor !== 'undefined') {
315
+ this.legendsList = this.legendsExtractor(this.layers);
316
+ }
317
+ }
318
+
290
319
  private getBaseLayersCount(): number {
291
320
  const baseLayers = this.layers.filter((layer) => {
292
321
  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,92 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Tooltip } from '@map-colonies/react-core';
3
+ import { Box } from '../../box';
4
+
5
+ import './MapLegend.css';
6
+
7
+ export interface IMapLegend {
8
+ layer?: string;
9
+ legendDoc?: string;
10
+ legendImg?: string;
11
+ legend?: Record<string, unknown>[];
12
+ }
13
+ interface MapLegendProps {
14
+ legend: IMapLegend;
15
+ docText?: string;
16
+ imgText?: string;
17
+ }
18
+
19
+ export const MapLegend: React.FC<MapLegendProps> = ({
20
+ legend: { legendImg, legendDoc, layer },
21
+ docText,
22
+ imgText,
23
+ }) => {
24
+ const handleLegendImgOpen = useCallback(() => {
25
+ // Open image in a new tab.
26
+ window.open(legendImg, '_blank');
27
+ }, [legendImg]);
28
+
29
+ const handleLegendDocOpen = useCallback(() => {
30
+ // Open doc in a new tab.
31
+ window.open(legendDoc, '_blank');
32
+ }, [legendDoc]);
33
+
34
+ const renderLayerName = useCallback(() => {
35
+ const MAX_LAYER_NAME_LENGTH = 15;
36
+
37
+ const layerNameContainer = (
38
+ <Box className="layerNameContainer">
39
+ <h3
40
+ style={{ maxWidth: `${MAX_LAYER_NAME_LENGTH}ch` }}
41
+ className="layerName"
42
+ >
43
+ {layer}
44
+ </h3>
45
+ </Box>
46
+ );
47
+
48
+ if ((layer ?? '').length > MAX_LAYER_NAME_LENGTH) {
49
+ return <Tooltip content={layer}>{layerNameContainer}</Tooltip>;
50
+ }
51
+
52
+ return layerNameContainer;
53
+ }, [layer]);
54
+
55
+ const renderLinks = useCallback(() => {
56
+ return [
57
+ typeof legendImg === 'string' && (
58
+ <a
59
+ className="legendAction"
60
+ href={legendImg}
61
+ target="_blank"
62
+ rel="noreferrer"
63
+ >
64
+ {imgText}
65
+ </a>
66
+ ),
67
+ typeof legendDoc === 'string' && (
68
+ <a
69
+ className="legendAction"
70
+ href={legendDoc}
71
+ target="_blank"
72
+ rel="noreferrer"
73
+ >
74
+ {docText}
75
+ </a>
76
+ ),
77
+ ];
78
+ }, [legendImg, imgText, legendDoc, docText]);
79
+
80
+ return (
81
+ <Box className="mapLegend">
82
+ {renderLayerName()}
83
+ <img
84
+ alt="Map Legend"
85
+ className="legendImg"
86
+ src={legendImg}
87
+ onClick={handleLegendImgOpen}
88
+ />
89
+ <Box className="legendActionsContainer">{renderLinks()}</Box>
90
+ </Box>
91
+ );
92
+ };
@@ -0,0 +1,47 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Box } from '../../box';
3
+ import { IMapLegend, MapLegend } from './MapLegend';
4
+
5
+ import './MapLegend.css';
6
+
7
+ interface MapLegendListProps {
8
+ legends: IMapLegend[];
9
+ actionsTexts: {
10
+ docText: string;
11
+ imgText: string;
12
+ };
13
+ noLegendsText: string;
14
+ }
15
+
16
+ export const MapLegendList: React.FC<MapLegendListProps> = ({
17
+ legends,
18
+ actionsTexts: { docText, imgText },
19
+ noLegendsText,
20
+ }) => {
21
+ const handleNoLegends = useCallback(() => {
22
+ return (
23
+ <Box className="noLegendsContainer">
24
+ <h2 className="noLegendsMsg">{noLegendsText}</h2>
25
+ </Box>
26
+ );
27
+ }, [noLegendsText]);
28
+
29
+ const renderList = useCallback(() => {
30
+ if (!legends.length) {
31
+ return handleNoLegends();
32
+ }
33
+
34
+ return legends.map((legend, i) => {
35
+ return (
36
+ <MapLegend
37
+ key={`${legend.layer as string}_${i}`}
38
+ legend={legend}
39
+ docText={docText}
40
+ imgText={imgText}
41
+ />
42
+ );
43
+ });
44
+ }, [legends]);
45
+
46
+ return <Box className="mapLegendsList">{renderList()}</Box>;
47
+ };
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import {
3
+ Icon,
4
+ Drawer,
5
+ DrawerHeader,
6
+ DrawerTitle,
7
+ DrawerContent,
8
+ } from '@map-colonies/react-core';
9
+ import { IMapLegend } from './MapLegend';
10
+ import { MapLegendList } from './MapLegendList';
11
+
12
+ import './MapLegend.css';
13
+
14
+ interface MapLegendSidebarProps {
15
+ isOpen: boolean;
16
+ toggleSidebar: () => void;
17
+ title?: string;
18
+ noLegendsText?: string;
19
+ actionsTexts?: { docText: string; imgText: string };
20
+ legends?: IMapLegend[];
21
+ }
22
+
23
+ export const MapLegendSidebar: React.FC<MapLegendSidebarProps> = ({
24
+ isOpen,
25
+ toggleSidebar,
26
+ title = 'Map Legends',
27
+ noLegendsText = 'No legends to display...',
28
+ actionsTexts = { docText: 'Docs', imgText: 'View Image' },
29
+ legends = [],
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';