@mailwoman/react 7.4.0 → 7.6.0
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/common/PresetChips.tsx +4 -1
- package/index.ts +31 -0
- package/map/BackendControl.tsx +39 -0
- package/map/CompareToggle.tsx +81 -0
- package/map/DemoControls.tsx +173 -0
- package/map/DemoMap.tsx +98 -0
- package/map/GeocoderDemo.tsx +178 -0
- package/map/OverlayLayers.tsx +46 -0
- package/map/PlaceAutocomplete.tsx +69 -0
- package/map/PlaceMarker.tsx +40 -0
- package/map/ResolvedPlaceLayers.tsx +63 -0
- package/map/ResultCamera.tsx +60 -0
- package/map/ResultOverlay.tsx +61 -0
- package/map/ResultPanel.tsx +84 -0
- package/map/VersionPicker.tsx +62 -0
- package/map/geometry.ts +135 -0
- package/map/index.ts +73 -0
- package/map/place-render.ts +157 -0
- package/map/types.ts +207 -0
- package/map/useCompareState.ts +45 -0
- package/map/useDemoGeocode.ts +67 -0
- package/map/useMapPlaceRender.ts +21 -0
- package/map/usePlaceAutocomplete.ts +195 -0
- package/out/common/PresetChips.d.ts +3 -1
- package/out/common/PresetChips.d.ts.map +1 -1
- package/out/common/PresetChips.js +2 -2
- package/out/common/PresetChips.js.map +1 -1
- package/out/index.d.ts +4 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js +2 -0
- package/out/index.js.map +1 -1
- package/out/map/BackendControl.d.ts +24 -0
- package/out/map/BackendControl.d.ts.map +1 -0
- package/out/map/BackendControl.js +6 -0
- package/out/map/BackendControl.js.map +1 -0
- package/out/map/CompareToggle.d.ts +35 -0
- package/out/map/CompareToggle.d.ts.map +1 -0
- package/out/map/CompareToggle.js +9 -0
- package/out/map/CompareToggle.js.map +1 -0
- package/out/map/DemoControls.d.ts +44 -0
- package/out/map/DemoControls.d.ts.map +1 -0
- package/out/map/DemoControls.js +28 -0
- package/out/map/DemoControls.js.map +1 -0
- package/out/map/DemoMap.d.ts +59 -0
- package/out/map/DemoMap.d.ts.map +1 -0
- package/out/map/DemoMap.js +12 -0
- package/out/map/DemoMap.js.map +1 -0
- package/out/map/GeocoderDemo.d.ts +47 -0
- package/out/map/GeocoderDemo.d.ts.map +1 -0
- package/out/map/GeocoderDemo.js +73 -0
- package/out/map/GeocoderDemo.js.map +1 -0
- package/out/map/OverlayLayers.d.ts +23 -0
- package/out/map/OverlayLayers.d.ts.map +1 -0
- package/out/map/OverlayLayers.js +27 -0
- package/out/map/OverlayLayers.js.map +1 -0
- package/out/map/PlaceAutocomplete.d.ts +34 -0
- package/out/map/PlaceAutocomplete.d.ts.map +1 -0
- package/out/map/PlaceAutocomplete.js +9 -0
- package/out/map/PlaceAutocomplete.js.map +1 -0
- package/out/map/PlaceMarker.d.ts +27 -0
- package/out/map/PlaceMarker.d.ts.map +1 -0
- package/out/map/PlaceMarker.js +9 -0
- package/out/map/PlaceMarker.js.map +1 -0
- package/out/map/ResolvedPlaceLayers.d.ts +34 -0
- package/out/map/ResolvedPlaceLayers.d.ts.map +1 -0
- package/out/map/ResolvedPlaceLayers.js +11 -0
- package/out/map/ResolvedPlaceLayers.js.map +1 -0
- package/out/map/ResultCamera.d.ts +34 -0
- package/out/map/ResultCamera.d.ts.map +1 -0
- package/out/map/ResultCamera.js +43 -0
- package/out/map/ResultCamera.js.map +1 -0
- package/out/map/ResultOverlay.d.ts +34 -0
- package/out/map/ResultOverlay.d.ts.map +1 -0
- package/out/map/ResultOverlay.js +18 -0
- package/out/map/ResultOverlay.js.map +1 -0
- package/out/map/ResultPanel.d.ts +34 -0
- package/out/map/ResultPanel.d.ts.map +1 -0
- package/out/map/ResultPanel.js +12 -0
- package/out/map/ResultPanel.js.map +1 -0
- package/out/map/VersionPicker.d.ts +30 -0
- package/out/map/VersionPicker.d.ts.map +1 -0
- package/out/map/VersionPicker.js +8 -0
- package/out/map/VersionPicker.js.map +1 -0
- package/out/map/geometry.d.ts +55 -0
- package/out/map/geometry.d.ts.map +1 -0
- package/out/map/geometry.js +99 -0
- package/out/map/geometry.js.map +1 -0
- package/out/map/index.d.ts +52 -0
- package/out/map/index.d.ts.map +1 -0
- package/out/map/index.js +36 -0
- package/out/map/index.js.map +1 -0
- package/out/map/place-render.d.ts +85 -0
- package/out/map/place-render.d.ts.map +1 -0
- package/out/map/place-render.js +98 -0
- package/out/map/place-render.js.map +1 -0
- package/out/map/types.d.ts +193 -0
- package/out/map/types.d.ts.map +1 -0
- package/out/map/types.js +18 -0
- package/out/map/types.js.map +1 -0
- package/out/map/useCompareState.d.ts +25 -0
- package/out/map/useCompareState.d.ts.map +1 -0
- package/out/map/useCompareState.js +27 -0
- package/out/map/useCompareState.js.map +1 -0
- package/out/map/useDemoGeocode.d.ts +36 -0
- package/out/map/useDemoGeocode.d.ts.map +1 -0
- package/out/map/useDemoGeocode.js +44 -0
- package/out/map/useDemoGeocode.js.map +1 -0
- package/out/map/useMapPlaceRender.d.ts +15 -0
- package/out/map/useMapPlaceRender.d.ts.map +1 -0
- package/out/map/useMapPlaceRender.js +18 -0
- package/out/map/useMapPlaceRender.js.map +1 -0
- package/out/map/usePlaceAutocomplete.d.ts +61 -0
- package/out/map/usePlaceAutocomplete.d.ts.map +1 -0
- package/out/map/usePlaceAutocomplete.js +122 -0
- package/out/map/usePlaceAutocomplete.js.map +1 -0
- package/out/pipeline/QueryForm.d.ts +6 -2
- package/out/pipeline/QueryForm.d.ts.map +1 -1
- package/out/pipeline/QueryForm.js +2 -2
- package/out/pipeline/QueryForm.js.map +1 -1
- package/out/poi/LiveResultsBlock.d.ts +3 -3
- package/out/poi/LiveResultsBlock.d.ts.map +1 -1
- package/out/poi/LiveResultsBlock.js +2 -2
- package/out/poi/LiveResultsBlock.js.map +1 -1
- package/out/poi/POIExplorer.d.ts +7 -1
- package/out/poi/POIExplorer.d.ts.map +1 -1
- package/out/poi/POIExplorer.js +17 -5
- package/out/poi/POIExplorer.js.map +1 -1
- package/out/poi/SubjectPanel.d.ts +3 -2
- package/out/poi/SubjectPanel.d.ts.map +1 -1
- package/out/poi/SubjectPanel.js +8 -1
- package/out/poi/SubjectPanel.js.map +1 -1
- package/out/poi/runtime.d.ts +13 -4
- package/out/poi/runtime.d.ts.map +1 -1
- package/out/poi/runtime.js +36 -12
- package/out/poi/runtime.js.map +1 -1
- package/out/poi/types.d.ts +31 -4
- package/out/poi/types.d.ts.map +1 -1
- package/out/poi/usePOISearch.d.ts +8 -1
- package/out/poi/usePOISearch.d.ts.map +1 -1
- package/out/poi/usePOISearch.js +47 -12
- package/out/poi/usePOISearch.js.map +1 -1
- package/out/runtime/useDemoRuntime.d.ts +113 -0
- package/out/runtime/useDemoRuntime.d.ts.map +1 -0
- package/out/runtime/useDemoRuntime.js +135 -0
- package/out/runtime/useDemoRuntime.js.map +1 -0
- package/package.json +31 -10
- package/pipeline/QueryForm.tsx +17 -2
- package/poi/LiveResultsBlock.tsx +4 -4
- package/poi/POIExplorer.tsx +39 -8
- package/poi/SubjectPanel.tsx +47 -12
- package/poi/runtime.ts +35 -10
- package/poi/types.ts +34 -4
- package/poi/usePOISearch.ts +62 -11
- package/runtime/useDemoRuntime.ts +245 -0
- package/styles.css +166 -0
- package/vitest.node.config.ts +20 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `computeMapPlaceRenderSpec` — the PURE core that replaces the docs demo's ~100-line imperative
|
|
7
|
+
* marker/bbox/camera redraw effect (`_app.tsx:601-705`). It takes an ALREADY-RESOLVED place (the async
|
|
8
|
+
* polygon-DB fetch is a host/runtime concern — a later phase — so a crisp polygon arrives pre-fetched
|
|
9
|
+
* as `place.geometry`) and returns a declarative render spec: the marker position(s), the outline
|
|
10
|
+
* geometry to draw, and the camera target. No map instance, no DOM, no `react-map-gl` — so it is
|
|
11
|
+
* node-testable (see `place-render.node.test.ts`). The declarative components in this folder render
|
|
12
|
+
* the spec; a controlled-viewport consumer applies the camera.
|
|
13
|
+
*
|
|
14
|
+
* The decision cascade is a faithful, side-effect-free transcription of the imperative effect:
|
|
15
|
+
* 1. street tier (situs / interp) → exact-radius circle, fly to a tight zoom
|
|
16
|
+
* 2. crisp admin polygon (pre-fetched) → draw the polygon, fit its bounds
|
|
17
|
+
* 3. anchor-centroid postcode (no bbox) → ~3 km "around here" circle, fly to zoom 11
|
|
18
|
+
* 4. bbox with real extent → bbox-sized approximate circle, fit the bbox
|
|
19
|
+
* 5. bare point → no outline, fly to zoom 12
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ResolvedPlaceView } from "../pipeline/types.ts"
|
|
23
|
+
import { approxCircleGeometry, bboxToBounds, geomBounds, radiusCircleGeometry } from "./geometry.ts"
|
|
24
|
+
import type { BoundsTuple, PlaceBBox, PlaceGeometry } from "./geometry.ts"
|
|
25
|
+
|
|
26
|
+
/** `[longitude, latitude]`. */
|
|
27
|
+
export type LngLat = [number, number]
|
|
28
|
+
|
|
29
|
+
/** The street-level resolution tier (#377): `address_point` = exact building; `interpolated` = TIGER estimate. */
|
|
30
|
+
export type PlaceTier = "address_point" | "interpolated"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The resolved-place shape the map render consumes — the pipeline {@link ResolvedPlaceView} plus the map-only extras the
|
|
34
|
+
* demo's `ResolvedHit` carries (bbox, street tier + uncertainty), and an optional PRE-FETCHED crisp polygon. Extending
|
|
35
|
+
* `ResolvedPlaceView` keeps the map render aligned with the shared parse result; the extras are additive.
|
|
36
|
+
*/
|
|
37
|
+
export interface ResolvedMapPlace extends ResolvedPlaceView {
|
|
38
|
+
/** The place's bounding box, when the gazetteer carries one (admin places). Absent for anchor-centroid postcodes. */
|
|
39
|
+
bbox?: PlaceBBox
|
|
40
|
+
/** Street-level tier, when this hit came from the situs/interp tier rather than the WOF admin cascade. */
|
|
41
|
+
tier?: PlaceTier
|
|
42
|
+
/** Honest uncertainty radius in meters for a street-level tier (10 m situs floor; calibrated interp). */
|
|
43
|
+
uncertaintyM?: number
|
|
44
|
+
/**
|
|
45
|
+
* The crisp admin polygon, when the host has ALREADY fetched it from the sibling polygon DB. Its presence drives the
|
|
46
|
+
* polygon path; the async fetch itself stays out of this pure function (a runtime concern in a later phase).
|
|
47
|
+
*/
|
|
48
|
+
geometry?: PlaceGeometry
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The camera target the render computes. `center` (fly to a point at a zoom) HAS a declarative equivalent — a consumer
|
|
53
|
+
* can feed it to a controlled `viewState` (see {@link cameraToViewState}). `bounds` (fit a box with pixel padding) does
|
|
54
|
+
* NOT — `fitBounds` needs the map's pixel dimensions, so it is applied imperatively by `<ResultCamera>`.
|
|
55
|
+
*/
|
|
56
|
+
export type MapCameraTarget =
|
|
57
|
+
| { kind: "center"; center: LngLat; zoom: number }
|
|
58
|
+
| { kind: "bounds"; bounds: BoundsTuple; padding: number }
|
|
59
|
+
|
|
60
|
+
/** The declarative render spec for one resolved place — the pure output of {@link computeMapPlaceRenderSpec}. */
|
|
61
|
+
export interface MapPlaceRenderSpec {
|
|
62
|
+
/** Marker position(s) as `[lon, lat]`. Single-element today; an array leaves room for multi-marker later. */
|
|
63
|
+
markers: LngLat[]
|
|
64
|
+
/** The outline geometry (polygon / circle) to draw, or `null` when the place renders as a bare point. */
|
|
65
|
+
outline: PlaceGeometry | null
|
|
66
|
+
/** The camera target — animate or fit to this. */
|
|
67
|
+
camera: MapCameraTarget
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Zoom levels the imperative effect flew to, kept named so the cascade reads as intent, not magic numbers. */
|
|
71
|
+
const ZOOM = {
|
|
72
|
+
addressPoint: 17,
|
|
73
|
+
interpolated: 15,
|
|
74
|
+
postcode: 11,
|
|
75
|
+
point: 12,
|
|
76
|
+
} as const
|
|
77
|
+
|
|
78
|
+
/** Padding (px) `fitBounds` insets a fitted box by, matching the ported effect. */
|
|
79
|
+
const FIT_PADDING = 40
|
|
80
|
+
|
|
81
|
+
/** The minimum lat/lon span (degrees) a bbox must exceed to be treated as a real extent rather than a point. */
|
|
82
|
+
const MIN_EXTENT_DEG = 0.001
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Map a resolved place to its declarative render spec. Pure — same input, same output, no side effects. The `place`
|
|
86
|
+
* arrives fully resolved (crisp polygon pre-fetched into `place.geometry` when available), so this is the honest
|
|
87
|
+
* inverse of the old redraw effect with the imperative map mutation and the async DB load removed.
|
|
88
|
+
*/
|
|
89
|
+
export function computeMapPlaceRenderSpec(place: ResolvedMapPlace): MapPlaceRenderSpec {
|
|
90
|
+
const markers: LngLat[] = [[place.lon, place.lat]]
|
|
91
|
+
|
|
92
|
+
// 1. Street tier (#377): the honest uncertainty circle (exact meter radius) + a tight zoom. Takes precedence over
|
|
93
|
+
// the admin polygon/bbox paths — a precise point gets no admin boundary.
|
|
94
|
+
if (place.tier && place.uncertaintyM != null) {
|
|
95
|
+
return {
|
|
96
|
+
markers,
|
|
97
|
+
outline: radiusCircleGeometry(place.lat, place.lon, place.uncertaintyM),
|
|
98
|
+
camera: {
|
|
99
|
+
kind: "center",
|
|
100
|
+
center: [place.lon, place.lat],
|
|
101
|
+
zoom: place.tier === "address_point" ? ZOOM.addressPoint : ZOOM.interpolated,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2. Crisp admin polygon (host pre-fetched it from the polygon DB) — draw the real boundary and fit it.
|
|
107
|
+
if (place.geometry) {
|
|
108
|
+
return {
|
|
109
|
+
markers,
|
|
110
|
+
outline: place.geometry,
|
|
111
|
+
camera: { kind: "bounds", bounds: bboxToBounds(geomBounds(place.geometry)), padding: FIT_PADDING },
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 3. Anchor-centroid postcode: no bbox, no polygon — a default ~3 km circle says "approximately here" without
|
|
116
|
+
// inventing a boundary.
|
|
117
|
+
if (!place.bbox && place.placetype === "postcode") {
|
|
118
|
+
return {
|
|
119
|
+
markers,
|
|
120
|
+
outline: approxCircleGeometry(place.lat, place.lon),
|
|
121
|
+
camera: { kind: "center", center: [place.lon, place.lat], zoom: ZOOM.postcode },
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 4. A bbox with real extent — draw an approximate CIRCLE sized from the bbox (a rectangle would read as a wrong,
|
|
126
|
+
// real boundary) and fit the bbox.
|
|
127
|
+
const bbox = place.bbox
|
|
128
|
+
|
|
129
|
+
if (bbox && Math.max(bbox.maxLat - bbox.minLat, bbox.maxLon - bbox.minLon) > MIN_EXTENT_DEG) {
|
|
130
|
+
return {
|
|
131
|
+
markers,
|
|
132
|
+
outline: approxCircleGeometry(place.lat, place.lon, bbox),
|
|
133
|
+
camera: { kind: "bounds", bounds: bboxToBounds(bbox), padding: FIT_PADDING },
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 5. A bare point (no tier, no polygon, no meaningful bbox) — just a marker and a mid zoom.
|
|
138
|
+
return {
|
|
139
|
+
markers,
|
|
140
|
+
outline: null,
|
|
141
|
+
camera: { kind: "center", center: [place.lon, place.lat], zoom: ZOOM.point },
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The DECLARATIVE camera path: reshape a `center` target into a `viewState` patch a controlled `<DemoMap viewState>`
|
|
147
|
+
* can apply directly (a hard jump, no animation). Returns `null` for a `bounds` target — fitting a box to the viewport
|
|
148
|
+
* needs the map's pixel dimensions, which only the live map has, so that case is applied imperatively by
|
|
149
|
+
* `<ResultCamera>`. Pure + node-testable.
|
|
150
|
+
*/
|
|
151
|
+
export function cameraToViewState(
|
|
152
|
+
camera: MapCameraTarget
|
|
153
|
+
): { longitude: number; latitude: number; zoom: number } | null {
|
|
154
|
+
if (camera.kind !== "center") return null
|
|
155
|
+
|
|
156
|
+
return { longitude: camera.center[0], latitude: camera.center[1], zoom: camera.zoom }
|
|
157
|
+
}
|
package/map/types.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Types for the geocoder-demo map surface. Mirrors the pipeline seam: the package owns the UI state
|
|
7
|
+
* machine + the declarative map, while the host injects a {@link DemoRuntime} that owns ONNX / httpvfs
|
|
8
|
+
* / R2 and the composed map style. {@link DemoRuntime} EXTENDS {@link PipelineRuntime} so the shared
|
|
9
|
+
* `runParse` / `parseStageLabels` / `loading` contract is reused, and adds the map-specific surface
|
|
10
|
+
* (style, overlays, initial center, viewport bias, backend/version selection). Phase 4 adds the
|
|
11
|
+
* `resolveMapPlace` enricher, the {@link DemoPanels} injection bag, and the {@link DemoCompareContext}.
|
|
12
|
+
*
|
|
13
|
+
* The map-spec types are imported type-only from `react-map-gl/maplibre`; nothing here loads maplibre at
|
|
14
|
+
* runtime, so this module stays node-safe (its concrete-value CONSUMERS — `DemoMap`, `GeocoderDemo` —
|
|
15
|
+
* are the ones gated behind the `@mailwoman/react/map` subpath).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ReactNode } from "react"
|
|
19
|
+
import type { LayerSpecification, SourceSpecification } from "react-map-gl/maplibre"
|
|
20
|
+
|
|
21
|
+
import type { ParseResult, PipelineRuntime, ResolvedPlaceView } from "../pipeline/types.ts"
|
|
22
|
+
import type { DemoMapStyle } from "./DemoMap.tsx"
|
|
23
|
+
import type { ResolvedMapPlace } from "./place-render.ts"
|
|
24
|
+
|
|
25
|
+
/** `[longitude, latitude]`. */
|
|
26
|
+
export type LngLatTuple = [number, number]
|
|
27
|
+
|
|
28
|
+
/** A viewport bias handed to `runParse` — the map's current center (and optionally zoom) as a soft prior. */
|
|
29
|
+
export interface MapBias {
|
|
30
|
+
/** Map center as `[lon, lat]`, typically read from `map.getCenter()`. */
|
|
31
|
+
center: LngLatTuple
|
|
32
|
+
/** Current zoom, if the host wants distance-aware biasing. */
|
|
33
|
+
zoom?: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A host-supplied overlay: one map `<Source>` plus one or more `<Layer>`s laid over the basemap (coverage "fog of war",
|
|
38
|
+
* race-dots, …). The host composes the specs; the package renders them declaratively in a later phase.
|
|
39
|
+
*/
|
|
40
|
+
export interface OverlaySpec {
|
|
41
|
+
/** Stable id — used as the `<Source>` id and the layer-id prefix. */
|
|
42
|
+
id: string
|
|
43
|
+
/** The map source spec (vector/geojson/raster). */
|
|
44
|
+
source: SourceSpecification
|
|
45
|
+
/** The layers drawn from that source. */
|
|
46
|
+
layers: LayerSpecification[]
|
|
47
|
+
/** Whether the overlay is visible initially. @default true */
|
|
48
|
+
visible?: boolean
|
|
49
|
+
/** Human label for a layer-toggle control. */
|
|
50
|
+
label?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One autocomplete suggestion produced by the host's FST prefix-walk. */
|
|
54
|
+
export interface Suggestion {
|
|
55
|
+
/** The text inserted when the suggestion is picked. */
|
|
56
|
+
value: string
|
|
57
|
+
/** Optional display label if it differs from `value`. */
|
|
58
|
+
label?: string
|
|
59
|
+
/** Optional place kind for badge/icon rendering. */
|
|
60
|
+
placetype?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A selectable model bundle (version tag + a display label the picker shows). */
|
|
64
|
+
export interface DemoVersionOption {
|
|
65
|
+
/** The version tag (e.g. a git tag or model-card version). */
|
|
66
|
+
version: string
|
|
67
|
+
/** Display label; falls back to `version`. */
|
|
68
|
+
label?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Which neural backend the demo is currently running on. */
|
|
72
|
+
export type DemoBackend = "webgpu" | "wasm"
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The injected demo runtime. Extends {@link PipelineRuntime} (shared `runParse` / `parseStageLabels` / `loading` /
|
|
76
|
+
* `ready`) with the map + version/backend surface the demo needs. The host composes `mapStyle` (via cartographer's
|
|
77
|
+
* `StyleSpecificationComposer` + the tile-worker TileJSON), supplies the overlay specs, the initial center (from
|
|
78
|
+
* geolocation), the FST autocomplete, and the calibrator — nothing in the package imports `@mailwoman/cartographer`,
|
|
79
|
+
* `@mailwoman/neural-web`, httpvfs, or Docusaurus.
|
|
80
|
+
*/
|
|
81
|
+
export interface DemoRuntime extends PipelineRuntime {
|
|
82
|
+
// ── Map ────────────────────────────────────────────────────────────────
|
|
83
|
+
/** The composed basemap style (URL or `StyleSpecification`). */
|
|
84
|
+
mapStyle: DemoMapStyle
|
|
85
|
+
/** Host-supplied overlays (coverage, race-dots, …). */
|
|
86
|
+
overlays?: OverlaySpec[]
|
|
87
|
+
/** Initial map center as `[lon, lat]` (the host's browser-geolocation result). */
|
|
88
|
+
initialCenter: LngLatTuple
|
|
89
|
+
/** Initial zoom for the first camera. */
|
|
90
|
+
initialZoom?: number
|
|
91
|
+
|
|
92
|
+
// ── Parse extras layered over PipelineRuntime.runParse ──────────────────
|
|
93
|
+
/**
|
|
94
|
+
* A bias-aware parse. The map demo feeds the current viewport center as a soft prior; when absent the host falls back
|
|
95
|
+
* to the base {@link PipelineRuntime.runParse}. Kept separate so the shared `runParse` contract is unchanged.
|
|
96
|
+
*/
|
|
97
|
+
runParseWithBias?: (
|
|
98
|
+
input: string,
|
|
99
|
+
bias: MapBias | null,
|
|
100
|
+
hooks: { onStage: (stage: number) => void }
|
|
101
|
+
) => ReturnType<PipelineRuntime["runParse"]>
|
|
102
|
+
/** FST prefix-walk autocomplete, wrapped by the host. */
|
|
103
|
+
autocomplete?: (query: string) => Promise<Suggestion[]>
|
|
104
|
+
/** Maps a raw model score to a calibrated one; `null` when no calibration table is loaded. */
|
|
105
|
+
calibrator?: (raw: number) => number | null
|
|
106
|
+
/**
|
|
107
|
+
* Enrich the selected candidate into the richer {@link ResolvedMapPlace} the declarative map render consumes (bbox,
|
|
108
|
+
* street tier + uncertainty, a pre-fetched crisp polygon) — the fields that live on the demo's `ResolvedHit` but not
|
|
109
|
+
* on the shared {@link ResolvedPlaceView}. The host owns this because those extras (and the async polygon fetch in the
|
|
110
|
+
* real demo) are host/gazetteer concerns; the package keeps {@link ParseResult} unpolluted. Absent → the candidate
|
|
111
|
+
* renders as a bare point (marker + a mid-zoom fly-to). Returning `null` also renders nothing.
|
|
112
|
+
*/
|
|
113
|
+
resolveMapPlace?: (candidate: ResolvedPlaceView, result: ParseResult) => ResolvedMapPlace | null
|
|
114
|
+
|
|
115
|
+
// ── Version + backend selection ─────────────────────────────────────────
|
|
116
|
+
/** The selectable model bundles the version picker offers. */
|
|
117
|
+
availableVersions?: DemoVersionOption[]
|
|
118
|
+
/** The currently-selected model version. */
|
|
119
|
+
selectedVersion?: string
|
|
120
|
+
/** Switch the active model bundle (re-loads weights/tokenizer/gazetteer). */
|
|
121
|
+
selectVersion?: (version: string) => void
|
|
122
|
+
/** The backend the neural runtime resolved to (e.g. `webgpu (28 MB int8)`); free-form for the label. */
|
|
123
|
+
activeBackend?: string
|
|
124
|
+
/** Whether the CPU/WASM backend is currently forced (the controlled value for the backend toggle). */
|
|
125
|
+
forceWASM?: boolean
|
|
126
|
+
/** Force the WASM backend (opt out of WebGPU), for the backend toggle. */
|
|
127
|
+
setForceWASM?: (forceWASM: boolean) => void
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The compare-mode state a {@link DemoPanels.compare} render-prop receives (the second parse itself stays host-side). */
|
|
131
|
+
export interface DemoCompareContext {
|
|
132
|
+
/** The current primary parse result, or `null` before the first submit. */
|
|
133
|
+
result: ParseResult | null
|
|
134
|
+
/** Whether the compare toggle is on. */
|
|
135
|
+
compareMode: boolean
|
|
136
|
+
/** The version selected to compare against, or `null` when none is chosen. */
|
|
137
|
+
compareVersion: string | null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The state a {@link DemoPanels.result} render-prop receives, so a host can render its OWN result block (the docs
|
|
142
|
+
* `<ResultPanel>` with its span-highlight / timing / hierarchy / precision detail) in place of the package's default
|
|
143
|
+
* {@link ResultPanel}. Everything the default panel needs is passed through; the candidate-selection state stays owned
|
|
144
|
+
* by the package (`useDemoGeocode`).
|
|
145
|
+
*/
|
|
146
|
+
export interface DemoResultContext {
|
|
147
|
+
/** The current parse+resolve result. */
|
|
148
|
+
result: ParseResult
|
|
149
|
+
/** The selected candidate (falls back to the first), enriched for the resolved-place detail. */
|
|
150
|
+
selectedCandidate: ResolvedPlaceView | null
|
|
151
|
+
/** The selected candidate index, for the picker's active state. */
|
|
152
|
+
selectedCandidateIndex: number
|
|
153
|
+
/** Fired when a candidate in the picker is chosen. */
|
|
154
|
+
onSelectCandidate: (index: number) => void
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Host-injected panels for {@link GeocoderDemo}, the map analogue of `PipelinePanels`. Each is an already-rendered
|
|
159
|
+
* `ReactNode` (or a thunk of the parse result / compare state) so the package needs neither the heavy docs visualizers
|
|
160
|
+
* (ModelVisualizer, VersionCompare, AboutDemo, PermalinkButton) nor their data types. Every field is optional — the
|
|
161
|
+
* fake-runtime Storybook stories pass none and still render the whole demo.
|
|
162
|
+
*/
|
|
163
|
+
export interface DemoPanels {
|
|
164
|
+
/** Rendered at the top of the control panel (e.g. the docs "About this demo"). */
|
|
165
|
+
header?: ReactNode
|
|
166
|
+
/** One-line release blurb for the selected version. */
|
|
167
|
+
releaseInfo?: ReactNode
|
|
168
|
+
/** Rendered at the bottom of the control panel (e.g. a guided tour). */
|
|
169
|
+
footer?: ReactNode
|
|
170
|
+
/**
|
|
171
|
+
* A device-location / proximity-bias control, rendered between the query form and the autocomplete list (the demo's
|
|
172
|
+
* "📍 Use my location" row). Host-owned so the geolocation permission + the bias it feeds into the host's
|
|
173
|
+
* {@link DemoRuntime.runParseWithBias} stay a host concern.
|
|
174
|
+
*/
|
|
175
|
+
bias?: ReactNode
|
|
176
|
+
/** Heavy visualizers (span highlight, tree, timing, BIO, …), rendered from the result. */
|
|
177
|
+
extras?: (result: ParseResult) => ReactNode
|
|
178
|
+
/**
|
|
179
|
+
* Rendered just above the result block (present or empty). The demo's opt-in display toggles live here — calibrated
|
|
180
|
+
* confidence + dev-mode — because the host owns both the toggle state AND the {@link result} / {@link debugDrawer}
|
|
181
|
+
* renderers those toggles drive.
|
|
182
|
+
*/
|
|
183
|
+
aboveResult?: (context: { result: ParseResult | null }) => ReactNode
|
|
184
|
+
/**
|
|
185
|
+
* Replace the package's default {@link ResultPanel} entirely. When provided, the host renders its own result block
|
|
186
|
+
* (the docs `<ResultPanel>` — span highlight, timing, hierarchy, precision detail, calibrated confidences) from the
|
|
187
|
+
* {@link DemoResultContext}. Absent → the built-in panel renders.
|
|
188
|
+
*/
|
|
189
|
+
result?: (context: DemoResultContext) => ReactNode
|
|
190
|
+
/**
|
|
191
|
+
* Rendered in place of the resolved-place panel when nothing resolved (host's FailureDiagnostic). Ignored when
|
|
192
|
+
* {@link result} is set.
|
|
193
|
+
*/
|
|
194
|
+
failure?: (result: ParseResult) => ReactNode
|
|
195
|
+
/** The version-compare view — the host renders its own diff from the compare state it owns. */
|
|
196
|
+
compare?: (context: DemoCompareContext) => ReactNode
|
|
197
|
+
/**
|
|
198
|
+
* The model-visualizer / debug drawer, mounted beside the map (host's ModelVisualizer). A render-prop so the host can
|
|
199
|
+
* trace the CURRENT result (its input) — the package passes the live parse result; the host gates on its own dev-mode
|
|
200
|
+
* state and returns `null` when the drawer is closed.
|
|
201
|
+
*/
|
|
202
|
+
debugDrawer?: (context: { result: ParseResult | null }) => ReactNode
|
|
203
|
+
/** Extra map controls mounted as `<DemoMap>` children (host's DebugControl / LayerToggle via `useControl`). */
|
|
204
|
+
mapControls?: ReactNode
|
|
205
|
+
/** A permalink control for the current address (host's PermalinkButton). */
|
|
206
|
+
permalink?: (text: string) => ReactNode
|
|
207
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `useCompareState` — the small headless state machine behind the demo's compare toggle
|
|
7
|
+
* (`_app.tsx:142-143, 1238-1305`): whether compare mode is on, and which version to compare against.
|
|
8
|
+
* Turning compare off clears the chosen version; selecting a compare version equal to the primary is
|
|
9
|
+
* rejected (the picker filters it out, this guards the programmatic path). The SECOND parse itself is a
|
|
10
|
+
* host concern surfaced through `panels.compare` — this hook owns only the toggle + selection.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useCallback, useState } from "react"
|
|
14
|
+
|
|
15
|
+
export interface UseCompareState {
|
|
16
|
+
/** Whether compare mode is on. */
|
|
17
|
+
compareMode: boolean
|
|
18
|
+
/** The version chosen to compare against, or `null`. */
|
|
19
|
+
compareVersion: string | null
|
|
20
|
+
/** Flip compare mode; turning it off clears the chosen version. */
|
|
21
|
+
setCompareMode: (compareMode: boolean) => void
|
|
22
|
+
/** Choose a compare version (`null` clears it). */
|
|
23
|
+
setCompareVersion: (version: string | null) => void
|
|
24
|
+
/** Keep the compare selection distinct from the primary — call when the primary version changes. */
|
|
25
|
+
clearIfPrimary: (primaryVersion: string) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useCompareState(): UseCompareState {
|
|
29
|
+
const [compareMode, setCompareModeState] = useState(false)
|
|
30
|
+
const [compareVersion, setCompareVersion] = useState<string | null>(null)
|
|
31
|
+
|
|
32
|
+
const setCompareMode = useCallback((next: boolean) => {
|
|
33
|
+
setCompareModeState(next)
|
|
34
|
+
|
|
35
|
+
if (!next) {
|
|
36
|
+
setCompareVersion(null)
|
|
37
|
+
}
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
const clearIfPrimary = useCallback((primaryVersion: string) => {
|
|
41
|
+
setCompareVersion((prev) => (prev === primaryVersion ? null : prev))
|
|
42
|
+
}, [])
|
|
43
|
+
|
|
44
|
+
return { compareMode, compareVersion, setCompareMode, setCompareVersion, clearIfPrimary }
|
|
45
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `useDemoGeocode` — the parse+resolve state machine for the geocoder map demo. It REUSES the pipeline's
|
|
7
|
+
* `useParsePipeline` (text / busy / stage / result / candidate selection) and layers on the two map-only
|
|
8
|
+
* concerns the demo adds over the base explorer:
|
|
9
|
+
*
|
|
10
|
+
* 1. Viewport bias — when the runtime exposes `runParseWithBias`, the current map center (read through
|
|
11
|
+
* an injected `getBias`, itself reading the `MapRef`) rides along as a soft proximity prior. The
|
|
12
|
+
* bias is injected by deriving a `runParse` that binds it, so `useParsePipeline` is reused verbatim.
|
|
13
|
+
* 2. The map render place — the selected candidate, enriched by the host's `resolveMapPlace` into the
|
|
14
|
+
* richer {@link ResolvedMapPlace} the declarative overlays consume (bbox / tier / polygon). Absent
|
|
15
|
+
* an enricher, the candidate renders as a bare point.
|
|
16
|
+
*
|
|
17
|
+
* No maplibre import at module scope — the map handle is reached only through the injected `getBias`
|
|
18
|
+
* callback, so this hook stays independent of the map binding.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useMemo } from "react"
|
|
22
|
+
|
|
23
|
+
import type { PipelineRuntime } from "../pipeline/types.ts"
|
|
24
|
+
import { useParsePipeline, type UseParsePipeline } from "../pipeline/useParsePipeline.ts"
|
|
25
|
+
import type { ResolvedMapPlace } from "./place-render.ts"
|
|
26
|
+
import type { DemoRuntime, MapBias } from "./types.ts"
|
|
27
|
+
|
|
28
|
+
export interface UseDemoGeocodeOptions {
|
|
29
|
+
/** The injected demo runtime (extends `PipelineRuntime` with the map + bias surface). */
|
|
30
|
+
runtime: DemoRuntime
|
|
31
|
+
/** Address to pre-fill. */
|
|
32
|
+
defaultText: string
|
|
33
|
+
/** Read the current viewport bias (the map center) at submit time. Absent → no bias. */
|
|
34
|
+
getBias?: () => MapBias | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UseDemoGeocode extends UseParsePipeline {
|
|
38
|
+
/** The selected candidate enriched into the map-render shape (bbox / tier / polygon), or `null`. */
|
|
39
|
+
mapPlace: ResolvedMapPlace | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useDemoGeocode({ runtime, defaultText, getBias }: UseDemoGeocodeOptions): UseDemoGeocode {
|
|
43
|
+
// Bind the viewport bias into a derived `runParse` so `useParsePipeline` is reused unchanged. When the runtime has no
|
|
44
|
+
// bias-aware parse, pass it straight through.
|
|
45
|
+
const geoRuntime = useMemo<PipelineRuntime>(() => {
|
|
46
|
+
const withBias = runtime.runParseWithBias
|
|
47
|
+
|
|
48
|
+
if (!withBias) return runtime
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
...runtime,
|
|
52
|
+
runParse: (input, hooks) => withBias(input, getBias?.() ?? null, hooks),
|
|
53
|
+
}
|
|
54
|
+
}, [runtime, getBias])
|
|
55
|
+
|
|
56
|
+
const pipeline = useParsePipeline({ runtime: geoRuntime, defaultText })
|
|
57
|
+
|
|
58
|
+
const mapPlace = useMemo<ResolvedMapPlace | null>(() => {
|
|
59
|
+
if (!pipeline.selectedCandidate || !pipeline.result) return null
|
|
60
|
+
|
|
61
|
+
return runtime.resolveMapPlace
|
|
62
|
+
? runtime.resolveMapPlace(pipeline.selectedCandidate, pipeline.result)
|
|
63
|
+
: pipeline.selectedCandidate
|
|
64
|
+
}, [pipeline.selectedCandidate, pipeline.result, runtime])
|
|
65
|
+
|
|
66
|
+
return { ...pipeline, mapPlace }
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `useMapPlaceRender` — the thin React memo wrapper over the pure {@link computeMapPlaceRenderSpec}. It
|
|
7
|
+
* recomputes the render spec only when the resolved place changes, so the declarative overlays render a
|
|
8
|
+
* stable spec object. The MATH lives in `place-render.ts` (pure, node-tested); this file adds nothing
|
|
9
|
+
* but memoization, so it stays trivially correct. A `null` place (no result yet, or a result with no
|
|
10
|
+
* candidate) yields `null` — the overlays render nothing.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useMemo } from "react"
|
|
14
|
+
|
|
15
|
+
import { computeMapPlaceRenderSpec } from "./place-render.ts"
|
|
16
|
+
import type { MapPlaceRenderSpec, ResolvedMapPlace } from "./place-render.ts"
|
|
17
|
+
|
|
18
|
+
/** Memoize the render spec for a resolved place; `null` in → `null` out (nothing to draw). */
|
|
19
|
+
export function useMapPlaceRender(place: ResolvedMapPlace | null | undefined): MapPlaceRenderSpec | null {
|
|
20
|
+
return useMemo(() => (place ? computeMapPlaceRenderSpec(place) : null), [place])
|
|
21
|
+
}
|