Files
road-compiler/workbench/client/probe/ol-ext-transform/main.tsx
que01 bc4b9a9717 feat: add direct edit handles behind directEdit flag
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>
2026-08-27 14:59:11 +08:00

62 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Component, StrictMode, useState, type ReactNode } from 'react';
import { createRoot } from 'react-dom/client';
import { ProbeMap } from './ProbeMap';
import './probe.css';
// A probe that white-screens teaches nothing: the run has to say *whose* bug it
// was, ol-ext's or ours. This boundary keeps the error on the page so the gate
// result stays attributable.
class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
state: { error: Error | null } = { error: null };
static getDerivedStateFromError(error: Error) {
return { error };
}
render() {
const { error } = this.state;
if (!error) return this.props.children;
return (
<section className="probe-error">
<h1></h1>
<p> </p>
<pre>
{error.name}: {error.message}
{'\n\n'}
{error.stack}
</pre>
<button type="button" onClick={() => this.setState({ error: null })}>
</button>
</section>
);
}
}
// The mount toggle exists so the React lifecycle question gets answered too: a
// remount is *expected* to build a new Map, while dragging and re-rendering must
// not. Gate 1 is about the latter.
function Probe() {
const [mounted, setMounted] = useState(true);
return (
<>
<nav className="probe-nav">
<strong>ol-ext Transform </strong>
<span> API</span>
<button type="button" onClick={() => setMounted(!mounted)}>
{mounted ? '卸载地图' : '重新挂载地图'}
</button>
</nav>
{mounted ? <ProbeMap /> : <p className="probe-empty"> Map</p>}
</>
);
}
createRoot(document.getElementById('probe')!).render(
<StrictMode>
<ErrorBoundary>
<Probe />
</ErrorBoundary>
</StrictMode>,
);