@osm-editor-kit/street-imagery-react 0.1.0-alpha.5 → 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 (34) hide show
  1. package/README.md +45 -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 +9 -1
  6. package/dist/StreetLevelImageryViewCone.d.ts +11 -1
  7. package/dist/StreetLevelImageryViewer.d.ts +7 -2
  8. package/dist/{chunk-72HJV6II.js → chunk-53RA4OSJ.js} +6 -2
  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 +10 -4
  16. package/dist/index.js +470 -167
  17. package/dist/mapillary/MapillaryFeatureBar.d.ts +13 -0
  18. package/dist/openers/LocationPickOnMap.d.ts +10 -0
  19. package/dist/openers/useLocationPickStore.d.ts +9 -0
  20. package/dist/panels/MapillaryPanel.d.ts +7 -1
  21. package/dist/panels/PanoramaxPanel.d.ts +8 -1
  22. package/dist/panels/panoramaxLimitPanning.d.ts +54 -0
  23. package/dist/panels/panoramaxPhotoFromMetadata.d.ts +8 -0
  24. package/dist/photo-details/PhotoDetailsDialog.d.ts +21 -0
  25. package/dist/photo-details/index.d.ts +6 -0
  26. package/dist/photo-details/labels.d.ts +33 -0
  27. package/dist/photo-details.js +235 -0
  28. package/dist/viewpoints/FloatingPhotoViewer.d.ts +25 -2
  29. package/dist/viewpoints/useViewpointSessionStore.d.ts +2 -9
  30. package/dist/viewpoints/viewDirectionLabels.d.ts +4 -4
  31. package/package.json +10 -2
  32. package/dist/MapillaryPanel-I5GKT6YT.js +0 -2
  33. package/dist/PanoramaxPanel-J6ANR6LB.js +0 -2
  34. package/dist/chunk-ZH3TPW4D.js +0 -165
@@ -0,0 +1,13 @@
1
+ import { type MapFeatureImages, type TargetImage } from '@osm-editor-kit/street-imagery';
2
+ export type MapillaryFeatureBarProps = {
3
+ data: MapFeatureImages;
4
+ shownImage: TargetImage | null;
5
+ onShow: (image: TargetImage) => void;
6
+ };
7
+ /**
8
+ * A Mapillary sign or object above its photo: what it is, when Mapillary saw it, and one button
9
+ * per capture day (newest first). The shown day is highlighted; clicking it again steps through
10
+ * that day's photos. Dates show month and year; the tooltip has the full date and the age.
11
+ * Texts and dates follow the `StreetImageryLocaleProvider`.
12
+ */
13
+ export declare const MapillaryFeatureBar: ({ data, shownImage, onShow }: MapillaryFeatureBarProps) => import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { type LocationOpener, type OpenTarget } from '@osm-editor-kit/street-imagery';
2
+ export type LocationPickOnMapProps = {
3
+ /** Called after the place was opened, e.g. to mark it on the map. */
4
+ onPick?: (target: OpenTarget, opener: LocationOpener) => void;
5
+ };
6
+ /**
7
+ * Render inside `<Map>`. While a location opener is armed, the next map click opens the clicked
8
+ * place in a new tab and disarms; Escape disarms. The map's own click handlers still run.
9
+ */
10
+ export declare const LocationPickOnMap: ({ onPick }: LocationPickOnMapProps) => null;
@@ -0,0 +1,9 @@
1
+ import type { LocationOpenerId } from '@osm-editor-kit/street-imagery';
2
+ /** The opener waiting for a map click, or `null`. */
3
+ export declare const useArmedLocationOpenerId: () => LocationOpenerId | null;
4
+ export declare const useLocationPickActions: () => {
5
+ arm: (openerId: LocationOpenerId) => void;
6
+ disarm: () => void;
7
+ /** Arm, or disarm when this opener is armed already. */
8
+ toggle: (openerId: LocationOpenerId) => void;
9
+ };
@@ -18,6 +18,12 @@ type MapillaryPanelProps = {
18
18
  lookAt?: MapillaryLookAt | null;
19
19
  /** Outlines to draw in the shown image, e.g. the detections you care about. */
20
20
  outlines?: MapillaryViewerOutline[];
21
+ /**
22
+ * Hide the viewer's own attribution (creator and year). The host must then show the creator
23
+ * (`creatorName` of `onViewerPhoto`) and a link to the image on Mapillary itself; the images
24
+ * are CC BY-SA.
25
+ */
26
+ hideAttribution?: boolean;
21
27
  };
22
- export declare const MapillaryPanel: ({ photo, onPhotoSelected, onEaseMapToPoint, onViewerPhoto, lookAtBearing, lookAt, outlines, }: MapillaryPanelProps) => import("react").JSX.Element;
28
+ export declare const MapillaryPanel: ({ photo, onPhotoSelected, onEaseMapToPoint, onViewerPhoto, lookAtBearing, lookAt, outlines, hideAttribution, }: MapillaryPanelProps) => import("react").JSX.Element;
23
29
  export {};
@@ -6,6 +6,13 @@ type PanoramaxPanelProps = {
6
6
  groupPhotos: NormalizedPhoto[];
7
7
  onPhotoSelected: (selection: StreetImageryPhotoSelection) => void;
8
8
  onEaseMapToPoint: (lng: number, lat: number) => void;
9
+ /** The shown picture with creator, licence, local capture time and camera. */
10
+ onViewerPhoto?: (photo: NormalizedPhoto) => void;
11
+ /**
12
+ * Hide the viewer's own legend (a bottom drawer in narrow containers). The host must then show
13
+ * creator and licence itself, from `onViewerPhoto`.
14
+ */
15
+ hideLegend?: boolean;
9
16
  };
10
- export declare const PanoramaxPanel: ({ photo, onPhotoSelected, onEaseMapToPoint, }: PanoramaxPanelProps) => import("react").JSX.Element;
17
+ export declare const PanoramaxPanel: ({ photo, onPhotoSelected, onEaseMapToPoint, onViewerPhoto, hideLegend, }: PanoramaxPanelProps) => import("react").JSX.Element;
11
18
  export {};
@@ -0,0 +1,54 @@
1
+ /** The parts of Photo Sphere Viewer (inside the Panoramax viewer) that limiting the view needs. */
2
+ export type PsvForPanLimit = {
3
+ state: {
4
+ hFov: number;
5
+ vFov: number;
6
+ textureData?: {
7
+ panoData?: {
8
+ baseData?: {
9
+ fullWidth: number;
10
+ fullHeight: number;
11
+ croppedWidth: number;
12
+ croppedHeight: number;
13
+ croppedX: number;
14
+ croppedY: number;
15
+ };
16
+ };
17
+ };
18
+ };
19
+ dataHelper: {
20
+ fovToZoomLevel(fov: number): number;
21
+ };
22
+ getZoomLevel(): number;
23
+ zoom(level: number): void;
24
+ getPosition(): {
25
+ yaw: number;
26
+ pitch: number;
27
+ };
28
+ rotate(position: {
29
+ yaw: number;
30
+ pitch: number;
31
+ }): void;
32
+ addEventListener(type: string, listener: (event: Event) => void): void;
33
+ removeEventListener(type: string, listener: (event: Event) => void): void;
34
+ };
35
+ type ViewPosition = {
36
+ yaw: number;
37
+ pitch: number;
38
+ };
39
+ /**
40
+ * The view position moved back so the image stays in view. Flat photos are shown as a small
41
+ * patch of a sphere; outside the patch there is only black. 360° photos have no limit.
42
+ */
43
+ export declare const limitToImage: (psv: PsvForPanLimit, position: ViewPosition) => ViewPosition;
44
+ /**
45
+ * The widest zoom level for a flat photo: the image with a small margin around it (in the
46
+ * tighter direction). `null` when there is no limit.
47
+ */
48
+ export declare const widestZoomLevel: (psv: PsvForPanLimit) => number | null;
49
+ /**
50
+ * Keep the view inside the image while the user pans and zooms. Returns a function that removes
51
+ * the listeners again.
52
+ */
53
+ export declare const limitPanningToImage: (psv: PsvForPanLimit) => (() => void);
54
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { NormalizedPhoto } from '@osm-editor-kit/street-imagery';
2
+ import type { PnxPictureMetadata } from './panoramax-photo-viewer.d';
3
+ /**
4
+ * The shown picture as a photo with everything the Panoramax viewer knows about it: creator,
5
+ * licence, local capture time, camera. The viewer's own legend holds the same; hosts show it in
6
+ * their own layout instead.
7
+ */
8
+ export declare const panoramaxPhotoFromMetadata: (metadata: PnxPictureMetadata) => NormalizedPhoto;
@@ -0,0 +1,21 @@
1
+ import { type NormalizedPhoto } from '@osm-editor-kit/street-imagery';
2
+ import { type PhotoDetailsLabels } from './labels';
3
+ export type PhotoDetailsDialogProps = {
4
+ /** The photo with its `details`, as a viewer's `onViewerPhoto` gives it. */
5
+ photo: NormalizedPhoto;
6
+ open: boolean;
7
+ onClose: () => void;
8
+ /** Link to the photo on the provider's site, shown at the photo id. */
9
+ externalUrl?: string;
10
+ /** Replace single texts; the rest follows the `StreetImageryLocaleProvider`'s language. */
11
+ labels?: Partial<PhotoDetailsLabels>;
12
+ };
13
+ /**
14
+ * Everything known about a photo, as a modal: capture, camera, position, source and raw EXIF.
15
+ * Replaces the providers' own legends (Panoramax's drawer), which do not fit a small viewer box.
16
+ * A native `<dialog>`: Escape and a click on the backdrop close it.
17
+ *
18
+ * Lives in its own entry (`@osm-editor-kit/street-imagery-react/photo-details`), so apps that do
19
+ * not import it do not bundle it.
20
+ */
21
+ export declare const PhotoDetailsDialog: ({ photo, open, onClose, externalUrl, labels, }: PhotoDetailsDialogProps) => import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Optional entry: the photo details dialog. Import it from
3
+ * `@osm-editor-kit/street-imagery-react/photo-details`; apps that do not, do not bundle it.
4
+ */
5
+ export { PhotoDetailsDialog, type PhotoDetailsDialogProps } from './PhotoDetailsDialog';
6
+ export { PHOTO_DETAILS_LABELS, type PhotoDetailsLabels } from './labels';
@@ -0,0 +1,33 @@
1
+ import type { StreetImageryLocale } from '@osm-editor-kit/street-imagery';
2
+ /** Texts of the photo details dialog and of the button that opens it. */
3
+ export type PhotoDetailsLabels = {
4
+ open: string;
5
+ title: string;
6
+ close: string;
7
+ sectionCapture: string;
8
+ sectionCamera: string;
9
+ sectionPosition: string;
10
+ sectionSource: string;
11
+ creator: string;
12
+ contact: string;
13
+ capturedAt: string;
14
+ uploadedAt: string;
15
+ license: string;
16
+ type: string;
17
+ pano: string;
18
+ flat: string;
19
+ camera: string;
20
+ focalLength: string;
21
+ fieldOfView: string;
22
+ coordinates: string;
23
+ heading: string;
24
+ accuracy: string;
25
+ provider: string;
26
+ instance: string;
27
+ photoId: string;
28
+ sequence: string;
29
+ rankInSequence: string;
30
+ file: string;
31
+ exif: (count: number) => string;
32
+ };
33
+ export declare const PHOTO_DETAILS_LABELS: Record<StreetImageryLocale, PhotoDetailsLabels>;
@@ -0,0 +1,235 @@
1
+ import { useStreetImageryI18n } from './chunk-HA4GJWHI.js';
2
+ import { providerById, formatLocalDateTime, formatDate, formatRelativeDate } from '@osm-editor-kit/street-imagery';
3
+ import { useRef, useEffect } from 'react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/photo-details/labels.ts
7
+ var PHOTO_DETAILS_LABELS = {
8
+ en: {
9
+ open: "Details of this photo",
10
+ title: "Photo details",
11
+ close: "Close",
12
+ sectionCapture: "Capture",
13
+ sectionCamera: "Camera",
14
+ sectionPosition: "Position",
15
+ sectionSource: "Source",
16
+ creator: "Creator",
17
+ contact: "Contact",
18
+ capturedAt: "Captured",
19
+ uploadedAt: "Uploaded",
20
+ license: "Licence",
21
+ type: "Type",
22
+ pano: "360\xB0 panorama",
23
+ flat: "Flat photo",
24
+ camera: "Camera",
25
+ focalLength: "Focal length",
26
+ fieldOfView: "Field of view",
27
+ coordinates: "Coordinates",
28
+ heading: "Heading",
29
+ accuracy: "Accuracy",
30
+ provider: "Provider",
31
+ instance: "Instance",
32
+ photoId: "Photo ID",
33
+ sequence: "Sequence",
34
+ rankInSequence: "Position in sequence",
35
+ file: "File",
36
+ exif: (count) => `EXIF (${count} tags)`
37
+ },
38
+ de: {
39
+ open: "Details zu diesem Foto",
40
+ title: "Fotodetails",
41
+ close: "Schlie\xDFen",
42
+ sectionCapture: "Aufnahme",
43
+ sectionCamera: "Kamera",
44
+ sectionPosition: "Position",
45
+ sectionSource: "Quelle",
46
+ creator: "Urheber:in",
47
+ contact: "Kontakt",
48
+ capturedAt: "Aufgenommen",
49
+ uploadedAt: "Hochgeladen",
50
+ license: "Lizenz",
51
+ type: "Typ",
52
+ pano: "360\xB0-Panorama",
53
+ flat: "Normales Foto",
54
+ camera: "Kamera",
55
+ focalLength: "Brennweite",
56
+ fieldOfView: "Bildwinkel",
57
+ coordinates: "Koordinaten",
58
+ heading: "Blickrichtung",
59
+ accuracy: "Genauigkeit",
60
+ provider: "Anbieter",
61
+ instance: "Instanz",
62
+ photoId: "Foto-ID",
63
+ sequence: "Sequenz",
64
+ rankInSequence: "Position in der Sequenz",
65
+ file: "Datei",
66
+ exif: (count) => `EXIF (${count} Eintr\xE4ge)`
67
+ }
68
+ };
69
+ var formatBytes = (bytes) => bytes >= 1e6 ? `${(bytes / 1e6).toFixed(1)} MB` : `${Math.round(bytes / 1e3)} kB`;
70
+ var LINK_CLASS = "text-zinc-950 underline decoration-zinc-950/30 hover:decoration-zinc-950";
71
+ var Section = ({ title, rows }) => {
72
+ const filled = rows.filter(([, value]) => value != null && value !== "");
73
+ if (filled.length === 0) {
74
+ return null;
75
+ }
76
+ return /* @__PURE__ */ jsxs("section", { className: "mt-6 first:mt-0", children: [
77
+ /* @__PURE__ */ jsx("h3", { className: "text-xs/5 font-semibold tracking-wide text-zinc-500 uppercase", children: title }),
78
+ /* @__PURE__ */ jsx("dl", { className: "mt-1 grid grid-cols-[minmax(7rem,35%)_1fr] text-sm/6", children: filled.map(([term, value]) => /* @__PURE__ */ jsxs("div", { className: "col-span-2 grid grid-cols-subgrid border-t border-zinc-950/5", children: [
79
+ /* @__PURE__ */ jsx("dt", { className: "py-2 pr-3 text-zinc-500", children: term }),
80
+ /* @__PURE__ */ jsx("dd", { className: "min-w-0 py-2 wrap-break-word text-zinc-950", children: value })
81
+ ] }, term)) })
82
+ ] });
83
+ };
84
+ var PhotoDetailsDialog = ({
85
+ photo,
86
+ open,
87
+ onClose,
88
+ externalUrl,
89
+ labels
90
+ }) => {
91
+ const { locale } = useStreetImageryI18n();
92
+ const t = { ...PHOTO_DETAILS_LABELS[locale], ...labels };
93
+ const dialogRef = useRef(null);
94
+ useEffect(
95
+ function syncOpenState() {
96
+ const dialog = dialogRef.current;
97
+ if (!dialog) {
98
+ return;
99
+ }
100
+ if (open && !dialog.open) {
101
+ dialog.showModal();
102
+ dialog.firstElementChild?.scrollTo({ top: 0 });
103
+ } else if (!open && dialog.open) {
104
+ dialog.close();
105
+ }
106
+ },
107
+ [open]
108
+ );
109
+ const details = photo.details ?? {};
110
+ const provider = providerById[photo.providerId];
111
+ const capturedAt = (details.capturedAtLocal ? formatLocalDateTime(details.capturedAtLocal, locale) : null) ?? (photo.capturedAt != null ? formatDate(photo.capturedAt, locale) : null);
112
+ const contact = details.creatorContact?.includes("@") ? /* @__PURE__ */ jsx("a", { className: LINK_CLASS, href: `mailto:${details.creatorContact}`, children: details.creatorContact }) : details.creatorContact;
113
+ const license = details.license && details.licenseUrl ? /* @__PURE__ */ jsx("a", { className: LINK_CLASS, href: details.licenseUrl, rel: "noreferrer", target: "_blank", children: details.license }) : details.license;
114
+ const photoId = externalUrl ? /* @__PURE__ */ jsx("a", { className: LINK_CLASS, href: externalUrl, rel: "noreferrer", target: "_blank", children: photo.photoId }) : photo.photoId;
115
+ const exif = Object.entries(details.exif ?? {}).sort(([a], [b]) => a.localeCompare(b));
116
+ return /* @__PURE__ */ jsx(
117
+ "dialog",
118
+ {
119
+ "aria-labelledby": "photo-details-title",
120
+ className: "m-auto w-full max-w-lg rounded-2xl bg-white p-0 shadow-lg ring-1 ring-zinc-950/10 backdrop:bg-zinc-950/25",
121
+ ref: dialogRef,
122
+ onClick: (event) => {
123
+ if (event.target === event.currentTarget) {
124
+ onClose();
125
+ }
126
+ },
127
+ onClose,
128
+ onKeyDown: (event) => {
129
+ if (event.key === "Escape") {
130
+ event.stopPropagation();
131
+ }
132
+ },
133
+ children: /* @__PURE__ */ jsxs("div", { className: "max-h-[85vh] overflow-y-auto p-6 sm:p-8", children: [
134
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
135
+ /* @__PURE__ */ jsxs("h2", { className: "text-base/6 font-semibold text-zinc-950", id: "photo-details-title", children: [
136
+ t.title,
137
+ /* @__PURE__ */ jsx("span", { className: "ml-2 font-normal text-zinc-500", children: provider.label })
138
+ ] }),
139
+ /* @__PURE__ */ jsx(
140
+ "button",
141
+ {
142
+ className: "-m-1 rounded-md p-1 text-sm/6 font-medium text-zinc-500 hover:bg-zinc-950/5 hover:text-zinc-950",
143
+ type: "button",
144
+ onClick: onClose,
145
+ children: t.close
146
+ }
147
+ )
148
+ ] }),
149
+ /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
150
+ /* @__PURE__ */ jsx(
151
+ Section,
152
+ {
153
+ rows: [
154
+ [t.creator, photo.creatorName],
155
+ [t.contact, contact],
156
+ [
157
+ t.capturedAt,
158
+ capturedAt && photo.capturedAt != null ? `${capturedAt} \xB7 ${formatRelativeDate(photo.capturedAt, locale)}` : capturedAt
159
+ ],
160
+ [
161
+ t.uploadedAt,
162
+ details.uploadedAt != null ? formatDate(details.uploadedAt, locale) : null
163
+ ],
164
+ [t.license, license]
165
+ ],
166
+ title: t.sectionCapture
167
+ }
168
+ ),
169
+ /* @__PURE__ */ jsx(
170
+ Section,
171
+ {
172
+ rows: [
173
+ [t.type, photo.isPano == null ? null : photo.isPano ? t.pano : t.flat],
174
+ [t.camera, details.camera],
175
+ [t.focalLength, details.focalLengthMm != null ? `${details.focalLengthMm} mm` : null],
176
+ [
177
+ t.fieldOfView,
178
+ details.fieldOfViewDeg != null ? `${Math.round(details.fieldOfViewDeg)}\xB0` : null
179
+ ]
180
+ ],
181
+ title: t.sectionCamera
182
+ }
183
+ ),
184
+ /* @__PURE__ */ jsx(
185
+ Section,
186
+ {
187
+ rows: [
188
+ [t.coordinates, `${photo.lngLat[1].toFixed(6)}, ${photo.lngLat[0].toFixed(6)}`],
189
+ [t.heading, photo.heading != null ? `${Math.round(photo.heading)}\xB0` : null],
190
+ [
191
+ t.accuracy,
192
+ details.positionAccuracyMeters != null ? `\xB1 ${details.positionAccuracyMeters} m` : null
193
+ ]
194
+ ],
195
+ title: t.sectionPosition
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx(
199
+ Section,
200
+ {
201
+ rows: [
202
+ [t.provider, provider.label],
203
+ [t.instance, details.instance],
204
+ [t.photoId, photoId],
205
+ [t.sequence, photo.sequenceId],
206
+ [t.rankInSequence, details.rankInSequence],
207
+ [
208
+ t.file,
209
+ details.originalFileName ? `${details.originalFileName}${details.originalFileSizeBytes != null ? ` (${formatBytes(details.originalFileSizeBytes)})` : ""}` : null
210
+ ]
211
+ ],
212
+ title: t.sectionSource
213
+ }
214
+ ),
215
+ exif.length > 0 ? /* @__PURE__ */ jsxs("details", { className: "mt-6 border-t border-zinc-950/5 pt-3", children: [
216
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-xs/5 font-semibold tracking-wide text-zinc-500 uppercase hover:text-zinc-950", children: t.exif(exif.length) }),
217
+ /* @__PURE__ */ jsx("dl", { className: "mt-2 grid grid-cols-[minmax(7rem,45%)_1fr] text-xs/5", children: exif.map(([key, value]) => /* @__PURE__ */ jsxs(
218
+ "div",
219
+ {
220
+ className: "col-span-2 grid grid-cols-subgrid border-t border-zinc-950/5",
221
+ children: [
222
+ /* @__PURE__ */ jsx("dt", { className: "py-1 pr-3 wrap-break-word text-zinc-500", children: key }),
223
+ /* @__PURE__ */ jsx("dd", { className: "min-w-0 py-1 wrap-break-word text-zinc-950", children: value })
224
+ ]
225
+ },
226
+ key
227
+ )) })
228
+ ] }) : null
229
+ ] })
230
+ ] })
231
+ }
232
+ );
233
+ };
234
+
235
+ export { PHOTO_DETAILS_LABELS, PhotoDetailsDialog };
@@ -1,7 +1,14 @@
1
1
  import type { ViewSuggestion } from '@osm-editor-kit/street-imagery';
2
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;
3
8
  export type FloatingPhotoViewerProps = {
4
9
  title: ReactNode;
10
+ /** Buttons right after the title, e.g. `<FloatingViewerInfoButton>` for the photo's details. */
11
+ titleActions?: ReactNode;
5
12
  /** Suggested views (viewpoint × direction); shown as chips. Empty hides the strip. */
6
13
  suggestions: ViewSuggestion[];
7
14
  activeDirectionKey: string | null;
@@ -11,6 +18,21 @@ export type FloatingPhotoViewerProps = {
11
18
  onBack: () => void;
12
19
  onForward: () => void;
13
20
  onClose: () => void;
21
+ /**
22
+ * Step to the neighbouring photo, e.g. along the clicked street: the `<` `>` buttons at the
23
+ * left of the header. A missing handler disables its button.
24
+ */
25
+ step?: {
26
+ onPrevious?: () => void;
27
+ onNext?: () => void;
28
+ previousLabel: string;
29
+ nextLabel: string;
30
+ };
31
+ /**
32
+ * Bar below the header, in place of the suggested-view chips; e.g. the map feature the photos
33
+ * belong to, with a button per capture day.
34
+ */
35
+ toolbar?: ReactNode;
14
36
  /** Short status line, e.g. "Loading…" or "No photos from the last 2 years". */
15
37
  status?: ReactNode;
16
38
  /** Viewer body (provider panel). */
@@ -26,6 +48,7 @@ export type FloatingPhotoViewerProps = {
26
48
  };
27
49
  /**
28
50
  * Photo viewer box floating over the map. Render inside a positioned (relative) map container.
29
- * Drag the header to move, drag the left edge to resize. Keys: Esc closes, [ and ] step history.
51
+ * Drag the header to move, drag the left edge to resize. Keys: Esc closes, [ and ] step history,
52
+ * Alt + arrow left/right step along (`step`).
30
53
  */
31
- export declare const FloatingPhotoViewer: ({ title, suggestions, activeDirectionKey, onSelectSuggestion, canGoBack, canGoForward, onBack, onForward, onClose, status, children, footer, drawer, storageKey, }: FloatingPhotoViewerProps) => import("react").JSX.Element;
54
+ export declare const FloatingPhotoViewer: ({ title, titleActions, suggestions, activeDirectionKey, onSelectSuggestion, canGoBack, canGoForward, onBack, onForward, onClose, step, toolbar, status, children, footer, drawer, storageKey, }: FloatingPhotoViewerProps) => import("react").JSX.Element;
@@ -9,21 +9,12 @@ type OpenInput = {
9
9
  /** Clicked line, drawn as context on the map. */
10
10
  line?: LngLat[] | null;
11
11
  };
12
- export declare const useViewpointSessionId: () => number;
13
12
  export declare const useViewpoints: () => Viewpoint[];
14
13
  export declare const useViewpointLine: () => LngLat[] | null;
15
14
  export declare const useActiveDirectionKey: () => string | null;
16
15
  export declare const useCurrentHistoryEntry: () => ViewpointHistoryEntry;
17
16
  export declare const useCanGoBack: () => boolean;
18
17
  export declare const useCanGoForward: () => boolean;
19
- export declare const useViewpointSessionActions: () => {
20
- open: (input: OpenInput) => void;
21
- /** Show a photo; truncates forward history. No-op when it is already the current photo. */
22
- showPhoto: (entry: ViewpointHistoryEntry) => void;
23
- back: () => ViewpointHistoryEntry | null;
24
- forward: () => ViewpointHistoryEntry | null;
25
- close: () => void;
26
- };
27
18
  /** Actions + current entry outside React (event handlers, tests). */
28
19
  export declare const getViewpointSession: () => {
29
20
  actions: {
@@ -33,6 +24,8 @@ export declare const getViewpointSession: () => {
33
24
  back: () => ViewpointHistoryEntry | null;
34
25
  forward: () => ViewpointHistoryEntry | null;
35
26
  close: () => void;
27
+ /** Like `close`, and forget the history too: the viewer was closed. */
28
+ reset: () => void;
36
29
  };
37
30
  current: ViewpointHistoryEntry;
38
31
  };
@@ -1,4 +1,4 @@
1
- import type { ViewSuggestion, ViewpointRole } from '@osm-editor-kit/street-imagery';
2
- export declare const viewpointRoleLabel: (role: ViewpointRole) => string;
3
- /** Short human label, e.g. "Start → forward", "Here · N", "Junction · in". */
4
- export declare const viewSuggestionLabel: ({ viewpoint, direction }: ViewSuggestion) => string;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@osm-editor-kit/street-imagery-react",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "React MapLibre layers and photo viewers for street-level imagery providers.",
5
5
  "homepage": "https://github.com/osmberlin/street-space-editor/tree/main/packages/street-imagery-react",
6
6
  "license": "MIT",
@@ -19,6 +19,10 @@
19
19
  "types": "./dist/index.d.ts",
20
20
  "import": "./dist/index.js"
21
21
  },
22
+ "./photo-details": {
23
+ "types": "./dist/photo-details/index.d.ts",
24
+ "import": "./dist/photo-details.js"
25
+ },
22
26
  "./package.json": "./package.json"
23
27
  },
24
28
  "publishConfig": {
@@ -51,7 +55,7 @@
51
55
  "zustand": "^5.0.7"
52
56
  },
53
57
  "peerDependencies": {
54
- "@osm-editor-kit/street-imagery": "^0.1.0-alpha.4",
58
+ "@osm-editor-kit/street-imagery": "^0.1.0-alpha.5",
55
59
  "@panoramax/web-viewer": "^5",
56
60
  "@tanstack/react-query": "^5",
57
61
  "mapillary-js": "^4",
@@ -66,6 +70,10 @@
66
70
  "types": "./dist/index.d.ts",
67
71
  "import": "./dist/index.js"
68
72
  },
73
+ "./photo-details": {
74
+ "types": "./dist/photo-details/index.d.ts",
75
+ "import": "./dist/photo-details.js"
76
+ },
69
77
  "./package.json": "./package.json"
70
78
  }
71
79
  }
@@ -1,2 +0,0 @@
1
- export { MapillaryPanel } from './chunk-72HJV6II.js';
2
- import './chunk-25UGGXIE.js';
@@ -1,2 +0,0 @@
1
- export { PanoramaxPanel } from './chunk-ZH3TPW4D.js';
2
- import './chunk-25UGGXIE.js';