@macrostrat/map-interface 0.3.0 → 1.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.0.1] - 2024-10-02
8
+
9
+ - Bug fix: missing package specifier
10
+
11
+ ## [1.0.0] - 2024-10-02
12
+
13
+ Updated to use BlueprintJS 5.0
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@macrostrat/map-interface",
3
- "version": "0.3.0",
3
+ "version": "1.0.1",
4
4
  "description": "Map interface for Macrostrat",
5
- "main": "dist/index.cjs",
6
- "module": "dist/index.js",
5
+ "main": "dist/main.js",
6
+ "module": "dist/module.js",
7
7
  "types": "dist/types.d.ts",
8
8
  "source": "src/index.ts",
9
- "style": "dist/index.css",
9
+ "style": "dist/main.css",
10
10
  "dependencies": {
11
11
  "@macrostrat/hyper": "^2.2.1",
12
- "@macrostrat/mapbox-react": "^2.1.0",
13
- "@macrostrat/mapbox-utils": "^1.2.1",
14
- "@macrostrat/ui-components": "^3.1.0",
12
+ "@macrostrat/mapbox-react": "2.2.1",
13
+ "@macrostrat/mapbox-utils": "1.3.0",
14
+ "@macrostrat/ui-components": "workspace:^4.1.0",
15
15
  "@mapbox/tilebelt": "^1.0.2",
16
16
  "@turf/bbox": "^6.5.0",
17
17
  "chroma-js": "^2.4.2",
@@ -19,12 +19,13 @@
19
19
  "d3-array": "^3.2.4",
20
20
  "d3-format": "^3.1.0",
21
21
  "mapbox-gl": "^2.15.0",
22
+ "query-string": "^8.1.0",
22
23
  "transition-hook": "^1.5.2",
23
24
  "underscore": "^1.13.6",
24
25
  "use-resize-observer": "^9.1.0"
25
26
  },
26
27
  "peerDependencies": {
27
- "@blueprintjs/core": "^3.43.0 || ^4.3.0",
28
+ "@blueprintjs/core": "^3.43.0 || ^4.3.0 || ^5.0.0",
28
29
  "react": "^16.8.6||^17.0.0||^18.0.0",
29
30
  "react-dom": "^16.8.6||^17.0.0||^18.0.0"
30
31
  },
@@ -33,10 +34,10 @@
33
34
  "build": "parcel build"
34
35
  },
35
36
  "exports": {
36
- "typescript": "./src/index.ts",
37
37
  ".": {
38
- "import": "./dist/index.js",
39
- "require": "./dist/index.cjs"
38
+ "typescript": "./src",
39
+ "import": "./dist/module.js",
40
+ "require": "./dist/main.js"
40
41
  },
41
42
  "./dist/": {
42
43
  "import": "./dist/",
@@ -46,5 +47,8 @@
46
47
  "files": [
47
48
  "dist",
48
49
  "src"
49
- ]
50
+ ],
51
+ "devDependencies": {
52
+ "parcel": "^2.12.0"
53
+ }
50
54
  }
package/src/container.ts CHANGED
@@ -1,4 +1,4 @@
1
- import hyper from "@macrostrat/hyper";
1
+ import hyper, { addClassNames } from "@macrostrat/hyper";
2
2
  import { HTMLDivProps } from "@blueprintjs/core";
3
3
  import styles from "./main.module.sass";
4
4
  import classNames from "classnames";
@@ -22,6 +22,16 @@ type AnyElement = React.ReactNode | React.ReactElement | React.ReactFragment;
22
22
  export const PanelCard = (props) =>
23
23
  h(Card, { ...props, className: classNames("panel-card", props.className) });
24
24
 
25
+ interface ContextStackProps extends HTMLDivProps {
26
+ adaptiveWidth: boolean;
27
+ navbar: AnyElement;
28
+ }
29
+
30
+ export enum DetailPanelStyle {
31
+ FIXED = "fixed",
32
+ FLOATING = "floating",
33
+ }
34
+
25
35
  function _MapAreaContainer({
26
36
  children,
27
37
  className,
@@ -35,7 +45,10 @@ function _MapAreaContainer({
35
45
  mapControls = h(MapBottomControls),
36
46
  contextStackProps = null,
37
47
  detailStackProps = null,
48
+ detailPanelStyle = DetailPanelStyle.FLOATING,
38
49
  fitViewport = true,
50
+ showPanelOutlines = false,
51
+ preventMapInteraction = false,
39
52
  ...rest
40
53
  }: {
41
54
  navbar: AnyElement;
@@ -49,9 +62,11 @@ function _MapAreaContainer({
49
62
  className?: string;
50
63
  detailPanelOpen?: boolean;
51
64
  contextPanelOpen?: boolean;
52
- contextStackProps?: HTMLDivProps;
65
+ contextStackProps?: ContextStackProps;
53
66
  detailStackProps?: HTMLDivProps;
67
+ detailPanelStyle: DetailPanelStyle;
54
68
  fitViewport?: boolean;
69
+ showPanelOutlines?: boolean;
55
70
  }) {
56
71
  const _detailPanelOpen = detailPanelOpen ?? detailPanel != null;
57
72
  const contextPanelTrans = useTransition(contextPanelOpen, 800);
@@ -65,45 +80,65 @@ function _MapAreaContainer({
65
80
  - These styles are doubly applied so we can have both namespaced and
66
81
  outside-accessible styles for each case.
67
82
  */
68
- const mainUIClassName = classNames(
69
- {
70
- "detail-panel-open": _detailPanelOpen,
71
- "map-context-open": contextPanelOpen,
72
- },
83
+ const mainUIClassNames = classNames(
84
+ "map-container",
85
+ className,
86
+ `detail-panel-${detailPanelStyle}`,
73
87
  `context-panel-${contextPanelTrans.stage}`,
74
88
  `map-context-${contextPanelTrans.stage}`,
75
89
  `detail-panel-${detailPanelTrans.stage}`,
76
- `map-detail-${detailPanelTrans.stage}`
90
+ `map-detail-${detailPanelTrans.stage}`,
91
+ {
92
+ "detail-panel-open": _detailPanelOpen,
93
+ "map-context-open": contextPanelOpen,
94
+ "show-panel-outlines": showPanelOutlines,
95
+ "fit-viewport": fitViewport,
96
+ }
77
97
  );
78
98
 
79
- return h(
80
- MapStyledContainer,
81
- {
82
- className: classNames("map-page", className, {
83
- "fit-viewport": fitViewport,
84
- }),
85
- },
99
+ const mapControlsExt = h([
100
+ h(ZoomControl, { className: "zoom-control" }),
101
+ h("div.spacer"),
102
+ mapControls,
103
+ ]);
104
+
105
+ const detailStackExt = h(
106
+ "div.detail-stack.infodrawer-container",
107
+ detailStackProps,
86
108
  [
87
- h("div.main-ui", { className: mainUIClassName, ...rest }, [
88
- h("div.context-stack", [
89
- navbar,
90
- h("div.context-panel-holder", [
91
- h.if(contextPanelTrans.shouldMount)([contextPanel]),
92
- ]),
93
- h("div.spacer"),
109
+ h("div.detail-panel-holder", null, detailPanel),
110
+ h.if(detailPanelStyle == DetailPanelStyle.FLOATING)([mapControlsExt]),
111
+ ]
112
+ );
113
+
114
+ return h(MapStyledContainer, { className: mainUIClassNames }, [
115
+ h("div.main-row", [
116
+ h("div.map-ui", { ...rest }, [
117
+ h(ContextStack, { navbar, ...contextStackProps }, [
118
+ h.if(contextPanelTrans.shouldMount)([contextPanel]),
94
119
  ]),
95
120
  //h(MapView),
96
121
  children ?? mainPanel,
97
- h("div.detail-stack.infodrawer-container", detailStackProps, [
98
- detailPanel,
99
- h(ZoomControl, { className: "zoom-control" }),
100
- h("div.spacer"),
101
- mapControls,
102
- ]),
122
+ h.if(detailPanelStyle == DetailPanelStyle.FLOATING)([detailStackExt]),
123
+ h.if(detailPanelStyle == DetailPanelStyle.FIXED)(
124
+ "div.map-control-stack",
125
+ mapControlsExt
126
+ ),
103
127
  ]),
104
- h("div.bottom", null, bottomPanel),
105
- ]
106
- );
128
+ h.if(detailPanelStyle == DetailPanelStyle.FIXED)([detailStackExt]),
129
+ ]),
130
+ h("div.bottom", null, bottomPanel),
131
+ ]);
132
+ }
133
+
134
+ function ContextStack(props: ContextStackProps) {
135
+ const { adaptiveWidth, navbar, children, ...rest } = props;
136
+ const props1 = addClassNames(rest, { "adaptive-width": adaptiveWidth });
137
+ return h("div.context-stack", props1, [
138
+ navbar,
139
+ h("div.context-panel-holder", null, children),
140
+ h("div.spacer"),
141
+ ]);
107
142
  }
108
143
 
109
144
  const MapProviders = ({ children }) =>
@@ -15,8 +15,9 @@ export function LoadingButton({
15
15
  icon = "menu",
16
16
  }) {
17
17
  return h(Button, {
18
+ className: "loading-button",
18
19
  icon: isLoading ? spinnerElement : icon,
19
- large: true,
20
+ large: false,
20
21
  minimal: true,
21
22
  onClick,
22
23
  active: active && !isLoading,
@@ -9,18 +9,19 @@
9
9
  .searchbar
10
10
  width: 100%
11
11
  background-color: var(--panel-background-color)
12
- border-radius: 3px
12
+ border-radius: 5px
13
13
  padding: 0 5px
14
14
  display: flex
15
15
  flex-direction: row
16
16
  align-items: center
17
- :global(.bp4-input-group)
17
+ gap: 5px
18
+ :global(.bp5-input-group)
18
19
  flex-grow: 1
19
20
  cursor: text
20
- &>*
21
- margin-right: 5px
22
- &:last-child
23
- margin-right: 0
21
+
22
+ :global(.bp5-navbar)>.loading-button
23
+ width: 40px
24
+ height: 40px
24
25
 
25
26
  .status-tongue
26
27
  background-color: var(--panel-background-color)
package/src/controls.ts CHANGED
@@ -7,8 +7,10 @@ import {
7
7
  GlobeControl,
8
8
  ThreeDControl,
9
9
  MapControlWrapper,
10
+ useMapStatus,
10
11
  } from "@macrostrat/mapbox-react";
11
12
  import { ScaleControl as BaseScaleControl } from "mapbox-gl";
13
+ import { DevToolsButtonSlot } from "@macrostrat/ui-components";
12
14
 
13
15
  const h = hyper.styled(styles);
14
16
 
@@ -41,12 +43,21 @@ function GeolocationControl(props) {
41
43
  });
42
44
  }
43
45
 
44
- export function MapBottomControls() {
46
+ export function MapBottomControls({ children }) {
47
+ const { isInitialized } = useMapStatus();
48
+
49
+ if (!isInitialized) {
50
+ return null;
51
+ }
52
+
45
53
  return h("div.map-controls", [
46
54
  h(ScaleControl),
47
55
  h(ThreeDControl, { className: "map-3d-control" }),
48
56
  h(CompassControl, { className: "compass-control" }),
49
57
  h(GlobeControl, { className: "globe-control" }),
50
58
  h(GeolocationControl, { className: "geolocation-control" }),
59
+ // If we have global development tools enabled, show the button
60
+ h(DevToolsButtonSlot, { className: "map-control" }),
61
+ children,
51
62
  ]);
52
63
  }
@@ -41,7 +41,7 @@
41
41
  .opacity-slider
42
42
  margin: 0 1em 0.5em
43
43
  :global
44
- .bp4-slider-handle .bp4-slider-label
44
+ .bp5-slider-handle .bp5-slider-label
45
45
  background-color: var(--secondary-color)
46
46
  color: var(--text-color)
47
47
 
@@ -20,9 +20,13 @@
20
20
  .title
21
21
  flex-grow: 1
22
22
 
23
- // :global(.bp4-dark) .panel-subhead
23
+ // :global(.bp5-dark) .panel-subhead
24
24
  // margin 0 1px
25
25
 
26
+ .info-panel-section
27
+ &>.panel-subhead
28
+ margin: -1px calc(var(--panel-padding-h) * -1) 0
29
+
26
30
  .expansion-panel
27
31
  padding: 0
28
32
  flex-wrap: wrap
@@ -56,14 +60,14 @@
56
60
 
57
61
  .expansion-summary-title-help
58
62
  margin-left: 5px
59
- :global(.bp4-icon)
63
+ :global(.bp5-icon)
60
64
  margin-left: 5px
61
65
 
62
66
  .expansion-panel-header
63
67
  cursor: pointer
64
68
  &:hover
65
69
  background-color: var(--accent-hover-color)
66
- :global(.bp4-icon)
70
+ :global(.bp5-icon)
67
71
  transform: translate(0,3px)
68
72
 
69
73
  .expansion-children
@@ -107,7 +111,7 @@
107
111
  position: relative
108
112
 
109
113
  .expandable-details-toggle
110
- :global(.bp4-button)
114
+ :global(.bp5-button)
111
115
  font-size: 10px
112
116
 
113
117
  .expandable-details
package/src/helpers.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useMapRef,
3
- useMapEaseToCenter,
3
+ useMapEaseTo,
4
4
  useMapDispatch,
5
5
  useMapStatus,
6
6
  } from "@macrostrat/mapbox-react";
@@ -67,7 +67,7 @@ export function MapPaddingManager({
67
67
  });
68
68
 
69
69
  // Ideally, we would not have to do this when we know the infobox is loaded
70
- useMapEaseToCenter(infoMarkerPosition, padding);
70
+ useMapEaseTo({ center: infoMarkerPosition, padding });
71
71
 
72
72
  return null;
73
73
  }
@@ -0,0 +1,102 @@
1
+ import {
2
+ LatLng,
3
+ MapPosition,
4
+ formatCoordForZoomLevel,
5
+ } from "@macrostrat/mapbox-utils";
6
+ import { ParsedQuery } from "query-string";
7
+ import { fmt1, fmt2, fmtInt } from "./utils";
8
+
9
+ interface LocationHashParams {
10
+ x?: string;
11
+ y?: string;
12
+ z?: string;
13
+ a?: string;
14
+ e?: string;
15
+ }
16
+
17
+ export function applyMapPositionToHash(
18
+ args: LocationHashParams,
19
+ mapPosition: MapPosition | null
20
+ ) {
21
+ const pos = mapPosition?.camera;
22
+ if (pos == null) return;
23
+ const zoom = mapPosition.target?.zoom;
24
+
25
+ args.x = formatCoordForZoomLevel(pos.lng, zoom);
26
+ args.y = formatCoordForZoomLevel(pos.lat, zoom);
27
+
28
+ if (pos.bearing == 0 && pos.pitch == 0 && zoom != null) {
29
+ args.z = fmt1(zoom);
30
+ } else if (pos.altitude != null) {
31
+ if (pos.altitude > 5000) {
32
+ args.z = fmt2(pos.altitude / 1000) + "km";
33
+ } else {
34
+ args.z = fmtInt(pos.altitude) + "m";
35
+ }
36
+ }
37
+ if (pos.bearing != 0) {
38
+ let az = pos.bearing;
39
+ if (az < 0) az += 360;
40
+ args.a = fmtInt(az);
41
+ }
42
+ if (pos.pitch != 0) {
43
+ args.e = fmtInt(pos.pitch);
44
+ }
45
+ }
46
+
47
+ function _fmt(x: string | number | string[]) {
48
+ if (Array.isArray(x)) {
49
+ x = x[0];
50
+ }
51
+ return parseFloat(x.toString());
52
+ }
53
+
54
+ export function getMapPositionForHash(
55
+ hashData: ParsedQuery<string>,
56
+ centerPosition: LatLng | null
57
+ ): MapPosition {
58
+ const {
59
+ x = centerPosition?.lng ?? 0,
60
+ y = centerPosition?.lat ?? 0,
61
+ // Different default for zoom depending on whether we have a marker
62
+ z = centerPosition != null ? 7 : 2,
63
+ a = 0,
64
+ e = 0,
65
+ } = hashData;
66
+
67
+ const lng = _fmt(x);
68
+ const lat = _fmt(y);
69
+
70
+ let altitude = null;
71
+ let zoom = null;
72
+ const _z = z.toString();
73
+ if (_z.endsWith("km")) {
74
+ altitude = _fmt(_z.substring(0, _z.length - 2)) * 1000;
75
+ } else if (_z.endsWith("m")) {
76
+ altitude = _fmt(_z.substring(0, _z.length - 1));
77
+ } else {
78
+ zoom = _fmt(z);
79
+ }
80
+ const bearing = _fmt(a);
81
+ const pitch = _fmt(e);
82
+
83
+ let target = undefined;
84
+ if (bearing == 0 && pitch == 0 && zoom != null) {
85
+ target = {
86
+ lat,
87
+ lng,
88
+ zoom,
89
+ };
90
+ }
91
+
92
+ return {
93
+ camera: {
94
+ lng: _fmt(x),
95
+ lat: _fmt(y),
96
+ altitude,
97
+ bearing: _fmt(a),
98
+ pitch: _fmt(e),
99
+ },
100
+ target,
101
+ };
102
+ }
@@ -1,10 +1,12 @@
1
1
  import h from "@macrostrat/hyper";
2
2
  import {
3
- formatValue,
4
- normalizeLng,
5
- metersToFeet,
6
3
  formatCoordForZoomLevel,
7
- } from "./utils";
4
+ metersToFeet,
5
+ normalizeLng,
6
+ } from "@macrostrat/mapbox-utils";
7
+ import { formatValue } from "./utils";
8
+
9
+ export * from "./hash-string";
8
10
 
9
11
  export function ValueWithUnit(props) {
10
12
  const { value, unit } = props;
@@ -1,21 +1,5 @@
1
1
  import { format } from "d3-format";
2
2
 
3
- export function formatCoordForZoomLevel(val: number, zoom: number): string {
4
- if (zoom < 2) {
5
- return fmt1(val);
6
- } else if (zoom < 4) {
7
- return fmt2(val);
8
- } else if (zoom < 7) {
9
- return fmt3(val);
10
- }
11
- return fmt4(val);
12
- }
13
-
14
- export function normalizeLng(lng) {
15
- // via https://github.com/Leaflet/Leaflet/blob/32c9156cb1d1c9bd53130639ec4d8575fbeef5a6/src/core/Util.js#L87
16
- return (((((lng - 180) % 360) + 360) % 360) - 180).toFixed(4);
17
- }
18
-
19
3
  export const fmt4 = format(".4~f");
20
4
  export const fmt3 = format(".3~f");
21
5
  export const fmt2 = format(".2~f");
@@ -38,7 +22,3 @@ export function formatValue(val: number, precision: number = 0): string {
38
22
  return fmt4(val);
39
23
  }
40
24
  }
41
-
42
- export function metersToFeet(meters, precision = 0) {
43
- return (meters * 3.28084).toFixed(precision);
44
- }
@@ -11,12 +11,14 @@ import {
11
11
 
12
12
  const h = hyper.styled(styles);
13
13
 
14
- function PositionButton({ position }) {
14
+ function PositionButton({ position, showCopyLink = false }) {
15
15
  const focusState = useFocusState(position);
16
16
 
17
+ const copyLinkIsVisible = isCentered(focusState) && showCopyLink;
18
+
17
19
  return h("div.position-controls", [
18
20
  h(LocationFocusButton, { location: position, focusState }, []),
19
- isCentered(focusState) ? h(CopyLinkButton, { itemName: "position" }) : null,
21
+ h.if(copyLinkIsVisible)(CopyLinkButton, { itemName: "position" }),
20
22
  ]);
21
23
  }
22
24
 
@@ -63,20 +65,31 @@ function CopyLinkButton({ itemName, children, onClick, ...rest }) {
63
65
  );
64
66
  }
65
67
 
66
- interface InfoDrawerHeaderProps {
68
+ export interface InfoDrawerHeaderProps {
67
69
  onClose: () => void;
68
70
  position: mapboxgl.LngLat;
69
71
  zoom?: number;
70
72
  elevation?: number;
73
+ showCopyPositionButton?: boolean;
71
74
  }
72
75
 
73
76
  export function InfoDrawerHeader(props: InfoDrawerHeaderProps) {
74
- const { onClose, position, zoom = 7, elevation } = props;
77
+ const {
78
+ onClose,
79
+ position,
80
+ zoom = 7,
81
+ elevation,
82
+ showCopyPositionButton,
83
+ } = props;
75
84
 
76
85
  return h("header.location-panel-header", [
77
- h(PositionButton, { position }),
86
+ h(PositionButton, { position, showCopyLink: showCopyPositionButton }),
78
87
  h("div.spacer"),
79
- h(LngLatCoords, { position, zoom, className: "infodrawer-header-item" }),
88
+ h(LngLatCoords, {
89
+ position,
90
+ zoom,
91
+ className: "infodrawer-header-item",
92
+ }),
80
93
  h.if(elevation != null)(Elevation, {
81
94
  elevation,
82
95
  className: "infodrawer-header-item",
@@ -1,6 +1,6 @@
1
1
  import { Card } from "@blueprintjs/core";
2
2
  import hyper from "@macrostrat/hyper";
3
- import { InfoDrawerHeader } from "./header";
3
+ import { InfoDrawerHeader, InfoDrawerHeaderProps } from "./header";
4
4
  import classNames from "classnames";
5
5
  import styles from "./main.module.sass";
6
6
  import { ErrorBoundary } from "@macrostrat/ui-components";
@@ -12,11 +12,38 @@ export function InfoDrawerContainer(props) {
12
12
  return h(Card, { ...props, className });
13
13
  }
14
14
 
15
+ interface BaseInfoDrawerProps extends InfoDrawerHeaderProps {
16
+ className?: string;
17
+ title?: string;
18
+ headerElement?: JSX.Element;
19
+ children?: React.ReactNode;
20
+ }
21
+
22
+ export function BaseInfoDrawer(props: BaseInfoDrawerProps) {
23
+ const {
24
+ className,
25
+ headerElement = null,
26
+ title,
27
+ onClose,
28
+ children,
29
+ ...rest
30
+ } = props;
31
+ const header =
32
+ headerElement ??
33
+ h(InfoDrawerHeader, { onClose, ...rest }, [
34
+ title == null ? null : h("h3", [title]),
35
+ ]);
36
+ return h(InfoDrawerContainer, { className }, [
37
+ header,
38
+ h(
39
+ "div.infodrawer-body",
40
+ h("div.infodrawer-contents", h(ErrorBoundary, null, children))
41
+ ),
42
+ ]);
43
+ }
44
+
15
45
  export function LocationPanel(props) {
16
46
  const { children, className, loading = false, ...rest } = props;
17
47
  const cls = classNames("location-panel", className, { loading });
18
- return h(InfoDrawerContainer, { className: cls }, [
19
- h(InfoDrawerHeader, rest),
20
- h("div.infodrawer-body", h("div.infodrawer-contents", h(ErrorBoundary, null, children))),
21
- ]);
48
+ return h(BaseInfoDrawer, { className: cls, ...rest }, children);
22
49
  }
@@ -1,4 +1,4 @@
1
- .copy-link-button:global(.bp4-minimal.bp4-button)
1
+ .copy-link-button:global(.bp5-minimal.bp5-button)
2
2
  color: var(--text-subtle-color)
3
3
  svg
4
4
  fill: var(--text-subtle-color)
@@ -15,7 +15,7 @@
15
15
  .left-icon
16
16
  padding: 7px
17
17
 
18
- .position-controls :global(.bp4-button)
18
+ .position-controls :global(.bp5-button)
19
19
  font-size: 12px !important
20
20
 
21
21
  .infodrawer-header-item
@@ -32,7 +32,7 @@
32
32
  display: flex
33
33
  flex-direction: column
34
34
  overflow: hidden
35
- &:global(.bp4-card)
35
+ &:global(.bp5-card)
36
36
  padding: 0
37
37
 
38
38
  &.loading