@osm-editor-kit/street-imagery-react 0.1.0-alpha.3 → 0.1.0-alpha.6

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 (39) hide show
  1. package/README.md +113 -0
  2. package/dist/MapillaryPanel-N45NQY4C.js +2 -0
  3. package/dist/PanoramaxPanel-MFI4L67Z.js +2 -0
  4. package/dist/StreetLevelImagerySelectionOverlay.d.ts +20 -3
  5. package/dist/StreetLevelImagerySourcesAndLayers.d.ts +14 -1
  6. package/dist/StreetLevelImageryViewCone.d.ts +11 -1
  7. package/dist/StreetLevelImageryViewer.d.ts +15 -1
  8. package/dist/chunk-53RA4OSJ.js +304 -0
  9. package/dist/chunk-EQ2JKLGT.js +311 -0
  10. package/dist/chunk-HA4GJWHI.js +120 -0
  11. package/dist/hooks/useAllProviderMapFeatures.d.ts +0 -4
  12. package/dist/i18n/PhotoDate.d.ts +16 -0
  13. package/dist/i18n/StreetImageryLocaleProvider.d.ts +21 -0
  14. package/dist/i18n/messages.d.ts +35 -0
  15. package/dist/index.d.ts +15 -2
  16. package/dist/index.js +984 -117
  17. package/dist/mapillary/MapillaryFeatureBar.d.ts +13 -0
  18. package/dist/mapillary/useMapillaryFeatures.d.ts +36 -0
  19. package/dist/openers/LocationPickOnMap.d.ts +10 -0
  20. package/dist/openers/useLocationPickStore.d.ts +9 -0
  21. package/dist/panels/MapillaryPanel.d.ts +19 -1
  22. package/dist/panels/PanoramaxPanel.d.ts +8 -1
  23. package/dist/panels/mapillaryLookAt.d.ts +41 -0
  24. package/dist/panels/panoramaxLimitPanning.d.ts +54 -0
  25. package/dist/panels/panoramaxPhotoFromMetadata.d.ts +8 -0
  26. package/dist/photo-details/PhotoDetailsDialog.d.ts +21 -0
  27. package/dist/photo-details/index.d.ts +6 -0
  28. package/dist/photo-details/labels.d.ts +33 -0
  29. package/dist/photo-details.js +235 -0
  30. package/dist/viewpoints/FloatingPhotoViewer.d.ts +54 -0
  31. package/dist/viewpoints/ViewpointLayer.d.ts +17 -0
  32. package/dist/viewpoints/useViewSuggestions.d.ts +23 -0
  33. package/dist/viewpoints/useViewpointSessionStore.d.ts +32 -0
  34. package/dist/viewpoints/viewDirectionLabels.d.ts +4 -0
  35. package/package.json +11 -3
  36. package/dist/MapillaryPanel-QX6NESX7.js +0 -2
  37. package/dist/PanoramaxPanel-J6ANR6LB.js +0 -2
  38. package/dist/chunk-BS6LPDMY.js +0 -143
  39. package/dist/chunk-ZH3TPW4D.js +0 -165
@@ -0,0 +1,311 @@
1
+ import { useViewerActions } from './chunk-25UGGXIE.js';
2
+ import '@panoramax/web-viewer';
3
+ import { getStreetImageryConfig } from '@osm-editor-kit/street-imagery';
4
+ import { useRef, useEffectEvent, useEffect } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ // src/panels/panoramaxLimitPanning.ts
8
+ var toRad = (degrees) => degrees * Math.PI / 180;
9
+ var MIN_VISIBLE_SHARE = 0.3;
10
+ var MIN_IMAGE_SHARE_OF_VIEW = 0.8;
11
+ var keepInView = (centre, imageMin, imageMax, halfView) => {
12
+ const keep = MIN_VISIBLE_SHARE * Math.min(imageMax - imageMin, 2 * halfView);
13
+ return Math.max(imageMin - halfView + keep, Math.min(imageMax + halfView - keep, centre));
14
+ };
15
+ var limitToImage = (psv, position) => {
16
+ const crop = psv.state.textureData?.panoData?.baseData;
17
+ if (!crop || !crop.fullWidth || !crop.fullHeight) {
18
+ return position;
19
+ }
20
+ const halfH = toRad(psv.state.hFov) / 2;
21
+ const halfV = toRad(psv.state.vFov) / 2;
22
+ let { yaw, pitch } = position;
23
+ if (crop.croppedWidth < crop.fullWidth) {
24
+ const left = crop.croppedX / crop.fullWidth * 2 * Math.PI - Math.PI;
25
+ const right = (crop.croppedX + crop.croppedWidth) / crop.fullWidth * 2 * Math.PI - Math.PI;
26
+ const centred = yaw > Math.PI ? yaw - 2 * Math.PI : yaw;
27
+ yaw = keepInView(centred, left, right, halfH);
28
+ }
29
+ if (crop.croppedHeight < crop.fullHeight) {
30
+ const top = Math.PI / 2 - crop.croppedY / crop.fullHeight * Math.PI;
31
+ const bottom = Math.PI / 2 - (crop.croppedY + crop.croppedHeight) / crop.fullHeight * Math.PI;
32
+ pitch = keepInView(pitch, bottom, top, halfV);
33
+ }
34
+ return { yaw, pitch };
35
+ };
36
+ var toDeg = (radians) => radians * 180 / Math.PI;
37
+ var widestZoomLevel = (psv) => {
38
+ const crop = psv.state.textureData?.panoData?.baseData;
39
+ if (!crop || !crop.fullWidth || !crop.fullHeight) {
40
+ return null;
41
+ }
42
+ const flatWide = crop.croppedWidth < crop.fullWidth;
43
+ const flatHigh = crop.croppedHeight < crop.fullHeight;
44
+ if (!flatWide && !flatHigh) {
45
+ return null;
46
+ }
47
+ const imageV = crop.croppedHeight / crop.fullHeight * 180;
48
+ const imageH = crop.croppedWidth / crop.fullWidth * 360;
49
+ const aspect = Math.tan(toRad(psv.state.hFov) / 2) / Math.tan(toRad(psv.state.vFov) / 2);
50
+ const vForImageWidth = toDeg(2 * Math.atan(Math.tan(toRad(Math.min(imageH, 179)) / 2) / aspect));
51
+ const widestV = Math.min(flatHigh ? imageV : 180, flatWide ? vForImageWidth : 180);
52
+ return psv.dataHelper.fovToZoomLevel(Math.min(widestV / MIN_IMAGE_SHARE_OF_VIEW, 179));
53
+ };
54
+ var limitPanningToImage = (psv) => {
55
+ const onMove = (event) => {
56
+ const move = event;
57
+ if (move.position) {
58
+ move.position = limitToImage(psv, move.position);
59
+ }
60
+ };
61
+ const onZoomOrLoad = () => {
62
+ const widest = widestZoomLevel(psv);
63
+ if (widest != null && psv.getZoomLevel() < widest - 0.01) {
64
+ psv.zoom(widest);
65
+ return;
66
+ }
67
+ const current = psv.getPosition();
68
+ const limited = limitToImage(psv, current);
69
+ if (Math.abs(limited.yaw - current.yaw) > 1e-4 || Math.abs(limited.pitch - current.pitch) > 1e-4) {
70
+ psv.rotate(limited);
71
+ }
72
+ };
73
+ psv.addEventListener("before-rotate", onMove);
74
+ psv.addEventListener("before-animate", onMove);
75
+ psv.addEventListener("zoom-updated", onZoomOrLoad);
76
+ psv.addEventListener("panorama-loaded", onZoomOrLoad);
77
+ return () => {
78
+ psv.removeEventListener("before-rotate", onMove);
79
+ psv.removeEventListener("before-animate", onMove);
80
+ psv.removeEventListener("zoom-updated", onZoomOrLoad);
81
+ psv.removeEventListener("panorama-loaded", onZoomOrLoad);
82
+ };
83
+ };
84
+
85
+ // src/panels/panoramaxPhotoFromMetadata.ts
86
+ var toMs = (iso) => {
87
+ const ms = iso ? Date.parse(iso) : Number.NaN;
88
+ return Number.isNaN(ms) ? null : ms;
89
+ };
90
+ var panoramaxPhotoFromMetadata = (metadata) => {
91
+ const properties = metadata.properties ?? {};
92
+ const producers = metadata.caption?.producer ?? [];
93
+ const creatorName = producers.find((entry) => !entry.includes("@")) ?? producers.at(-1);
94
+ const creatorContact = producers.find((entry) => entry.includes("@"));
95
+ const camera = properties["pers:interior_orientation"];
96
+ const cameraName = [camera?.camera_manufacturer, camera?.camera_model].filter(Boolean).join(" ");
97
+ const crop = metadata.panorama?.cropData;
98
+ const isFlat = crop?.croppedWidth != null && crop.fullWidth != null && crop.croppedWidth < crop.fullWidth;
99
+ const uploadedAt = toMs(properties.created);
100
+ const exifEntries = Object.entries(properties.exif ?? {}).filter(([, value]) => typeof value === "string" || typeof value === "number").map(([key, value]) => [key, String(value)]);
101
+ const exif = exifEntries.length > 0 ? Object.fromEntries(exifEntries) : null;
102
+ const details = {
103
+ ...creatorContact && creatorContact !== creatorName ? { creatorContact } : {},
104
+ ...properties.license ? { license: properties.license } : {},
105
+ ...metadata.origLinks?.find((link) => link.rel === "license")?.href ? { licenseUrl: metadata.origLinks.find((link) => link.rel === "license")?.href } : {},
106
+ ...properties.datetimetz ? { capturedAtLocal: properties.datetimetz } : {},
107
+ ...uploadedAt != null ? { uploadedAt } : {},
108
+ ...cameraName ? { camera: cameraName } : {},
109
+ ...isFlat && metadata.horizontalFov ? { fieldOfViewDeg: metadata.horizontalFov } : {},
110
+ ...properties["quality:horizontal_accuracy"] != null ? { positionAccuracyMeters: properties["quality:horizontal_accuracy"] } : {},
111
+ ...metadata.origInstance?.instance_name ? { instance: metadata.origInstance.instance_name } : {},
112
+ ...camera?.focal_length != null ? { focalLengthMm: camera.focal_length } : {},
113
+ ...properties["original_file:name"] ? { originalFileName: properties["original_file:name"] } : {},
114
+ ...properties["original_file:size"] != null ? { originalFileSizeBytes: properties["original_file:size"] } : {},
115
+ ...properties["geovisio:rank_in_collection"] != null ? { rankInSequence: properties["geovisio:rank_in_collection"] } : {},
116
+ ...exif ? { exif } : {}
117
+ };
118
+ return {
119
+ providerId: "panoramax",
120
+ photoId: metadata.id,
121
+ sequenceId: metadata.sequence?.id ?? null,
122
+ capturedAt: toMs(properties.datetime),
123
+ isPano: !isFlat,
124
+ heading: properties["view:azimuth"] ?? null,
125
+ lngLat: metadata.gps,
126
+ ...creatorName ? { creatorName } : {},
127
+ details
128
+ };
129
+ };
130
+ var panoramaxApiEndpoint = () => `${getStreetImageryConfig().panoramaxApiBase}/api`;
131
+ var HIDE_LEGEND_CSS = "pnx-photo-viewer pnx-bottom-drawer, pnx-photo-viewer pnx-picture-legend { display: none !important; }";
132
+ var normalizeBearing = (degrees) => (degrees % 360 + 360) % 360;
133
+ var PanoramaxPanel = ({
134
+ photo,
135
+ onPhotoSelected,
136
+ onEaseMapToPoint,
137
+ onViewerPhoto,
138
+ hideLegend = false
139
+ }) => {
140
+ const containerRef = useRef(null);
141
+ const viewerRef = useRef(null);
142
+ const lastViewerPhotoIdRef = useRef(null);
143
+ const readyRef = useRef(false);
144
+ const pendingSelectRef = useRef(null);
145
+ const photoRef = useRef(photo);
146
+ const bearingRafRef = useRef(null);
147
+ const pendingBearingRef = useRef(null);
148
+ const pendingHfovRef = useRef(null);
149
+ const actions = useViewerActions();
150
+ const emitViewerPhoto = useEffectEvent(
151
+ (viewerPhoto) => onViewerPhoto?.(viewerPhoto)
152
+ );
153
+ useEffect(
154
+ function syncPhotoRef() {
155
+ photoRef.current = photo;
156
+ },
157
+ [photo]
158
+ );
159
+ useEffect(
160
+ function resetViewerStoreOnProviderChange() {
161
+ return () => {
162
+ actions.reset();
163
+ };
164
+ },
165
+ [actions, photo.providerId]
166
+ );
167
+ useEffect(
168
+ function mountPanoramaxViewer() {
169
+ const viewer = viewerRef.current;
170
+ const container = containerRef.current;
171
+ if (!viewer || !container) {
172
+ return;
173
+ }
174
+ lastViewerPhotoIdRef.current = photoRef.current.photoId;
175
+ const flushBearing = () => {
176
+ bearingRafRef.current = null;
177
+ if (pendingBearingRef.current != null) {
178
+ actions.setPov({ bearing: pendingBearingRef.current });
179
+ pendingBearingRef.current = null;
180
+ }
181
+ if (pendingHfovRef.current != null) {
182
+ actions.setPov({ hfov: pendingHfovRef.current });
183
+ pendingHfovRef.current = null;
184
+ }
185
+ };
186
+ const onSelect = (event) => {
187
+ const { seqId, picId } = event.detail;
188
+ if (!picId) {
189
+ return;
190
+ }
191
+ lastViewerPhotoIdRef.current = picId;
192
+ const currentPhoto = photoRef.current;
193
+ const sequenceId = seqId ?? (picId === currentPhoto.photoId && currentPhoto.sequenceId ? currentPhoto.sequenceId : `photo:${picId}`);
194
+ onPhotoSelected({
195
+ provider: "panoramax",
196
+ sequenceId,
197
+ photoId: picId
198
+ });
199
+ };
200
+ const onPictureLoaded = (event) => {
201
+ const detail = event.detail;
202
+ const metadata = viewer.psv?.getPictureMetadata();
203
+ if (metadata) {
204
+ emitViewerPhoto(panoramaxPhotoFromMetadata(metadata));
205
+ }
206
+ if (detail.lon == null || detail.lat == null) {
207
+ return;
208
+ }
209
+ actions.setPov({ lngLat: [detail.lon, detail.lat] });
210
+ if (detail.x != null) {
211
+ actions.setPov({ bearing: normalizeBearing(detail.x) });
212
+ }
213
+ const psv = viewer.psv;
214
+ if (psv && detail.z != null) {
215
+ actions.setPov({ hfov: psv.dataHelper.zoomLevelToFov(detail.z) });
216
+ }
217
+ onEaseMapToPoint(detail.lon, detail.lat);
218
+ };
219
+ const onViewRotated = (event) => {
220
+ const detail = event.detail;
221
+ pendingBearingRef.current = normalizeBearing(detail.x);
222
+ const psv = viewer.psv;
223
+ if (psv && detail.z != null) {
224
+ pendingHfovRef.current = psv.dataHelper.zoomLevelToFov(detail.z);
225
+ }
226
+ if (bearingRafRef.current == null) {
227
+ bearingRafRef.current = requestAnimationFrame(flushBearing);
228
+ }
229
+ };
230
+ viewer.addEventListener("select", onSelect);
231
+ viewer.addEventListener("psv:picture-loaded", onPictureLoaded);
232
+ viewer.addEventListener("psv:view-rotated", onViewRotated);
233
+ const resizeObserver = new ResizeObserver(() => {
234
+ viewer.psv?.resize();
235
+ });
236
+ resizeObserver.observe(container);
237
+ let stopLimit = null;
238
+ const startLimit = () => {
239
+ if (!stopLimit && viewer.psv) {
240
+ stopLimit = limitPanningToImage(viewer.psv);
241
+ }
242
+ };
243
+ startLimit();
244
+ viewer.addEventListener("psv:picture-loaded", startLimit);
245
+ readyRef.current = true;
246
+ const pending = pendingSelectRef.current;
247
+ if (pending) {
248
+ pendingSelectRef.current = null;
249
+ if (typeof viewer.select === "function") {
250
+ viewer.select(pending.sequenceId ?? null, pending.photoId);
251
+ }
252
+ }
253
+ return () => {
254
+ readyRef.current = false;
255
+ if (bearingRafRef.current != null) {
256
+ cancelAnimationFrame(bearingRafRef.current);
257
+ bearingRafRef.current = null;
258
+ }
259
+ resizeObserver.disconnect();
260
+ viewer.removeEventListener("select", onSelect);
261
+ viewer.removeEventListener("psv:picture-loaded", onPictureLoaded);
262
+ viewer.removeEventListener("psv:view-rotated", onViewRotated);
263
+ viewer.removeEventListener("psv:picture-loaded", startLimit);
264
+ stopLimit?.();
265
+ };
266
+ },
267
+ [actions, onEaseMapToPoint, onPhotoSelected]
268
+ );
269
+ useEffect(
270
+ function syncExternalPhotoSelection() {
271
+ const viewer = viewerRef.current;
272
+ if (!viewer || photo.photoId === lastViewerPhotoIdRef.current) {
273
+ return;
274
+ }
275
+ actions.reset();
276
+ lastViewerPhotoIdRef.current = photo.photoId;
277
+ if (!readyRef.current) {
278
+ pendingSelectRef.current = { sequenceId: photo.sequenceId, photoId: photo.photoId };
279
+ return;
280
+ }
281
+ if (typeof viewer.select === "function") {
282
+ viewer.select(photo.sequenceId ?? null, photo.photoId);
283
+ }
284
+ },
285
+ [actions, photo.photoId, photo.sequenceId]
286
+ );
287
+ return /* @__PURE__ */ jsxs(
288
+ "div",
289
+ {
290
+ ref: containerRef,
291
+ className: "min-h-48 overflow-hidden rounded-lg border border-slate-200 bg-slate-900",
292
+ style: { aspectRatio: "4 / 3" },
293
+ children: [
294
+ hideLegend ? /* @__PURE__ */ jsx("style", { children: HIDE_LEGEND_CSS }) : null,
295
+ /* @__PURE__ */ jsx(
296
+ "pnx-photo-viewer",
297
+ {
298
+ ref: viewerRef,
299
+ className: "block h-full w-full",
300
+ endpoint: panoramaxApiEndpoint(),
301
+ "url-parameters": "false",
302
+ picture: photo.photoId,
303
+ sequence: photo.sequenceId ?? void 0
304
+ }
305
+ )
306
+ ]
307
+ }
308
+ );
309
+ };
310
+
311
+ export { PanoramaxPanel };
@@ -0,0 +1,120 @@
1
+ import { DEFAULT_STREET_IMAGERY_LOCALE, mapillaryValueName } from '@osm-editor-kit/street-imagery';
2
+ import { createContext, use } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/i18n/messages.ts
6
+ var en = {
7
+ viewer: {
8
+ regionLabel: "Photo viewer",
9
+ loading: "Loading viewer\u2026",
10
+ backInHistory: "Back in history ([)",
11
+ forwardInHistory: "Forward in history (])",
12
+ minimize: "Minimize",
13
+ close: "Close (Esc)",
14
+ views: "Views",
15
+ suggestedViews: "Suggested views",
16
+ suggestedViewsHint: "Suggested views: where the photo is taken from, and which way it looks",
17
+ noPhoto: (label) => `${label} (no photo)`,
18
+ noMatchingPhoto: (label) => `${label}: no matching photo`
19
+ },
20
+ unknownDate: "Unknown date",
21
+ viewpointRole: {
22
+ here: "Here",
23
+ "line-start": "Start",
24
+ "line-end": "End",
25
+ "into-node": "Junction",
26
+ custom: "View"
27
+ },
28
+ viewDirection: (role, kind) => {
29
+ switch (kind) {
30
+ case "forward":
31
+ return `${role}, looking along the street`;
32
+ case "back":
33
+ return `${role}, looking back along the street`;
34
+ case "into":
35
+ return `${role}, looking into the junction`;
36
+ default:
37
+ return `${role}, looking ${kind}`;
38
+ }
39
+ },
40
+ feature: {
41
+ name: (value) => mapillaryValueName(value, "en"),
42
+ seen: "Seen",
43
+ noPhotos: "Mapillary lists no photos for this feature.",
44
+ daysSummary: (photos, days) => `${photos} photos on ${days} days`,
45
+ dayPhotos: (photos, canStep) => `${photos} photo${photos === 1 ? "" : "s"}${canStep ? " \u2014 click for the next one" : ""}`
46
+ }
47
+ };
48
+ var COMPASS_DE = { N: "Norden", E: "Osten", S: "S\xFCden", W: "Westen" };
49
+ var de = {
50
+ viewer: {
51
+ regionLabel: "Fotoansicht",
52
+ loading: "Ansicht wird geladen\u2026",
53
+ backInHistory: "Zur\xFCck im Verlauf ([)",
54
+ forwardInHistory: "Vor im Verlauf (])",
55
+ minimize: "Minimieren",
56
+ close: "Schlie\xDFen (Esc)",
57
+ views: "Blicke",
58
+ suggestedViews: "Vorgeschlagene Blicke",
59
+ suggestedViewsHint: "Vorgeschlagene Blicke: von wo das Foto aufgenommen ist und wohin es schaut",
60
+ noPhoto: (label) => `${label} (kein Foto)`,
61
+ noMatchingPhoto: (label) => `${label}: kein passendes Foto`
62
+ },
63
+ unknownDate: "Datum unbekannt",
64
+ viewpointRole: {
65
+ here: "Hier",
66
+ "line-start": "Anfang",
67
+ "line-end": "Ende",
68
+ "into-node": "Knoten",
69
+ custom: "Blick"
70
+ },
71
+ viewDirection: (role, kind) => {
72
+ switch (kind) {
73
+ case "forward":
74
+ return `${role}, Blick entlang der Stra\xDFe`;
75
+ case "back":
76
+ return `${role}, Blick zur\xFCck entlang der Stra\xDFe`;
77
+ case "into":
78
+ return `${role}, Blick in den Knoten`;
79
+ default:
80
+ return `${role}, Blick nach ${COMPASS_DE[kind] ?? kind}`;
81
+ }
82
+ },
83
+ feature: {
84
+ name: (value) => mapillaryValueName(value, "de"),
85
+ seen: "Gesehen",
86
+ noPhotos: "Mapillary f\xFChrt keine Fotos zu diesem Objekt.",
87
+ daysSummary: (photos, days) => `${photos} Fotos an ${days} Tagen`,
88
+ dayPhotos: (photos, canStep) => `${photos} Foto${photos === 1 ? "" : "s"}${canStep ? " \u2014 klicken f\xFCr das n\xE4chste" : ""}`
89
+ }
90
+ };
91
+ var STREET_IMAGERY_MESSAGES = {
92
+ en,
93
+ de
94
+ };
95
+ var DEFAULT_I18N = {
96
+ locale: DEFAULT_STREET_IMAGERY_LOCALE,
97
+ messages: STREET_IMAGERY_MESSAGES[DEFAULT_STREET_IMAGERY_LOCALE]
98
+ };
99
+ var I18nContext = createContext(DEFAULT_I18N);
100
+ var mergeMessages = (base, overrides) => overrides ? {
101
+ unknownDate: overrides.unknownDate ?? base.unknownDate,
102
+ viewer: { ...base.viewer, ...overrides.viewer },
103
+ viewpointRole: { ...base.viewpointRole, ...overrides.viewpointRole },
104
+ viewDirection: overrides.viewDirection ?? base.viewDirection,
105
+ feature: { ...base.feature, ...overrides.feature }
106
+ } : base;
107
+ var StreetImageryLocaleProvider = ({
108
+ locale,
109
+ messages,
110
+ children
111
+ }) => /* @__PURE__ */ jsx(
112
+ I18nContext,
113
+ {
114
+ value: { locale, messages: mergeMessages(STREET_IMAGERY_MESSAGES[locale], messages) },
115
+ children
116
+ }
117
+ );
118
+ var useStreetImageryI18n = () => use(I18nContext);
119
+
120
+ export { STREET_IMAGERY_MESSAGES, StreetImageryLocaleProvider, useStreetImageryI18n };
@@ -2,7 +2,3 @@ import type { DateRange } from '@osm-editor-kit/street-imagery';
2
2
  import type { Bbox, NormalizedMapFeature } from '@osm-editor-kit/street-imagery';
3
3
  import { type ProviderId } from '@osm-editor-kit/street-imagery';
4
4
  export declare const useAllProviderMapFeatures: (providerIds: ProviderId[], bbox: Bbox | null, zoom: number, date?: DateRange) => NormalizedMapFeature[];
5
- export declare const useAllProviderMapFeaturesLoading: (providerIds: ProviderId[], bbox: Bbox | null, zoom: number) => {
6
- isLoading: boolean;
7
- isFetching: boolean;
8
- };
@@ -0,0 +1,16 @@
1
+ type PhotoDateProps = {
2
+ /** Capture time (ms); `null` shows "Unknown date". */
3
+ timestamp: number | null | undefined;
4
+ className?: string;
5
+ /**
6
+ * Capture time with the camera's UTC offset (`2025-03-08T15:40:35+01:00`). The tooltip then
7
+ * shows date and time as the camera's clock had it, instead of the date alone.
8
+ */
9
+ localDateTime?: string;
10
+ };
11
+ /**
12
+ * A date as month and year ("Aug 2026"). The tooltip has the full date and, on a second line,
13
+ * how long ago that is.
14
+ */
15
+ export declare const PhotoDate: ({ timestamp, className, localDateTime }: PhotoDateProps) => import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,21 @@
1
+ import { type StreetImageryLocale } from '@osm-editor-kit/street-imagery';
2
+ import { type ReactNode } from 'react';
3
+ import { type StreetImageryMessages } from './messages';
4
+ type StreetImageryI18n = {
5
+ locale: StreetImageryLocale;
6
+ messages: StreetImageryMessages;
7
+ };
8
+ /** Overrides per group, e.g. `{ feature: { name: germanSignName } }`. */
9
+ export type StreetImageryMessageOverrides = {
10
+ [Group in keyof StreetImageryMessages]?: StreetImageryMessages[Group] extends object ? StreetImageryMessages[Group] extends (...args: never[]) => unknown ? StreetImageryMessages[Group] : Partial<StreetImageryMessages[Group]> : StreetImageryMessages[Group];
11
+ };
12
+ type StreetImageryLocaleProviderProps = {
13
+ /** Language of the package's texts and date formats. Without a provider: English. */
14
+ locale: StreetImageryLocale;
15
+ messages?: StreetImageryMessageOverrides;
16
+ children: ReactNode;
17
+ };
18
+ export declare const StreetImageryLocaleProvider: ({ locale, messages, children, }: StreetImageryLocaleProviderProps) => import("react").JSX.Element;
19
+ /** Locale and texts of the nearest `StreetImageryLocaleProvider`. */
20
+ export declare const useStreetImageryI18n: () => StreetImageryI18n;
21
+ export {};
@@ -0,0 +1,35 @@
1
+ import { type StreetImageryLocale, type ViewDirection, type ViewpointRole } from '@osm-editor-kit/street-imagery';
2
+ /**
3
+ * Every text the package's components show. Texts with values are plain functions, so a host app
4
+ * can override single entries from a constant or from its own i18n library.
5
+ */
6
+ export type StreetImageryMessages = {
7
+ viewer: {
8
+ regionLabel: string;
9
+ loading: string;
10
+ backInHistory: string;
11
+ forwardInHistory: string;
12
+ minimize: string;
13
+ close: string;
14
+ views: string;
15
+ suggestedViews: string;
16
+ suggestedViewsHint: string;
17
+ noPhoto: (label: string) => string;
18
+ noMatchingPhoto: (label: string) => string;
19
+ };
20
+ viewpointRole: Record<ViewpointRole, string>;
21
+ unknownDate: string;
22
+ /** e.g. "Start, looking along the street"; `role` is the viewpoint's label. */
23
+ viewDirection: (role: string, kind: ViewDirection['kind']) => string;
24
+ feature: {
25
+ /** Name of a Mapillary value like `regulatory--turn-right-ahead--g1`. */
26
+ name: (value: string) => string;
27
+ /** Line below the name; `first` and `last` are the rendered dates. */
28
+ seen: string;
29
+ noPhotos: string;
30
+ daysSummary: (photos: number, days: number) => string;
31
+ /** Third tooltip line of a day button, below date and age. */
32
+ dayPhotos: (photos: number, canStep: boolean) => string;
33
+ };
34
+ };
35
+ export declare const STREET_IMAGERY_MESSAGES: Record<StreetImageryLocale, StreetImageryMessages>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { StreetLevelImagerySelectionOverlay, resolveSelectedSequence, } from './StreetLevelImagerySelectionOverlay';
1
+ export { BASE_COLOR, SELECTION_COLOR, StreetLevelImagerySelectionOverlay, resolveSelectedSequence, } from './StreetLevelImagerySelectionOverlay';
2
2
  export { StreetLevelImagerySourcesAndLayers, type PhotoFilter, } from './StreetLevelImagerySourcesAndLayers';
3
- export { StreetLevelImageryViewCone } from './StreetLevelImageryViewCone';
3
+ export { StreetLevelImageryViewCone, VIEW_SHAPE_FILL_OPACITY } from './StreetLevelImageryViewCone';
4
4
  export { StreetLevelImageryViewer } from './StreetLevelImageryViewer';
5
5
  export type { StreetImageryPhotoSelection } from './types';
6
6
  export { queryStreetImageryFeatures, streetImageryInteractiveLayerIds, type StreetImageryClickFeature, } from './streetImageryClick';
@@ -12,3 +12,16 @@ export * from './hooks/usePhotoThumbnails';
12
12
  export { MapillaryPanel } from './panels/MapillaryPanel';
13
13
  export { PanoramaxPanel } from './panels/PanoramaxPanel';
14
14
  export * from './useViewerStore';
15
+ export { FloatingPhotoViewer, FloatingViewerInfoButton, type FloatingPhotoViewerProps, } from './viewpoints/FloatingPhotoViewer';
16
+ export { VIEWPOINT_DEFAULT_COLOR, VIEWPOINT_DIRECTION_LAYER_ID, ViewpointLayer, viewDirectionKeyFromFeatures, type ViewpointLayerProps, } from './viewpoints/ViewpointLayer';
17
+ export { viewSuggestionLabel } from './viewpoints/viewDirectionLabels';
18
+ export * from './viewpoints/useViewpointSessionStore';
19
+ export * from './viewpoints/useViewSuggestions';
20
+ export { mapillaryViewFor, setMapillaryViewerOutlines, turnMapillaryViewerTo, type MapillaryLookAt, type MapillaryViewerOutline, } from './panels/mapillaryLookAt';
21
+ export * from './mapillary/useMapillaryFeatures';
22
+ export { LocationPickOnMap, type LocationPickOnMapProps } from './openers/LocationPickOnMap';
23
+ export * from './openers/useLocationPickStore';
24
+ export { MapillaryFeatureBar, type MapillaryFeatureBarProps } from './mapillary/MapillaryFeatureBar';
25
+ export { STREET_IMAGERY_MESSAGES, type StreetImageryMessages } from './i18n/messages';
26
+ export { StreetImageryLocaleProvider, useStreetImageryI18n, type StreetImageryMessageOverrides, } from './i18n/StreetImageryLocaleProvider';
27
+ export { PhotoDate } from './i18n/PhotoDate';