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>
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
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>,
|
||
);
|