Steps 1-3 of the main map road interval editor. EditSession keeps the command stack, undo/redo and previewSeq arbitration as pure logic with no OpenLayers reference, so all of it is unit-tested in node. Pointer displacement converts to meters through EPSG:4326 and spherical distance: treating a 3857 delta as meters desyncs the geometry from the cursor by 1/cos(latitude). Handle drags project onto the axis the manifest declares and clamp to its range, so the client never writes a coordinate into a road polygon. All of it sits behind a directEdit flag that defaults to off. With the flag off the workbench requests no manifest, creates no extra source and registers no interaction, so behaviour matches main. The ol-ext probe passed its three gates but is not adopted for road handles. Transform translates by the raw pointer delta, so a handle detaches from its clamped constraint value: a drag reading -24.1 m produced a draft of -5.4 m. Production needs the handle position derived from the constraint instead, which means owning the position update, so native OL PointerInteraction will carry the drag. ol-ext stays out of package.json; the probe is kept as a manual harness. Reserve handles are unreachable with the current solver, recorded in research/ rather than worked around. Also names the dead backend when an API response is empty, instead of surfacing "Unexpected end of JSON input" from response.json(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
44 lines
1.9 KiB
TypeScript
44 lines
1.9 KiB
TypeScript
// selection -> which handles the main map may show.
|
|
//
|
|
// Pure manifest filtering, deliberately free of any OpenLayers import so it can
|
|
// be unit-tested in node. `handle-layer.ts` is the OL adapter that renders the
|
|
// result.
|
|
|
|
import { isRoadKind, type EditHandle, type HandleManifest, type JunctionReserve } from './types';
|
|
|
|
/**
|
|
* Road kinds only, and only for the selected road's segment.
|
|
*
|
|
* Junction kinds stay out even though the manifest carries them: reserve interiors
|
|
* belong to JunctionTools, and the main map must not offer a second way to edit
|
|
* them. Reserve-disabled road handles are *kept* rather than dropped, because the
|
|
* user has to be able to see why that stretch refuses to move.
|
|
*
|
|
* Handle anchors carry a directional `roadId`, while `reserves` are keyed by
|
|
* segment id, so callers pass a resolver instead of parsing ids apart.
|
|
*/
|
|
export function handlesForSegment(
|
|
manifest: HandleManifest | null,
|
|
segmentId: string | null,
|
|
segmentOf: (roadId: string) => string | undefined,
|
|
): EditHandle[] {
|
|
if (!manifest || !segmentId) return [];
|
|
return manifest.handles.filter((handle) => {
|
|
if (!isRoadKind(handle.kind)) return false;
|
|
const roadId = 'roadId' in handle.anchor ? handle.anchor.roadId : undefined;
|
|
return typeof roadId === 'string' && segmentOf(roadId) === segmentId;
|
|
});
|
|
}
|
|
|
|
/** Reserves that apply to one segment, for the range window and the tooltip copy. */
|
|
export function reservesForSegment(manifest: HandleManifest | null, segmentId: string | null): JunctionReserve[] {
|
|
if (!manifest || !segmentId) return [];
|
|
return manifest.reserves.filter((reserve) => reserve.roadId === segmentId);
|
|
}
|
|
|
|
/** Why a handle refuses to move. Falls back to a generic reason so the UI never says nothing. */
|
|
export function disabledReasonOf(handle: EditHandle): string | undefined {
|
|
if (handle.editable) return undefined;
|
|
return handle.disabledReason || '该手柄位于路口保留区,请进入 JunctionTools 编辑。';
|
|
}
|