@osm-editor-kit/street-imagery-react 0.1.0-alpha.1 → 0.1.0-alpha.10

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 (52) hide show
  1. package/README.md +178 -3
  2. package/dist/MapillaryPanel-FAXVZH37.js +2 -0
  3. package/dist/PanoramaxPanel-TWKTSOXM.js +2 -0
  4. package/dist/StreetLevelImagerySelectionOverlay.d.ts +22 -3
  5. package/dist/StreetLevelImagerySourcesAndLayers.d.ts +24 -1
  6. package/dist/StreetLevelImageryViewCone.d.ts +13 -1
  7. package/dist/StreetLevelImageryViewer.d.ts +23 -1
  8. package/dist/chunk-6EMFFIL2.js +147 -0
  9. package/dist/chunk-SGIB7AVC.js +309 -0
  10. package/dist/chunk-XQXYV6SR.js +466 -0
  11. package/dist/filters/PhotoDateRangeFilter.d.ts +46 -0
  12. package/dist/hooks/useAllProviderMapFeatures.d.ts +0 -4
  13. package/dist/hooks/useProviderData.d.ts +8 -3
  14. package/dist/i18n/PhotoCount.d.ts +12 -0
  15. package/dist/i18n/PhotoDate.d.ts +16 -0
  16. package/dist/i18n/StreetImageryLocaleProvider.d.ts +21 -0
  17. package/dist/i18n/messages.d.ts +45 -0
  18. package/dist/index.d.ts +19 -4
  19. package/dist/index.js +1542 -141
  20. package/dist/mapillary/MapillaryFeatureBar.d.ts +16 -0
  21. package/dist/mapillary/SelectedMapFeatureLayer.d.ts +19 -0
  22. package/dist/mapillary/featureBarLabels.d.ts +16 -0
  23. package/dist/mapillary/useMapillaryFeatures.d.ts +54 -0
  24. package/dist/openers/LocationPickOnMap.d.ts +10 -0
  25. package/dist/openers/useLocationPickStore.d.ts +9 -0
  26. package/dist/panels/MapillaryPanel.d.ts +19 -1
  27. package/dist/panels/PanoramaxPanel.d.ts +14 -4
  28. package/dist/panels/PanoramaxWebViewerPanel.d.ts +20 -0
  29. package/dist/panels/mapillaryLookAt.d.ts +41 -0
  30. package/dist/panels/panoramaxDataProvider.d.ts +44 -0
  31. package/dist/panels/panoramaxItem.d.ts +96 -0
  32. package/dist/panels/panoramaxLimitPanning.d.ts +54 -0
  33. package/dist/panels/panoramaxPhotoFromMetadata.d.ts +8 -0
  34. package/dist/panoramax-web-viewer/index.d.ts +1 -0
  35. package/dist/panoramax-web-viewer.js +314 -0
  36. package/dist/photo-details/PhotoDetailsDialog.d.ts +21 -0
  37. package/dist/photo-details/index.d.ts +6 -0
  38. package/dist/photo-details/labels.d.ts +33 -0
  39. package/dist/photo-details.js +235 -0
  40. package/dist/streetImageryClick.d.ts +4 -0
  41. package/dist/viewer-panels/index.d.ts +5 -0
  42. package/dist/viewer-panels.js +3 -0
  43. package/dist/viewpoints/FloatingPhotoViewer.d.ts +73 -0
  44. package/dist/viewpoints/ViewpointLayer.d.ts +17 -0
  45. package/dist/viewpoints/useViewSuggestions.d.ts +23 -0
  46. package/dist/viewpoints/useViewpointSessionStore.d.ts +35 -0
  47. package/dist/viewpoints/viewDirectionLabels.d.ts +4 -0
  48. package/package.json +37 -7
  49. package/dist/MapillaryPanel-QX6NESX7.js +0 -2
  50. package/dist/PanoramaxPanel-J6ANR6LB.js +0 -2
  51. package/dist/chunk-BS6LPDMY.js +0 -143
  52. package/dist/chunk-ZH3TPW4D.js +0 -165
@@ -0,0 +1,73 @@
1
+ import type { PhotoCandidate, ViewSuggestion } from '@osm-editor-kit/street-imagery';
2
+ import { type ReactNode } from 'react';
3
+ /** Info button for `titleActions`; `label` is its tooltip and accessible name. */
4
+ export declare const FloatingViewerInfoButton: ({ label, onClick, }: {
5
+ label: string;
6
+ onClick: () => void;
7
+ }) => import("react").JSX.Element;
8
+ export type FloatingPhotoViewerProps = {
9
+ title: ReactNode;
10
+ /** Buttons right after the title, e.g. `<FloatingViewerInfoButton>` for the photo's details. */
11
+ titleActions?: ReactNode;
12
+ /** Suggested views (viewpoint × direction); shown as chips. Empty hides the strip. */
13
+ suggestions: ViewSuggestion[];
14
+ activeDirectionKey: string | null;
15
+ /**
16
+ * A view button was clicked. `candidate` is the photo to show: the view's best one, or the next
17
+ * one when the active view is clicked again.
18
+ */
19
+ onSelectSuggestion: (suggestion: ViewSuggestion, candidate: PhotoCandidate) => void;
20
+ /** The photo in the viewer, so the active view can show which of its photos that is. */
21
+ shownPhotoId?: string | null;
22
+ /**
23
+ * History of shown photos: the back and forward buttons and the keys [ and ]. Leave out
24
+ * `onBack` and `onForward` for a host without a history; the buttons are then not shown.
25
+ */
26
+ canGoBack?: boolean;
27
+ canGoForward?: boolean;
28
+ onBack?: () => void;
29
+ onForward?: () => void;
30
+ onClose: () => void;
31
+ /**
32
+ * Step to the neighbouring photo, e.g. along the clicked street: the `<` `>` buttons at the
33
+ * left of the header. A missing handler disables its button.
34
+ */
35
+ step?: {
36
+ onPrevious?: () => void;
37
+ onNext?: () => void;
38
+ previousLabel: string;
39
+ nextLabel: string;
40
+ };
41
+ /**
42
+ * Bar below the header, in place of the suggested-view chips; e.g. the map feature the photos
43
+ * belong to, with a button per capture day.
44
+ */
45
+ toolbar?: ReactNode;
46
+ /** Short status line, e.g. "Loading…" or "No photos from the last 2 years". */
47
+ status?: ReactNode;
48
+ /** Viewer body (provider panel). */
49
+ children?: ReactNode;
50
+ footer?: ReactNode;
51
+ /** Collapsible list below the footer, e.g. other photos near the click. */
52
+ drawer?: {
53
+ label: ReactNode;
54
+ content: ReactNode;
55
+ };
56
+ /** localStorage key for position, width and minimized state. */
57
+ storageKey?: string;
58
+ /**
59
+ * Where the box is until the user moves it: the bottom right corner (default), or the top left
60
+ * one with `left` and `top` in px from the container's edges, e.g. next to a side panel.
61
+ */
62
+ defaultPosition?: {
63
+ corner: 'top-left';
64
+ left?: number;
65
+ top?: number;
66
+ };
67
+ };
68
+ /**
69
+ * Photo viewer box floating over the map. Render inside a positioned (relative) map container.
70
+ * Drag the header to move, drag the left edge to resize. Keys: Esc closes, [ and ] step history,
71
+ * Alt + arrow left/right step along (`step`).
72
+ */
73
+ export declare const FloatingPhotoViewer: ({ title, titleActions, suggestions, activeDirectionKey, onSelectSuggestion, shownPhotoId, canGoBack, canGoForward, onBack, onForward, onClose, step, toolbar, status, children, footer, drawer, storageKey, defaultPosition, }: FloatingPhotoViewerProps) => import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { type LngLat, type ViewSuggestion, type Viewpoint } from '@osm-editor-kit/street-imagery';
2
+ import type { MapGeoJSONFeature } from 'maplibre-gl';
3
+ /** Add to `interactiveLayerIds` to make view directions clickable; see `viewDirectionKeyFromFeatures`. */
4
+ export declare const VIEWPOINT_DIRECTION_LAYER_ID = "viewpoint-direction-fill";
5
+ export declare const VIEWPOINT_DEFAULT_COLOR = "#c026d3";
6
+ export type ViewpointLayerProps = {
7
+ viewpoints: Viewpoint[];
8
+ suggestions: ViewSuggestion[];
9
+ activeDirectionKey: string | null;
10
+ zoom: number;
11
+ /** Clicked line, drawn below the viewpoints. */
12
+ line?: LngLat[] | null;
13
+ color?: string;
14
+ };
15
+ export declare const ViewpointLayer: ({ viewpoints, suggestions, activeDirectionKey, zoom, line, color, }: ViewpointLayerProps) => import("react").JSX.Element;
16
+ /** Direction key of the top-most clicked view direction, if any. */
17
+ export declare const viewDirectionKeyFromFeatures: (features: MapGeoJSONFeature[] | undefined) => string | null;
@@ -0,0 +1,23 @@
1
+ import { type NormalizedPhoto, type PhotoTypeFilter, type ViewSuggestion, type ViewpointPhotoSource, type Viewpoint } from '@osm-editor-kit/street-imagery';
2
+ export type ViewSuggestionsOptions = {
3
+ /** Only photos from the last N years. */
4
+ maxAgeYears?: number;
5
+ photoTypes?: PhotoTypeFilter[];
6
+ /** Candidates kept per direction. */
7
+ limit?: number;
8
+ /** Extra app filter (e.g. date range), applied before ranking. */
9
+ filterPhoto?: (photo: NormalizedPhoto) => boolean;
10
+ /**
11
+ * Where photos come from; default Mapillary. Pass several to rank across providers, e.g.
12
+ * `[mapillaryPhotoSource, streetViewPhotoSource]` or your own (Infra3D).
13
+ */
14
+ sources?: ViewpointPhotoSource[];
15
+ enabled?: boolean;
16
+ };
17
+ export type ViewSuggestionsResult = {
18
+ suggestions: ViewSuggestion[];
19
+ isLoading: boolean;
20
+ isError: boolean;
21
+ };
22
+ /** Photos near each viewpoint from all `sources`, ranked per view direction. */
23
+ export declare const useViewSuggestions: (viewpoints: Viewpoint[], { maxAgeYears, photoTypes, limit, filterPhoto, sources, enabled, }?: ViewSuggestionsOptions) => ViewSuggestionsResult;
@@ -0,0 +1,35 @@
1
+ import type { LngLat, NormalizedPhoto, Viewpoint } from '@osm-editor-kit/street-imagery';
2
+ export type ViewpointHistoryEntry = {
3
+ photo: NormalizedPhoto;
4
+ /** Suggested view the photo came from; `null` for photos picked by hand. */
5
+ directionKey: string | null;
6
+ };
7
+ type OpenInput = {
8
+ viewpoints: Viewpoint[];
9
+ /** Clicked line, drawn as context on the map. */
10
+ line?: LngLat[] | null;
11
+ };
12
+ export declare const useViewpoints: () => Viewpoint[];
13
+ export declare const useViewpointLine: () => LngLat[] | null;
14
+ export declare const useActiveDirectionKey: () => string | null;
15
+ export declare const useCurrentHistoryEntry: () => ViewpointHistoryEntry;
16
+ export declare const useCanGoBack: () => boolean;
17
+ export declare const useCanGoForward: () => boolean;
18
+ /** Actions + current entry outside React (event handlers, tests). */
19
+ export declare const getViewpointSession: () => {
20
+ actions: {
21
+ open: (input: OpenInput) => void;
22
+ /**
23
+ * Show a photo; truncates forward history. For the photo that is shown already, only its
24
+ * direction changes.
25
+ */
26
+ showPhoto: (entry: ViewpointHistoryEntry) => void;
27
+ back: () => ViewpointHistoryEntry | null;
28
+ forward: () => ViewpointHistoryEntry | null;
29
+ close: () => void;
30
+ /** Like `close`, and forget the history too: the viewer was closed. */
31
+ reset: () => void;
32
+ };
33
+ current: ViewpointHistoryEntry;
34
+ };
35
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { ViewSuggestion } from '@osm-editor-kit/street-imagery';
2
+ import type { StreetImageryMessages } from '../i18n/messages';
3
+ /** Label of a suggested view, e.g. "Start, looking along the street". */
4
+ export declare const viewSuggestionLabel: ({ viewpoint, direction }: ViewSuggestion, messages: StreetImageryMessages) => string;
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@osm-editor-kit/street-imagery-react",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.10",
4
4
  "description": "React MapLibre layers and photo viewers for street-level imagery providers.",
5
+ "homepage": "https://github.com/osmberlin/street-space-editor/tree/main/packages/street-imagery-react",
5
6
  "license": "MIT",
6
7
  "author": "Tobias Jordans",
7
8
  "repository": {
@@ -9,16 +10,28 @@
9
10
  "url": "git+https://github.com/osmberlin/street-space-editor.git",
10
11
  "directory": "packages/street-imagery-react"
11
12
  },
12
- "homepage": "https://github.com/osmberlin/street-space-editor/tree/main/packages/street-imagery-react",
13
- "type": "module",
14
13
  "files": [
15
14
  "dist"
16
15
  ],
16
+ "type": "module",
17
+ "sideEffects": false,
17
18
  "exports": {
18
19
  ".": {
19
20
  "types": "./dist/index.d.ts",
20
21
  "import": "./dist/index.js"
21
22
  },
23
+ "./photo-details": {
24
+ "types": "./dist/photo-details/index.d.ts",
25
+ "import": "./dist/photo-details.js"
26
+ },
27
+ "./viewer-panels": {
28
+ "types": "./dist/viewer-panels/index.d.ts",
29
+ "import": "./dist/viewer-panels.js"
30
+ },
31
+ "./panoramax-web-viewer": {
32
+ "types": "./dist/panoramax-web-viewer/index.d.ts",
33
+ "import": "./dist/panoramax-web-viewer.js"
34
+ },
22
35
  "./package.json": "./package.json"
23
36
  },
24
37
  "publishConfig": {
@@ -42,30 +55,47 @@
42
55
  "@types/react-dom": "^19.1.7",
43
56
  "bun-types": "^1.2.20",
44
57
  "mapillary-js": "^4.1.2",
45
- "maplibre-gl": "^5.7.1",
58
+ "maplibre-gl": "^6.7.0",
46
59
  "react": "^19.1.1",
47
60
  "react-dom": "^19.1.1",
48
- "react-map-gl": "^8.0.4",
61
+ "react-map-gl": "^8.1.3",
49
62
  "tsup": "^8.5.1",
50
63
  "typescript": "^7.0.0",
51
64
  "zustand": "^5.0.7"
52
65
  },
53
66
  "peerDependencies": {
54
- "@osm-editor-kit/street-imagery": "workspace:*",
67
+ "@osm-editor-kit/street-imagery": "^0.1.0-alpha.8",
55
68
  "@panoramax/web-viewer": "^5",
56
69
  "@tanstack/react-query": "^5",
57
70
  "mapillary-js": "^4",
58
- "maplibre-gl": "^5",
71
+ "maplibre-gl": "^6",
59
72
  "react": "^19",
60
73
  "react-dom": "^19",
61
74
  "react-map-gl": "^8",
62
75
  "zustand": "^5"
63
76
  },
77
+ "peerDependenciesMeta": {
78
+ "@panoramax/web-viewer": {
79
+ "optional": true
80
+ }
81
+ },
64
82
  "publishExports": {
65
83
  ".": {
66
84
  "types": "./dist/index.d.ts",
67
85
  "import": "./dist/index.js"
68
86
  },
87
+ "./photo-details": {
88
+ "types": "./dist/photo-details/index.d.ts",
89
+ "import": "./dist/photo-details.js"
90
+ },
91
+ "./viewer-panels": {
92
+ "types": "./dist/viewer-panels/index.d.ts",
93
+ "import": "./dist/viewer-panels.js"
94
+ },
95
+ "./panoramax-web-viewer": {
96
+ "types": "./dist/panoramax-web-viewer/index.d.ts",
97
+ "import": "./dist/panoramax-web-viewer.js"
98
+ },
69
99
  "./package.json": "./package.json"
70
100
  }
71
101
  }
@@ -1,2 +0,0 @@
1
- export { MapillaryPanel } from './chunk-BS6LPDMY.js';
2
- import './chunk-25UGGXIE.js';
@@ -1,2 +0,0 @@
1
- export { PanoramaxPanel } from './chunk-ZH3TPW4D.js';
2
- import './chunk-25UGGXIE.js';
@@ -1,143 +0,0 @@
1
- import { useViewerActions } from './chunk-25UGGXIE.js';
2
- import { getStreetImageryConfig } from '@osm-editor-kit/street-imagery';
3
- import 'mapillary-js/dist/mapillary.css';
4
- import { Viewer } from 'mapillary-js';
5
- import { useRef, useEffect } from 'react';
6
- import { jsx } from 'react/jsx-runtime';
7
-
8
- var MapillaryPanel = ({
9
- photo,
10
- onPhotoSelected,
11
- onEaseMapToPoint
12
- }) => {
13
- const containerRef = useRef(null);
14
- const viewerRef = useRef(null);
15
- const navigableRef = useRef(false);
16
- const pendingImageIdRef = useRef(null);
17
- const lastViewerPhotoIdRef = useRef(null);
18
- const bearingRafRef = useRef(null);
19
- const pendingBearingRef = useRef(null);
20
- const actions = useViewerActions();
21
- const initialPhotoIdRef = useRef(photo.photoId);
22
- useEffect(
23
- function resetViewerStoreOnProviderChange() {
24
- return () => {
25
- actions.reset();
26
- };
27
- },
28
- [actions, photo.providerId]
29
- );
30
- useEffect(
31
- function mountMapillaryViewer() {
32
- const container = containerRef.current;
33
- if (!container) {
34
- return;
35
- }
36
- const viewer = new Viewer({
37
- accessToken: getStreetImageryConfig().mapillaryToken,
38
- container,
39
- imageId: initialPhotoIdRef.current,
40
- component: {
41
- cover: false,
42
- sequence: { visible: true }
43
- }
44
- });
45
- viewerRef.current = viewer;
46
- lastViewerPhotoIdRef.current = initialPhotoIdRef.current;
47
- const flushPendingMove = () => {
48
- const pendingId = pendingImageIdRef.current;
49
- if (!pendingId) {
50
- return;
51
- }
52
- pendingImageIdRef.current = null;
53
- void viewer.moveTo(pendingId).catch(() => {
54
- });
55
- };
56
- const onNavigable = (event) => {
57
- navigableRef.current = event.navigable;
58
- if (event.navigable) {
59
- flushPendingMove();
60
- }
61
- };
62
- const onImage = (event) => {
63
- const { image } = event;
64
- lastViewerPhotoIdRef.current = image.id;
65
- onPhotoSelected({
66
- provider: "mapillary",
67
- sequenceId: image.sequenceId,
68
- photoId: image.id
69
- });
70
- const position = image.lngLat ?? image.originalLngLat;
71
- if (!position) {
72
- return;
73
- }
74
- actions.setPov({ lngLat: [position.lng, position.lat] });
75
- onEaseMapToPoint(position.lng, position.lat);
76
- };
77
- const flushBearing = () => {
78
- bearingRafRef.current = null;
79
- if (pendingBearingRef.current != null) {
80
- actions.setPov({ bearing: pendingBearingRef.current });
81
- pendingBearingRef.current = null;
82
- }
83
- void viewer.getFieldOfView().then((fov) => {
84
- actions.setPov({ hfov: fov });
85
- }).catch(() => {
86
- });
87
- };
88
- const onBearing = (event) => {
89
- pendingBearingRef.current = event.bearing;
90
- if (bearingRafRef.current == null) {
91
- bearingRafRef.current = requestAnimationFrame(flushBearing);
92
- }
93
- };
94
- viewer.on("navigable", onNavigable);
95
- viewer.on("image", onImage);
96
- viewer.on("bearing", onBearing);
97
- const resizeObserver = new ResizeObserver(() => {
98
- viewer.resize();
99
- });
100
- resizeObserver.observe(container);
101
- return () => {
102
- if (bearingRafRef.current != null) {
103
- cancelAnimationFrame(bearingRafRef.current);
104
- bearingRafRef.current = null;
105
- }
106
- resizeObserver.disconnect();
107
- viewer.off("navigable", onNavigable);
108
- viewer.off("image", onImage);
109
- viewer.off("bearing", onBearing);
110
- viewer.remove();
111
- viewerRef.current = null;
112
- };
113
- },
114
- [actions, onEaseMapToPoint, onPhotoSelected]
115
- );
116
- useEffect(
117
- function syncExternalPhotoSelection() {
118
- const viewer = viewerRef.current;
119
- if (!viewer || photo.photoId === lastViewerPhotoIdRef.current) {
120
- return;
121
- }
122
- actions.reset();
123
- lastViewerPhotoIdRef.current = photo.photoId;
124
- if (navigableRef.current) {
125
- void viewer.moveTo(photo.photoId).catch(() => {
126
- });
127
- } else {
128
- pendingImageIdRef.current = photo.photoId;
129
- }
130
- },
131
- [actions, photo.photoId]
132
- );
133
- return /* @__PURE__ */ jsx(
134
- "div",
135
- {
136
- ref: containerRef,
137
- className: "min-h-48 overflow-hidden rounded-lg border border-slate-200 bg-slate-900",
138
- style: { aspectRatio: "4 / 3" }
139
- }
140
- );
141
- };
142
-
143
- export { MapillaryPanel };
@@ -1,165 +0,0 @@
1
- import { useViewerActions } from './chunk-25UGGXIE.js';
2
- import '@panoramax/web-viewer';
3
- import { getStreetImageryConfig } from '@osm-editor-kit/street-imagery';
4
- import { useRef, useEffect } from 'react';
5
- import { jsx } from 'react/jsx-runtime';
6
-
7
- var panoramaxApiEndpoint = () => `${getStreetImageryConfig().panoramaxApiBase}/api`;
8
- var normalizeBearing = (degrees) => (degrees % 360 + 360) % 360;
9
- var PanoramaxPanel = ({
10
- photo,
11
- onPhotoSelected,
12
- onEaseMapToPoint
13
- }) => {
14
- const containerRef = useRef(null);
15
- const viewerRef = useRef(null);
16
- const lastViewerPhotoIdRef = useRef(null);
17
- const readyRef = useRef(false);
18
- const pendingSelectRef = useRef(null);
19
- const photoRef = useRef(photo);
20
- const bearingRafRef = useRef(null);
21
- const pendingBearingRef = useRef(null);
22
- const pendingHfovRef = useRef(null);
23
- const actions = useViewerActions();
24
- useEffect(
25
- function syncPhotoRef() {
26
- photoRef.current = photo;
27
- },
28
- [photo]
29
- );
30
- useEffect(
31
- function resetViewerStoreOnProviderChange() {
32
- return () => {
33
- actions.reset();
34
- };
35
- },
36
- [actions, photo.providerId]
37
- );
38
- useEffect(
39
- function mountPanoramaxViewer() {
40
- const viewer = viewerRef.current;
41
- const container = containerRef.current;
42
- if (!viewer || !container) {
43
- return;
44
- }
45
- lastViewerPhotoIdRef.current = photoRef.current.photoId;
46
- const flushBearing = () => {
47
- bearingRafRef.current = null;
48
- if (pendingBearingRef.current != null) {
49
- actions.setPov({ bearing: pendingBearingRef.current });
50
- pendingBearingRef.current = null;
51
- }
52
- if (pendingHfovRef.current != null) {
53
- actions.setPov({ hfov: pendingHfovRef.current });
54
- pendingHfovRef.current = null;
55
- }
56
- };
57
- const onSelect = (event) => {
58
- const { seqId, picId } = event.detail;
59
- if (!picId) {
60
- return;
61
- }
62
- lastViewerPhotoIdRef.current = picId;
63
- const currentPhoto = photoRef.current;
64
- const sequenceId = seqId ?? (picId === currentPhoto.photoId && currentPhoto.sequenceId ? currentPhoto.sequenceId : `photo:${picId}`);
65
- onPhotoSelected({
66
- provider: "panoramax",
67
- sequenceId,
68
- photoId: picId
69
- });
70
- };
71
- const onPictureLoaded = (event) => {
72
- const detail = event.detail;
73
- if (detail.lon == null || detail.lat == null) {
74
- return;
75
- }
76
- actions.setPov({ lngLat: [detail.lon, detail.lat] });
77
- if (detail.x != null) {
78
- actions.setPov({ bearing: normalizeBearing(detail.x) });
79
- }
80
- const psv = viewer.psv;
81
- if (psv && detail.z != null) {
82
- actions.setPov({ hfov: psv.dataHelper.zoomLevelToFov(detail.z) });
83
- }
84
- onEaseMapToPoint(detail.lon, detail.lat);
85
- };
86
- const onViewRotated = (event) => {
87
- const detail = event.detail;
88
- pendingBearingRef.current = normalizeBearing(detail.x);
89
- const psv = viewer.psv;
90
- if (psv && detail.z != null) {
91
- pendingHfovRef.current = psv.dataHelper.zoomLevelToFov(detail.z);
92
- }
93
- if (bearingRafRef.current == null) {
94
- bearingRafRef.current = requestAnimationFrame(flushBearing);
95
- }
96
- };
97
- viewer.addEventListener("select", onSelect);
98
- viewer.addEventListener("psv:picture-loaded", onPictureLoaded);
99
- viewer.addEventListener("psv:view-rotated", onViewRotated);
100
- const resizeObserver = new ResizeObserver(() => {
101
- viewer.psv?.resize();
102
- });
103
- resizeObserver.observe(container);
104
- readyRef.current = true;
105
- const pending = pendingSelectRef.current;
106
- if (pending) {
107
- pendingSelectRef.current = null;
108
- if (typeof viewer.select === "function") {
109
- viewer.select(pending.sequenceId ?? null, pending.photoId);
110
- }
111
- }
112
- return () => {
113
- readyRef.current = false;
114
- if (bearingRafRef.current != null) {
115
- cancelAnimationFrame(bearingRafRef.current);
116
- bearingRafRef.current = null;
117
- }
118
- resizeObserver.disconnect();
119
- viewer.removeEventListener("select", onSelect);
120
- viewer.removeEventListener("psv:picture-loaded", onPictureLoaded);
121
- viewer.removeEventListener("psv:view-rotated", onViewRotated);
122
- };
123
- },
124
- [actions, onEaseMapToPoint, onPhotoSelected]
125
- );
126
- useEffect(
127
- function syncExternalPhotoSelection() {
128
- const viewer = viewerRef.current;
129
- if (!viewer || photo.photoId === lastViewerPhotoIdRef.current) {
130
- return;
131
- }
132
- actions.reset();
133
- lastViewerPhotoIdRef.current = photo.photoId;
134
- if (!readyRef.current) {
135
- pendingSelectRef.current = { sequenceId: photo.sequenceId, photoId: photo.photoId };
136
- return;
137
- }
138
- if (typeof viewer.select === "function") {
139
- viewer.select(photo.sequenceId ?? null, photo.photoId);
140
- }
141
- },
142
- [actions, photo.photoId, photo.sequenceId]
143
- );
144
- return /* @__PURE__ */ jsx(
145
- "div",
146
- {
147
- ref: containerRef,
148
- className: "min-h-48 overflow-hidden rounded-lg border border-slate-200 bg-slate-900",
149
- style: { aspectRatio: "4 / 3" },
150
- children: /* @__PURE__ */ jsx(
151
- "pnx-photo-viewer",
152
- {
153
- ref: viewerRef,
154
- className: "block h-full w-full",
155
- endpoint: panoramaxApiEndpoint(),
156
- "url-parameters": "false",
157
- picture: photo.photoId,
158
- sequence: photo.sequenceId ?? void 0
159
- }
160
- )
161
- }
162
- );
163
- };
164
-
165
- export { PanoramaxPanel };