@osm-editor-kit/street-imagery-react 0.1.0-alpha.7 → 0.1.0-alpha.8
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/README.md +40 -1
- package/dist/{chunk-EE7V3OIZ.js → chunk-ZAIXGYAH.js} +31 -0
- package/dist/filters/PhotoDateRangeFilter.d.ts +41 -0
- package/dist/i18n/messages.d.ts +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +310 -4
- package/dist/mapillary/SelectedMapFeatureLayer.d.ts +19 -0
- package/dist/mapillary/useMapillaryFeatures.d.ts +18 -0
- package/dist/photo-details.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -117,10 +117,49 @@ const { data: detections } = useMapillaryImageDetections(photo.photoId, {
|
|
|
117
117
|
<StreetLevelImageryViewer photo={photo} outlines={detections?.map((d) => ({ id: d.id, outline: d.outline }))} … />
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
- Map layers: `filter.mapFeatureValue` limits which signs/objects are drawn (`matchesAnyGroup(
|
|
120
|
+
- Map layers: `filter.mapFeatureValue` limits which signs/objects are drawn: `signGroupFilter(['parking', 'bike'])` for sign groups, or `matchesAnyGroup(…)`.
|
|
121
121
|
- Suggested views: `useViewSuggestions(viewpoints, { sources: [mapillaryPhotoSource, streetViewPhotoSource] })`.
|
|
122
122
|
- `turnMapillaryViewerTo` / `setMapillaryViewerOutlines` work on a mapillary-js `Viewer` you own.
|
|
123
123
|
|
|
124
|
+
## A selected sign: viewer, bar and map highlight
|
|
125
|
+
|
|
126
|
+
Keep the selected sign (`featureId`) and its shown photo in your own state or URL:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
const { data, shownImage, firstImage } = useSelectedMapillaryFeature({
|
|
130
|
+
featureId,
|
|
131
|
+
shownPhotoId, // the photo the viewer shows now
|
|
132
|
+
minCapturedAt: fromMs, // optional: open the best photo from this time on
|
|
133
|
+
})
|
|
134
|
+
// Open `firstImage` first: showPhoto(targetImageToPhoto(firstImage))
|
|
135
|
+
<MapillaryFeatureBar data={data} shownImage={shownImage} onShow={(image) => showPhoto(targetImageToPhoto(image))} />
|
|
136
|
+
<StreetLevelImageryViewer lookAt={{ lngLat: data.feature.lngLat, outline: shownImage?.outline, value: data.feature.value }} … />
|
|
137
|
+
// On the map, after StreetLevelImagerySourcesAndLayers: the sign's halo and a line from the camera
|
|
138
|
+
<SelectedMapFeatureLayer data={data} shownImage={shownImage} providers={providers} bbox={bbox} zoom={zoom} />
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A click on a sign dot: `queryStreetImageryFeatures(event).find((hit) => hit.kind === 'mapFeature')?.featureId`.
|
|
142
|
+
|
|
143
|
+
## Date filter with a slider
|
|
144
|
+
|
|
145
|
+
`PhotoDateRangeFilter` is the date filter of iD's photo panel as a controlled component: one slider with two handles on a scale that gives recent years more room. It works on plain `{ from?, to? }` days (`YYYY-MM-DD`), the same `DateRange` the layers take as `filter.date`.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const { photos } = useAllProviderPhotos(providers, bbox, zoom) // no date: all photos in view
|
|
149
|
+
|
|
150
|
+
<PhotoDateRangeFilter
|
|
151
|
+
value={date}
|
|
152
|
+
onChange={setDate} // on release of a handle, not while dragging
|
|
153
|
+
capturedAt={photos.map((photo) => photo.capturedAt)} // marks: where photos are
|
|
154
|
+
yearLines={[1, 2, 3, 4]} // lines with a label; a click shows the last N years
|
|
155
|
+
recommendedMaxAgeYears={2} // this line is red
|
|
156
|
+
markers={[{ date: '2024-04-01', label: 'Survey start' }]} // dashed lines for fixed dates
|
|
157
|
+
dateInputs="collapsed" // exact days: 'collapsed' (default), 'open' or 'none'
|
|
158
|
+
/>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
A handle at its end means no limit, so the left end also shows photos older than the slider spans (`maxYears`, default 10). The scale itself is in the core package (`dateSliderPosition`, `dateSliderBins`, …) for a host that draws its own chart. The texts are in `messages.dateFilter`.
|
|
162
|
+
|
|
124
163
|
## Pick a place on the map to open elsewhere
|
|
125
164
|
|
|
126
165
|
A button arms a location opener (see `@osm-editor-kit/street-imagery`), the next map click opens the clicked place in that service and disarms; Escape cancels.
|
|
@@ -18,6 +18,21 @@ var en = {
|
|
|
18
18
|
nextPhotoOfView: (label) => `${label} \u2014 click for the next photo`
|
|
19
19
|
},
|
|
20
20
|
unknownDate: "Unknown date",
|
|
21
|
+
dateFilter: {
|
|
22
|
+
allDates: "All dates",
|
|
23
|
+
since: (date) => `Since ${date}`,
|
|
24
|
+
until: (date) => `Until ${date}`,
|
|
25
|
+
between: (from, to) => `${from} \u2013 ${to}`,
|
|
26
|
+
yearsShort: (years) => `${years} y`,
|
|
27
|
+
lastYears: (years) => years === 1 ? "Show the last year" : `Show the last ${years} years`,
|
|
28
|
+
recommendedMaxAge: (years) => `Recommended: photos of the last ${years === 1 ? "year" : `${years} years`}`,
|
|
29
|
+
fromHandle: "Oldest photos to show",
|
|
30
|
+
toHandle: "Newest photos to show",
|
|
31
|
+
exactDates: "Exact dates",
|
|
32
|
+
from: "From",
|
|
33
|
+
to: "To",
|
|
34
|
+
photoCount: (count) => count === 1 ? "1 photo" : `${count} photos`
|
|
35
|
+
},
|
|
21
36
|
viewpointRole: {
|
|
22
37
|
here: "Here",
|
|
23
38
|
"line-start": "Start",
|
|
@@ -59,6 +74,21 @@ var de = {
|
|
|
59
74
|
nextPhotoOfView: (label) => `${label} \u2014 klicken f\xFCr das n\xE4chste Foto`
|
|
60
75
|
},
|
|
61
76
|
unknownDate: "Datum unbekannt",
|
|
77
|
+
dateFilter: {
|
|
78
|
+
allDates: "Alle Daten",
|
|
79
|
+
since: (date) => `Seit ${date}`,
|
|
80
|
+
until: (date) => `Bis ${date}`,
|
|
81
|
+
between: (from, to) => `${from} \u2013 ${to}`,
|
|
82
|
+
yearsShort: (years) => `${years} J.`,
|
|
83
|
+
lastYears: (years) => years === 1 ? "Das letzte Jahr zeigen" : `Die letzten ${years} Jahre zeigen`,
|
|
84
|
+
recommendedMaxAge: (years) => `Empfohlen: Fotos ${years === 1 ? "des letzten Jahres" : `der letzten ${years} Jahre`}`,
|
|
85
|
+
fromHandle: "\xC4lteste Fotos, die gezeigt werden",
|
|
86
|
+
toHandle: "Neueste Fotos, die gezeigt werden",
|
|
87
|
+
exactDates: "Genaue Daten",
|
|
88
|
+
from: "Von",
|
|
89
|
+
to: "Bis",
|
|
90
|
+
photoCount: (count) => count === 1 ? "1 Foto" : `${count} Fotos`
|
|
91
|
+
},
|
|
62
92
|
viewpointRole: {
|
|
63
93
|
here: "Hier",
|
|
64
94
|
"line-start": "Anfang",
|
|
@@ -95,6 +125,7 @@ var DEFAULT_I18N = {
|
|
|
95
125
|
var I18nContext = createContext(DEFAULT_I18N);
|
|
96
126
|
var mergeMessages = (base, overrides) => overrides ? {
|
|
97
127
|
unknownDate: overrides.unknownDate ?? base.unknownDate,
|
|
128
|
+
dateFilter: { ...base.dateFilter, ...overrides.dateFilter },
|
|
98
129
|
viewer: { ...base.viewer, ...overrides.viewer },
|
|
99
130
|
viewpointRole: { ...base.viewpointRole, ...overrides.viewpointRole },
|
|
100
131
|
compass: overrides.compass?.length === 8 ? overrides.compass : base.compass,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type DateRange } from '@osm-editor-kit/street-imagery';
|
|
2
|
+
/** A fixed date to show in the chart, e.g. the start of a project or of a survey. */
|
|
3
|
+
export type PhotoDateMarker = {
|
|
4
|
+
/** A day as `YYYY-MM-DD`, or a time (ms). */
|
|
5
|
+
date: string | number;
|
|
6
|
+
label: string;
|
|
7
|
+
/** CSS color of line and label. Default red. */
|
|
8
|
+
color?: string;
|
|
9
|
+
};
|
|
10
|
+
export type PhotoDateRangeFilterProps = {
|
|
11
|
+
/** The chosen range, days as `YYYY-MM-DD`. No `from` and no `to`: all dates. */
|
|
12
|
+
value: DateRange;
|
|
13
|
+
/** Called when a handle is released, a year label is clicked or a date input changes. */
|
|
14
|
+
onChange: (range: DateRange) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Capture times (ms) of the photos to show as marks above the slider, usually all photos in
|
|
17
|
+
* view before the date filter. Without it there are no marks.
|
|
18
|
+
*/
|
|
19
|
+
capturedAt?: Iterable<number | null | undefined>;
|
|
20
|
+
/** Lines with a label at these ages in years; a click on a label shows that many years. */
|
|
21
|
+
yearLines?: readonly number[];
|
|
22
|
+
/** Its line is red; added to `yearLines` when it is not one of them. */
|
|
23
|
+
recommendedMaxAgeYears?: number;
|
|
24
|
+
markers?: readonly PhotoDateMarker[];
|
|
25
|
+
/** Years the slider spans. Default 10. */
|
|
26
|
+
maxYears?: number;
|
|
27
|
+
/** Reference time (ms). Default: the time of the first render. */
|
|
28
|
+
now?: number;
|
|
29
|
+
/**
|
|
30
|
+
* The two date inputs for exact days, in sync with the slider: folded away (default), open,
|
|
31
|
+
* or left out.
|
|
32
|
+
*/
|
|
33
|
+
dateInputs?: 'collapsed' | 'open' | 'none';
|
|
34
|
+
className?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Date filter for photos after iD's photo age slider: one slider with two handles on a scale that
|
|
38
|
+
* gives recent years more room, marks that show how many photos there are at each time, lines at
|
|
39
|
+
* whole years, and optional fixed markers. Controlled: `value` in, `onChange` out.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PhotoDateRangeFilter: ({ value, onChange, capturedAt, yearLines, recommendedMaxAgeYears, markers, maxYears, now: nowProp, dateInputs, className, }: PhotoDateRangeFilterProps) => import("react").JSX.Element;
|
package/dist/i18n/messages.d.ts
CHANGED
|
@@ -21,6 +21,24 @@ export type StreetImageryMessages = {
|
|
|
21
21
|
/** Eight compass points, clockwise from north: N, NE, E, SE, S, SW, W, NW. */
|
|
22
22
|
compass: readonly string[];
|
|
23
23
|
unknownDate: string;
|
|
24
|
+
dateFilter: {
|
|
25
|
+
allDates: string;
|
|
26
|
+
/** e.g. "Since 5 Oct 2024". */
|
|
27
|
+
since: (date: string) => string;
|
|
28
|
+
until: (date: string) => string;
|
|
29
|
+
between: (from: string, to: string) => string;
|
|
30
|
+
/** Short label below a year line, e.g. "2 y". */
|
|
31
|
+
yearsShort: (years: number) => string;
|
|
32
|
+
/** Tooltip of a year label; a click shows the photos of the last `years` years. */
|
|
33
|
+
lastYears: (years: number) => string;
|
|
34
|
+
recommendedMaxAge: (years: number) => string;
|
|
35
|
+
fromHandle: string;
|
|
36
|
+
toHandle: string;
|
|
37
|
+
exactDates: string;
|
|
38
|
+
from: string;
|
|
39
|
+
to: string;
|
|
40
|
+
photoCount: (count: number) => string;
|
|
41
|
+
};
|
|
24
42
|
/** e.g. "Start, looking along the street"; `role` is the viewpoint's label. */
|
|
25
43
|
viewDirection: (role: string, kind: ViewDirection['kind']) => string;
|
|
26
44
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -21,9 +21,11 @@ export { mapillaryViewFor, setMapillaryViewerOutlines, turnMapillaryViewerTo, ty
|
|
|
21
21
|
export * from './mapillary/useMapillaryFeatures';
|
|
22
22
|
export { LocationPickOnMap, type LocationPickOnMapProps } from './openers/LocationPickOnMap';
|
|
23
23
|
export * from './openers/useLocationPickStore';
|
|
24
|
+
export { SelectedMapFeatureLayer, type SelectedMapFeatureLayerProps, } from './mapillary/SelectedMapFeatureLayer';
|
|
24
25
|
export { MapillaryFeatureBar, type MapillaryFeatureBarProps } from './mapillary/MapillaryFeatureBar';
|
|
25
26
|
export { MAPILLARY_FEATURE_BAR_LABELS, type MapillaryFeatureBarLabels, } from './mapillary/featureBarLabels';
|
|
26
27
|
export { STREET_IMAGERY_MESSAGES, type StreetImageryMessages } from './i18n/messages';
|
|
27
28
|
export { StreetImageryLocaleProvider, useStreetImageryI18n, type StreetImageryMessageOverrides, } from './i18n/StreetImageryLocaleProvider';
|
|
29
|
+
export { PhotoDateRangeFilter, type PhotoDateMarker, type PhotoDateRangeFilterProps, } from './filters/PhotoDateRangeFilter';
|
|
28
30
|
export { PhotoDate } from './i18n/PhotoDate';
|
|
29
31
|
export { PhotoCount } from './i18n/PhotoCount';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useStreetImageryI18n } from './chunk-
|
|
2
|
-
export { STREET_IMAGERY_MESSAGES, StreetImageryLocaleProvider, useStreetImageryI18n } from './chunk-
|
|
1
|
+
import { useStreetImageryI18n } from './chunk-ZAIXGYAH.js';
|
|
2
|
+
export { STREET_IMAGERY_MESSAGES, StreetImageryLocaleProvider, useStreetImageryI18n } from './chunk-ZAIXGYAH.js';
|
|
3
3
|
export { MapillaryPanel, mapillaryViewFor, setMapillaryViewerOutlines, turnMapillaryViewerTo } from './chunk-53RA4OSJ.js';
|
|
4
4
|
export { PanoramaxPanel } from './chunk-EQ2JKLGT.js';
|
|
5
5
|
export { useViewerActions, useViewerBearing, useViewerHfov, useViewerLngLat } from './chunk-25UGGXIE.js';
|
|
6
|
-
import { distanceMeters, sequencesToFeatureCollection, emptyLineCollection, adapterById, providerById, providerCoversBbox, viewConeGeoJson, viewShapeRadiusMeters, setStreetImageryConfig, viewfieldLayerId, photoLayerId, featureLayerId, PROVIDER_IDS, mapFeatureMatchesDateRange, isBrowserAvailableProvider, photoMatchesFilters, resolvePhotoPanoramaUrl, photoFullUrlQueryKey, resolvePhotoThumbnailUrl, photoThumbnailQueryKey, formatLocalDateTime, formatMonth, formatRelativeDate, formatDateTooltip, mapillaryPhotoSource, buildViewSuggestions, yearsToMs, SIGN_TARGET, fetchMapFeatureImages, fetchMapillaryMapFeaturesInBbox, bboxAround, fetchImageDetections, findLocationOpener, mapillaryValueName, mapillaryIconUrl, openLocationInNewTab, alignLineToPoints, coneRadiusMeters, mapFeaturesToFeatureCollection, emptyPointCollection, buildMapFeatureLayerFilter, featureSourceId, photosToFeatureCollection, photosToViewfieldsFeatureCollection, emptyPolygonCollection, buildPhotoLayerFilter, photoSourceId, viewfieldSourceId, sequenceSourceId, sequenceLayerId, viewfieldLineLayerId, VIEWFIELD_MIN_ZOOM } from '@osm-editor-kit/street-imagery';
|
|
6
|
+
import { distanceMeters, sequencesToFeatureCollection, emptyLineCollection, adapterById, providerById, providerCoversBbox, viewConeGeoJson, viewShapeRadiusMeters, setStreetImageryConfig, viewfieldLayerId, photoLayerId, featureLayerId, PROVIDER_IDS, mapFeatureMatchesDateRange, isBrowserAvailableProvider, photoMatchesFilters, resolvePhotoPanoramaUrl, photoFullUrlQueryKey, resolvePhotoThumbnailUrl, photoThumbnailQueryKey, formatLocalDateTime, formatMonth, formatRelativeDate, formatDateTooltip, mapillaryPhotoSource, buildViewSuggestions, yearsToMs, SIGN_TARGET, fetchMapFeatureImages, fetchMapillaryMapFeaturesInBbox, bboxAround, fetchImageDetections, bestTargetImage, findLocationOpener, MAP_FEATURE_COLOR, mapillaryValueName, mapillaryIconUrl, dateRangeToSliderPositions, sliderPositionsToDateRange, dateSliderBins, dateSliderPosition, yearsAgoMs, parseIsoDateStartMs, openLocationInNewTab, formatDate, isIsoDate, alignLineToPoints, coneRadiusMeters, mapFeaturesToFeatureCollection, emptyPointCollection, buildMapFeatureLayerFilter, featureSourceId, photosToFeatureCollection, photosToViewfieldsFeatureCollection, emptyPolygonCollection, buildPhotoLayerFilter, photoSourceId, viewfieldSourceId, sequenceSourceId, sequenceLayerId, viewfieldLineLayerId, VIEWFIELD_MIN_ZOOM } from '@osm-editor-kit/street-imagery';
|
|
7
7
|
import { Source, Layer, useMap } from 'react-map-gl/maplibre';
|
|
8
8
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
9
9
|
import { lazy, useEffect, Fragment as Fragment$1, Suspense, useState, useRef, useEffectEvent } from 'react';
|
|
@@ -1484,6 +1484,20 @@ var useMapillaryImageDetections = (imageId, { filter, filterKey } = {}) => useQu
|
|
|
1484
1484
|
enabled: !!imageId,
|
|
1485
1485
|
staleTime: STALE_TIME
|
|
1486
1486
|
});
|
|
1487
|
+
var useSelectedMapillaryFeature = ({
|
|
1488
|
+
featureId,
|
|
1489
|
+
shownPhotoId,
|
|
1490
|
+
minCapturedAt,
|
|
1491
|
+
shape
|
|
1492
|
+
}) => {
|
|
1493
|
+
const { data, isLoading, isError } = useMapillaryMapFeatureImages(featureId, { shape });
|
|
1494
|
+
const shownImage = data?.images.find((image) => image.id === shownPhotoId) ?? null;
|
|
1495
|
+
const firstImage = data ? bestTargetImage(data.images, data.feature.lngLat, {
|
|
1496
|
+
minCapturedAt: minCapturedAt ?? void 0,
|
|
1497
|
+
shape
|
|
1498
|
+
}) ?? null : null;
|
|
1499
|
+
return { data: data ?? null, isLoading, isError, shownImage, firstImage };
|
|
1500
|
+
};
|
|
1487
1501
|
var useLocationPickStore = create()((set) => ({
|
|
1488
1502
|
armedOpenerId: null,
|
|
1489
1503
|
actions: {
|
|
@@ -1535,6 +1549,70 @@ var LocationPickOnMap = ({ onPick }) => {
|
|
|
1535
1549
|
);
|
|
1536
1550
|
return null;
|
|
1537
1551
|
};
|
|
1552
|
+
var SelectedMapFeatureLayer = ({
|
|
1553
|
+
data,
|
|
1554
|
+
shownImage,
|
|
1555
|
+
providers,
|
|
1556
|
+
bbox,
|
|
1557
|
+
zoom,
|
|
1558
|
+
color = MAP_FEATURE_COLOR
|
|
1559
|
+
}) => {
|
|
1560
|
+
const drawnFeatures = useAllProviderMapFeatures(providers, bbox, zoom);
|
|
1561
|
+
const target = data ? drawnFeatures.find((feature) => feature.featureId === data.feature.id)?.lngLat ?? data.feature.lngLat : null;
|
|
1562
|
+
const camera = shownImage?.lngLat;
|
|
1563
|
+
const points = {
|
|
1564
|
+
type: "FeatureCollection",
|
|
1565
|
+
features: target ? [{ type: "Feature", properties: {}, geometry: { type: "Point", coordinates: target } }] : []
|
|
1566
|
+
};
|
|
1567
|
+
const line = {
|
|
1568
|
+
type: "FeatureCollection",
|
|
1569
|
+
features: target && camera ? [
|
|
1570
|
+
{
|
|
1571
|
+
type: "Feature",
|
|
1572
|
+
properties: {},
|
|
1573
|
+
geometry: { type: "LineString", coordinates: [camera, target] }
|
|
1574
|
+
}
|
|
1575
|
+
] : []
|
|
1576
|
+
};
|
|
1577
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1578
|
+
/* @__PURE__ */ jsx(Source, { data: line, id: "selected-map-feature-line", type: "geojson" }),
|
|
1579
|
+
/* @__PURE__ */ jsx(
|
|
1580
|
+
Layer,
|
|
1581
|
+
{
|
|
1582
|
+
id: "selected-map-feature-line",
|
|
1583
|
+
paint: { "line-color": SELECTION_COLOR, "line-width": 1.5, "line-dasharray": [2, 1.5] },
|
|
1584
|
+
source: "selected-map-feature-line",
|
|
1585
|
+
type: "line"
|
|
1586
|
+
}
|
|
1587
|
+
),
|
|
1588
|
+
/* @__PURE__ */ jsx(Source, { data: points, id: "selected-map-feature-point", type: "geojson" }),
|
|
1589
|
+
/* @__PURE__ */ jsx(
|
|
1590
|
+
Layer,
|
|
1591
|
+
{
|
|
1592
|
+
id: "selected-map-feature-disk",
|
|
1593
|
+
paint: {
|
|
1594
|
+
"circle-radius": 17,
|
|
1595
|
+
"circle-color": color,
|
|
1596
|
+
"circle-opacity": VIEW_SHAPE_FILL_OPACITY
|
|
1597
|
+
},
|
|
1598
|
+
source: "selected-map-feature-point",
|
|
1599
|
+
type: "circle"
|
|
1600
|
+
}
|
|
1601
|
+
),
|
|
1602
|
+
/* @__PURE__ */ jsx(
|
|
1603
|
+
Layer,
|
|
1604
|
+
{
|
|
1605
|
+
id: "selected-map-feature-dot",
|
|
1606
|
+
paint: {
|
|
1607
|
+
"circle-radius": ["interpolate", ["linear"], ["zoom"], 10, 1.8, 14, 3.6, 18, 4.8],
|
|
1608
|
+
"circle-color": color
|
|
1609
|
+
},
|
|
1610
|
+
source: "selected-map-feature-point",
|
|
1611
|
+
type: "circle"
|
|
1612
|
+
}
|
|
1613
|
+
)
|
|
1614
|
+
] });
|
|
1615
|
+
};
|
|
1538
1616
|
var MAPILLARY_FEATURE_BAR_LABELS = {
|
|
1539
1617
|
en: {
|
|
1540
1618
|
name: (value) => mapillaryValueName(value, "en"),
|
|
@@ -1630,5 +1708,233 @@ ${labels.dayPhotos(day.images.length, active && day.images.length > 1)}`,
|
|
|
1630
1708
|
)
|
|
1631
1709
|
] });
|
|
1632
1710
|
};
|
|
1711
|
+
var DEFAULT_YEAR_LINES = [1, 2, 3, 4];
|
|
1712
|
+
var BINS = 60;
|
|
1713
|
+
var STEP = 1e-3;
|
|
1714
|
+
var CHART_HEIGHT_PX = 36;
|
|
1715
|
+
var RED = "#dc2626";
|
|
1716
|
+
var HANDLE_CLASS = "pointer-events-none absolute inset-0 m-0 h-full w-full cursor-pointer appearance-none bg-transparent focus:outline-none [&::-webkit-slider-runnable-track]:appearance-none [&::-webkit-slider-runnable-track]:bg-transparent [&::-moz-range-track]:bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:size-4 [&::-webkit-slider-thumb]:cursor-grab [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:bg-slate-800 [&::-webkit-slider-thumb]:shadow [&::-moz-range-thumb]:pointer-events-auto [&::-moz-range-thumb]:box-border [&::-moz-range-thumb]:size-4 [&::-moz-range-thumb]:cursor-grab [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-white [&::-moz-range-thumb]:bg-slate-800 [&::-moz-range-thumb]:shadow focus-visible:[&::-webkit-slider-thumb]:ring-2 focus-visible:[&::-webkit-slider-thumb]:ring-sky-500 focus-visible:[&::-moz-range-thumb]:ring-2 focus-visible:[&::-moz-range-thumb]:ring-sky-500";
|
|
1717
|
+
var percent = (position) => `${position * 100}%`;
|
|
1718
|
+
var PhotoDateRangeFilter = ({
|
|
1719
|
+
value,
|
|
1720
|
+
onChange,
|
|
1721
|
+
capturedAt,
|
|
1722
|
+
yearLines = DEFAULT_YEAR_LINES,
|
|
1723
|
+
recommendedMaxAgeYears,
|
|
1724
|
+
markers = [],
|
|
1725
|
+
maxYears,
|
|
1726
|
+
now: nowProp,
|
|
1727
|
+
dateInputs = "collapsed",
|
|
1728
|
+
className
|
|
1729
|
+
}) => {
|
|
1730
|
+
const { locale, messages } = useStreetImageryI18n();
|
|
1731
|
+
const text = messages.dateFilter;
|
|
1732
|
+
const [startNow] = useState(() => Date.now());
|
|
1733
|
+
const now = nowProp ?? startNow;
|
|
1734
|
+
const scale = { now, maxYears };
|
|
1735
|
+
const [dragged, setDragged] = useState(null);
|
|
1736
|
+
const fromInput = useRef(null);
|
|
1737
|
+
const toInput = useRef(null);
|
|
1738
|
+
const positions = dragged ?? dateRangeToSliderPositions(value, scale);
|
|
1739
|
+
const shownRange = dragged ? sliderPositionsToDateRange(dragged, scale) : value;
|
|
1740
|
+
const readPositions = (moved) => {
|
|
1741
|
+
const from = Number(fromInput.current?.value ?? positions.from);
|
|
1742
|
+
const to = Number(toInput.current?.value ?? positions.to);
|
|
1743
|
+
return moved === "from" ? { from: Math.min(from, to), to } : { from, to: Math.max(from, to) };
|
|
1744
|
+
};
|
|
1745
|
+
const commitOnRelease = (moved) => (input) => {
|
|
1746
|
+
if (!input) {
|
|
1747
|
+
return void 0;
|
|
1748
|
+
}
|
|
1749
|
+
const commit = () => {
|
|
1750
|
+
setDragged(null);
|
|
1751
|
+
const next = sliderPositionsToDateRange(readPositions(moved), scale);
|
|
1752
|
+
const other = moved === "from" ? "to" : "from";
|
|
1753
|
+
onChange({ ...next, [other]: value[other] });
|
|
1754
|
+
};
|
|
1755
|
+
input.addEventListener("change", commit);
|
|
1756
|
+
return () => {
|
|
1757
|
+
input.removeEventListener("change", commit);
|
|
1758
|
+
};
|
|
1759
|
+
};
|
|
1760
|
+
const trackDrag = (moved, event) => {
|
|
1761
|
+
if (event.type === "input") {
|
|
1762
|
+
setDragged(readPositions(moved));
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
const bins = capturedAt ? dateSliderBins(capturedAt, scale, BINS) : [];
|
|
1766
|
+
const maxCount = Math.max(1, ...bins);
|
|
1767
|
+
const years = [
|
|
1768
|
+
...new Set(recommendedMaxAgeYears ? [...yearLines, recommendedMaxAgeYears] : yearLines)
|
|
1769
|
+
].map((count) => ({ count, position: dateSliderPosition(yearsAgoMs(count, now), scale) })).filter(({ position }) => position > 0 && position < 1);
|
|
1770
|
+
const shownMarkers = markers.map((marker) => {
|
|
1771
|
+
const time = typeof marker.date === "number" ? marker.date : parseIsoDateStartMs(marker.date);
|
|
1772
|
+
return { ...marker, position: dateSliderPosition(time, scale) };
|
|
1773
|
+
}).filter(({ position }) => position > 0 && position < 1);
|
|
1774
|
+
const day = (isoDate) => formatDate(parseIsoDateStartMs(isoDate), locale);
|
|
1775
|
+
const rangeLabel = shownRange.from && shownRange.to ? text.between(day(shownRange.from), day(shownRange.to)) : shownRange.from ? text.since(day(shownRange.from)) : shownRange.to ? text.until(day(shownRange.to)) : text.allDates;
|
|
1776
|
+
const setYears = (count) => {
|
|
1777
|
+
onChange({ from: new Date(yearsAgoMs(count, now)).toISOString().slice(0, 10) });
|
|
1778
|
+
};
|
|
1779
|
+
const setDay = (key, input) => {
|
|
1780
|
+
if (input === "") {
|
|
1781
|
+
onChange({ ...value, [key]: void 0 });
|
|
1782
|
+
} else if (isIsoDate(input)) {
|
|
1783
|
+
onChange({ ...value, [key]: input });
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
const isAllDates = !shownRange.from && !shownRange.to;
|
|
1787
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
1788
|
+
/* @__PURE__ */ jsx("div", { className: "mx-2", children: /* @__PURE__ */ jsxs(
|
|
1789
|
+
"div",
|
|
1790
|
+
{
|
|
1791
|
+
className: "relative",
|
|
1792
|
+
style: { height: CHART_HEIGHT_PX, marginTop: shownMarkers.length > 0 ? 14 : 0 },
|
|
1793
|
+
children: [
|
|
1794
|
+
/* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "absolute inset-0 flex items-end", children: bins.map((count, index) => {
|
|
1795
|
+
const start = index / BINS;
|
|
1796
|
+
const inRange = start + 1 / BINS > positions.from && start < positions.to;
|
|
1797
|
+
return /* @__PURE__ */ jsx(
|
|
1798
|
+
"div",
|
|
1799
|
+
{
|
|
1800
|
+
className: `flex-1 rounded-t-[1px] ${inRange ? "bg-slate-500" : "bg-slate-300"}`,
|
|
1801
|
+
style: {
|
|
1802
|
+
height: count === 0 ? 0 : Math.max(2, Math.sqrt(count / maxCount) * CHART_HEIGHT_PX)
|
|
1803
|
+
},
|
|
1804
|
+
title: count > 0 ? text.photoCount(count) : void 0
|
|
1805
|
+
},
|
|
1806
|
+
index
|
|
1807
|
+
);
|
|
1808
|
+
}) }),
|
|
1809
|
+
years.map(({ count, position }) => /* @__PURE__ */ jsx(
|
|
1810
|
+
"div",
|
|
1811
|
+
{
|
|
1812
|
+
"aria-hidden": true,
|
|
1813
|
+
className: "pointer-events-none absolute inset-y-0 w-px -translate-x-1/2",
|
|
1814
|
+
style: {
|
|
1815
|
+
left: percent(position),
|
|
1816
|
+
backgroundColor: count === recommendedMaxAgeYears ? RED : "rgb(148 163 184 / 0.7)"
|
|
1817
|
+
}
|
|
1818
|
+
},
|
|
1819
|
+
count
|
|
1820
|
+
)),
|
|
1821
|
+
shownMarkers.map((marker) => /* @__PURE__ */ jsx(
|
|
1822
|
+
"div",
|
|
1823
|
+
{
|
|
1824
|
+
className: "pointer-events-none absolute inset-y-0 w-0 border-l-2 border-dashed",
|
|
1825
|
+
style: { left: percent(marker.position), borderColor: marker.color ?? RED },
|
|
1826
|
+
children: /* @__PURE__ */ jsx(
|
|
1827
|
+
"span",
|
|
1828
|
+
{
|
|
1829
|
+
className: "absolute -top-3.5 -translate-x-1/2 text-[10px] leading-none whitespace-nowrap",
|
|
1830
|
+
style: { color: marker.color ?? RED },
|
|
1831
|
+
children: marker.label
|
|
1832
|
+
}
|
|
1833
|
+
)
|
|
1834
|
+
},
|
|
1835
|
+
`${marker.date}-${marker.label}`
|
|
1836
|
+
))
|
|
1837
|
+
]
|
|
1838
|
+
}
|
|
1839
|
+
) }),
|
|
1840
|
+
/* @__PURE__ */ jsxs("div", { className: "relative h-4", children: [
|
|
1841
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-x-2 top-1/2 h-1 -translate-y-1/2 rounded-full bg-slate-200", children: /* @__PURE__ */ jsx(
|
|
1842
|
+
"div",
|
|
1843
|
+
{
|
|
1844
|
+
className: "absolute inset-y-0 rounded-full bg-slate-700",
|
|
1845
|
+
style: {
|
|
1846
|
+
left: percent(positions.from),
|
|
1847
|
+
right: percent(1 - positions.to)
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
) }),
|
|
1851
|
+
/* @__PURE__ */ jsx(
|
|
1852
|
+
"input",
|
|
1853
|
+
{
|
|
1854
|
+
"aria-label": text.fromHandle,
|
|
1855
|
+
"aria-valuetext": shownRange.from ? day(shownRange.from) : text.allDates,
|
|
1856
|
+
className: HANDLE_CLASS,
|
|
1857
|
+
max: 1,
|
|
1858
|
+
min: 0,
|
|
1859
|
+
onChange: (event) => {
|
|
1860
|
+
trackDrag("from", event.nativeEvent);
|
|
1861
|
+
},
|
|
1862
|
+
ref: (input) => {
|
|
1863
|
+
fromInput.current = input;
|
|
1864
|
+
return commitOnRelease("from")(input);
|
|
1865
|
+
},
|
|
1866
|
+
step: STEP,
|
|
1867
|
+
type: "range",
|
|
1868
|
+
value: positions.from
|
|
1869
|
+
}
|
|
1870
|
+
),
|
|
1871
|
+
/* @__PURE__ */ jsx(
|
|
1872
|
+
"input",
|
|
1873
|
+
{
|
|
1874
|
+
"aria-label": text.toHandle,
|
|
1875
|
+
"aria-valuetext": shownRange.to ? day(shownRange.to) : text.allDates,
|
|
1876
|
+
className: HANDLE_CLASS,
|
|
1877
|
+
max: 1,
|
|
1878
|
+
min: 0,
|
|
1879
|
+
onChange: (event) => {
|
|
1880
|
+
trackDrag("to", event.nativeEvent);
|
|
1881
|
+
},
|
|
1882
|
+
ref: (input) => {
|
|
1883
|
+
toInput.current = input;
|
|
1884
|
+
return commitOnRelease("to")(input);
|
|
1885
|
+
},
|
|
1886
|
+
step: STEP,
|
|
1887
|
+
type: "range",
|
|
1888
|
+
value: positions.to
|
|
1889
|
+
}
|
|
1890
|
+
)
|
|
1891
|
+
] }),
|
|
1892
|
+
/* @__PURE__ */ jsx("div", { className: "relative mx-2 h-4", children: years.map(({ count, position }) => /* @__PURE__ */ jsx(
|
|
1893
|
+
"button",
|
|
1894
|
+
{
|
|
1895
|
+
className: `absolute top-0 -translate-x-1/2 rounded px-0.5 text-[10px] leading-4 whitespace-nowrap hover:underline ${count === recommendedMaxAgeYears ? "font-semibold text-red-600" : "text-slate-500 hover:text-slate-900"}`,
|
|
1896
|
+
onClick: () => {
|
|
1897
|
+
setYears(count);
|
|
1898
|
+
},
|
|
1899
|
+
style: { left: percent(position) },
|
|
1900
|
+
title: count === recommendedMaxAgeYears ? text.recommendedMaxAge(count) : text.lastYears(count),
|
|
1901
|
+
type: "button",
|
|
1902
|
+
children: text.yearsShort(count)
|
|
1903
|
+
},
|
|
1904
|
+
count
|
|
1905
|
+
)) }),
|
|
1906
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-baseline justify-between gap-2 text-xs", children: [
|
|
1907
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-slate-700", children: rangeLabel }),
|
|
1908
|
+
isAllDates ? null : /* @__PURE__ */ jsx(
|
|
1909
|
+
"button",
|
|
1910
|
+
{
|
|
1911
|
+
className: "shrink-0 font-medium text-slate-600 underline decoration-slate-300 underline-offset-2 hover:text-slate-900",
|
|
1912
|
+
onClick: () => {
|
|
1913
|
+
onChange({});
|
|
1914
|
+
},
|
|
1915
|
+
type: "button",
|
|
1916
|
+
children: text.allDates
|
|
1917
|
+
}
|
|
1918
|
+
)
|
|
1919
|
+
] }),
|
|
1920
|
+
dateInputs === "none" ? null : /* @__PURE__ */ jsxs("details", { className: "mt-1 text-xs text-slate-600", open: dateInputs === "open" || void 0, children: [
|
|
1921
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer select-none hover:text-slate-900", children: text.exactDates }),
|
|
1922
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1.5 grid grid-cols-2 gap-2", children: ["from", "to"].map((key) => /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
1923
|
+
text[key],
|
|
1924
|
+
/* @__PURE__ */ jsx(
|
|
1925
|
+
"input",
|
|
1926
|
+
{
|
|
1927
|
+
className: "rounded-md border border-slate-200 px-2 py-1.5 text-sm text-slate-800",
|
|
1928
|
+
onChange: (event) => {
|
|
1929
|
+
setDay(key, event.target.value);
|
|
1930
|
+
},
|
|
1931
|
+
type: "date",
|
|
1932
|
+
value: value[key] ?? ""
|
|
1933
|
+
}
|
|
1934
|
+
)
|
|
1935
|
+
] }, key)) })
|
|
1936
|
+
] })
|
|
1937
|
+
] });
|
|
1938
|
+
};
|
|
1633
1939
|
|
|
1634
|
-
export { BASE_COLOR, FloatingPhotoViewer, FloatingViewerInfoButton, LocationPickOnMap, MAPILLARY_FEATURE_BAR_LABELS, MapillaryFeatureBar, PhotoCount, PhotoDate, SELECTION_COLOR, StreetLevelImagerySelectionOverlay, StreetLevelImagerySourcesAndLayers, StreetLevelImageryViewCone, StreetLevelImageryViewer, VIEWPOINT_DEFAULT_COLOR, VIEWPOINT_DIRECTION_LAYER_ID, VIEW_SHAPE_FILL_OPACITY, ViewpointLayer, getViewpointSession, queryStreetImageryFeatures, resolveSelectedSequence, streetImageryInteractiveLayerIds, useActiveDirectionKey, useAllProviderMapFeatures, useAllProviderPhotos, useArmedLocationOpenerId, useCanGoBack, useCanGoForward, useCurrentHistoryEntry, useLocationPickActions, useMapViewportBbox, useMapillaryImageDetections, useMapillaryMapFeatureImages, useMapillaryMapFeaturesNear, usePhotoFullUrl, usePhotoThumbnail, useProviderMapFeatures, useProviderPhotos, useProviderSequences, useViewSuggestions, useViewpointLine, useViewpoints, viewDirectionKeyFromFeatures, viewSuggestionLabel };
|
|
1940
|
+
export { BASE_COLOR, FloatingPhotoViewer, FloatingViewerInfoButton, LocationPickOnMap, MAPILLARY_FEATURE_BAR_LABELS, MapillaryFeatureBar, PhotoCount, PhotoDate, PhotoDateRangeFilter, SELECTION_COLOR, SelectedMapFeatureLayer, StreetLevelImagerySelectionOverlay, StreetLevelImagerySourcesAndLayers, StreetLevelImageryViewCone, StreetLevelImageryViewer, VIEWPOINT_DEFAULT_COLOR, VIEWPOINT_DIRECTION_LAYER_ID, VIEW_SHAPE_FILL_OPACITY, ViewpointLayer, getViewpointSession, queryStreetImageryFeatures, resolveSelectedSequence, streetImageryInteractiveLayerIds, useActiveDirectionKey, useAllProviderMapFeatures, useAllProviderPhotos, useArmedLocationOpenerId, useCanGoBack, useCanGoForward, useCurrentHistoryEntry, useLocationPickActions, useMapViewportBbox, useMapillaryImageDetections, useMapillaryMapFeatureImages, useMapillaryMapFeaturesNear, usePhotoFullUrl, usePhotoThumbnail, useProviderMapFeatures, useProviderPhotos, useProviderSequences, useSelectedMapillaryFeature, useViewSuggestions, useViewpointLine, useViewpoints, viewDirectionKeyFromFeatures, viewSuggestionLabel };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Bbox, type MapFeatureImages, type ProviderId, type TargetImage } from '@osm-editor-kit/street-imagery';
|
|
2
|
+
export type SelectedMapFeatureLayerProps = {
|
|
3
|
+
/** From `useSelectedMapillaryFeature`. Nothing is drawn without it. */
|
|
4
|
+
data: MapFeatureImages | null;
|
|
5
|
+
/** The shown photo of the feature: a dotted line runs from its camera to the feature. */
|
|
6
|
+
shownImage?: TargetImage | null;
|
|
7
|
+
/** The providers whose map features are drawn, to ring the dot that is on the map. */
|
|
8
|
+
providers: ProviderId[];
|
|
9
|
+
bbox: Bbox | null;
|
|
10
|
+
zoom: number;
|
|
11
|
+
/** Color of the dot and disk. Default: the map features' purple. */
|
|
12
|
+
color?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The selected sign or object on the map: its dot on a light disk, and a dotted line to it from
|
|
16
|
+
* the camera position of the shown photo (Mapillary's computed position, which the feature was
|
|
17
|
+
* located from). Draw it after `StreetLevelImagerySourcesAndLayers`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const SelectedMapFeatureLayer: ({ data, shownImage, providers, bbox, zoom, color, }: SelectedMapFeatureLayerProps) => import("react").JSX.Element;
|
|
@@ -34,3 +34,21 @@ export declare const useMapillaryImageDetections: (imageId: string | null | unde
|
|
|
34
34
|
filter?: (value: string) => boolean;
|
|
35
35
|
filterKey?: string;
|
|
36
36
|
}) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@osm-editor-kit/street-imagery").MapillaryDetection[]>, Error>;
|
|
37
|
+
/**
|
|
38
|
+
* A selected Mapillary map feature (sign, object) for a host's feature + photo selection: all its
|
|
39
|
+
* photos by day, the photo that is shown (`shownPhotoId`), and the one to open first — the best of
|
|
40
|
+
* the newest day, from within `minCapturedAt` (ms) when set. Feed `data` and `shownImage` to
|
|
41
|
+
* `MapillaryFeatureBar` and `SelectedMapFeatureLayer`, and `shownImage.outline` to `lookAt`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const useSelectedMapillaryFeature: ({ featureId, shownPhotoId, minCapturedAt, shape, }: {
|
|
44
|
+
featureId: string | null | undefined;
|
|
45
|
+
shownPhotoId?: string | null;
|
|
46
|
+
minCapturedAt?: number | null;
|
|
47
|
+
shape?: TargetShape;
|
|
48
|
+
}) => {
|
|
49
|
+
data: NonNullable<NoInfer<import("@osm-editor-kit/street-imagery").MapFeatureImages | null>> | null;
|
|
50
|
+
isLoading: boolean;
|
|
51
|
+
isError: boolean;
|
|
52
|
+
shownImage: import("@osm-editor-kit/street-imagery").TargetImage | null;
|
|
53
|
+
firstImage: import("@osm-editor-kit/street-imagery").TargetImage | null;
|
|
54
|
+
};
|
package/dist/photo-details.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useStreetImageryI18n } from './chunk-
|
|
1
|
+
import { useStreetImageryI18n } from './chunk-ZAIXGYAH.js';
|
|
2
2
|
import { providerById, formatLocalDateTime, formatDate, formatRelativeDate } from '@osm-editor-kit/street-imagery';
|
|
3
3
|
import { useRef, useEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
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.
|
|
3
|
+
"version": "0.1.0-alpha.8",
|
|
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",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"zustand": "^5.0.7"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@osm-editor-kit/street-imagery": "^0.1.0-alpha.
|
|
59
|
+
"@osm-editor-kit/street-imagery": "^0.1.0-alpha.7",
|
|
60
60
|
"@panoramax/web-viewer": "^5",
|
|
61
61
|
"@tanstack/react-query": "^5",
|
|
62
62
|
"mapillary-js": "^4",
|