mirror of
https://github.com/renorris/openfsd
synced 2026-08-10 11:33:29 +08:00
airport-editor: restore kind colors on vertex nodes
Vertex circleMarkers now use the surfaceStyle palette (taxi blue-gray, runway near-black, hold orange, parking green) so types stay distinct while keeping centered grab handles and drag behavior.
This commit is contained in:
@@ -236,31 +236,48 @@ export const VERTEX_PANE = 'aptedVertex';
|
||||
|
||||
/**
|
||||
* Pure: Leaflet circleMarker options for a vertex node (always lat/lng-centered).
|
||||
* Colors follow surfaceStyle kind palette so taxi / rwy / hold / park stay distinct.
|
||||
* Drag is NOT Marker.draggable — OverlayController uses capture-phase map hit-test.
|
||||
*
|
||||
* @param {number} vertexIndex
|
||||
* @param {{ selected?: boolean, dragging?: boolean }} [state]
|
||||
* @param {{ kind?: string, selected?: boolean, dragging?: boolean }} [state]
|
||||
* @returns {object}
|
||||
*/
|
||||
export function buildVertexHandleStyle(vertexIndex, state = {}) {
|
||||
const selected = !!state.selected;
|
||||
const dragging = !!state.dragging;
|
||||
const kind = state.kind || SurfaceTaxiway;
|
||||
// Kind colors always (not selection-blue) so types stay readable when all nodes show.
|
||||
const base = surfaceStyle(kind, false);
|
||||
const stroke = base.color;
|
||||
// Polylines: white disc + kind stroke. Parking: green fill like parking markers.
|
||||
let fillColor = '#ffffff';
|
||||
let fillOpacity = 1;
|
||||
if (kind === SurfaceParking) {
|
||||
fillColor = base.fillColor || '#40916c';
|
||||
fillOpacity = 0.95;
|
||||
}
|
||||
if (dragging) {
|
||||
// Slightly emphasize without losing kind hue.
|
||||
fillColor = kind === SurfaceParking ? '#7dcea0' : '#ffffff';
|
||||
}
|
||||
return {
|
||||
radius: VERTEX_HANDLE_RADIUS,
|
||||
radius: VERTEX_HANDLE_RADIUS + (selected ? 1 : 0) + (dragging ? 1 : 0),
|
||||
// Interactive false: pointer events pass through to map capture hit-test.
|
||||
interactive: false,
|
||||
bubblingMouseEvents: false,
|
||||
weight: dragging ? 2 : 1.5,
|
||||
weight: dragging ? 2.5 : selected ? 2 : 1.5,
|
||||
opacity: 1,
|
||||
fillOpacity: 1,
|
||||
color: dragging ? '#1a3a60' : selected ? '#2a5a90' : '#4a7ab0',
|
||||
fillColor: dragging ? '#d0e4f8' : selected ? '#e8f0fa' : '#ffffff',
|
||||
fillOpacity,
|
||||
color: stroke,
|
||||
fillColor,
|
||||
className:
|
||||
'apted-vertex-handle' +
|
||||
` apted-vertex-${kindShort(kind)}` +
|
||||
(selected ? ' is-selected' : '') +
|
||||
(dragging ? ' is-dragging' : ''),
|
||||
// title is not a path option; kept only for call-site docs
|
||||
// (vertexIndex used so the API stays stable for tests)
|
||||
_vertexIndex: vertexIndex,
|
||||
_kind: kind,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1114,9 +1131,12 @@ export class OverlayController {
|
||||
mapDraggingWasEnabled,
|
||||
};
|
||||
|
||||
// Visual feedback on the centered circleMarker.
|
||||
// Visual feedback on the centered circleMarker (keep kind color).
|
||||
if (handle && typeof handle.setStyle === 'function') {
|
||||
handle.setStyle(buildVertexHandleStyle(vertexIndex, { selected: true, dragging: true }));
|
||||
const kind = this._airport?.surfaces?.[surfaceIndex]?.kind;
|
||||
handle.setStyle(
|
||||
buildVertexHandleStyle(vertexIndex, { kind, selected: true, dragging: true }),
|
||||
);
|
||||
}
|
||||
|
||||
// Do not snap vertex to cursor on mousedown — only move once the pointer moves.
|
||||
@@ -1152,7 +1172,14 @@ export class OverlayController {
|
||||
if (h && typeof h.setStyle === 'function') {
|
||||
const stillSelected =
|
||||
this._selection?.type === 'surface' && this._selection.index === si;
|
||||
h.setStyle(buildVertexHandleStyle(vi, { selected: stillSelected, dragging: false }));
|
||||
const kind = this._airport?.surfaces?.[si]?.kind;
|
||||
h.setStyle(
|
||||
buildVertexHandleStyle(vi, {
|
||||
kind,
|
||||
selected: stillSelected,
|
||||
dragging: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const ll = this._latLngFromPointerEvent(ev) || (h && h.getLatLng && h.getLatLng());
|
||||
@@ -1194,6 +1221,7 @@ export class OverlayController {
|
||||
const pts = surface.points || [];
|
||||
const selected =
|
||||
this._selection?.type === 'surface' && this._selection.index === surfaceIndex;
|
||||
const kind = surface.kind;
|
||||
|
||||
for (let vi = 0; vi < pts.length; vi++) {
|
||||
const p = pts[vi];
|
||||
@@ -1201,7 +1229,7 @@ export class OverlayController {
|
||||
// circleMarker: geographic center === visual center (no iconAnchor).
|
||||
const handle = L.circleMarker(
|
||||
[p.lat, p.lon],
|
||||
buildVertexHandleStyle(vi, { selected, dragging: false }),
|
||||
buildVertexHandleStyle(vi, { kind, selected, dragging: false }),
|
||||
);
|
||||
handle.addTo(this.vertexGroup);
|
||||
this._handleByKey.set(`${surfaceIndex}:${vi}`, handle);
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
Static assets are go:embed into the openfsd binary — restart the process after
|
||||
JS/CSS changes. Query string busts browser cache when the binary is rebuilt.
|
||||
*/}}
|
||||
<script src="/static/js/leaflet.js?v=vertex-edit-6"></script>
|
||||
<script src="/static/js/openfsd/leaflet.rotatedmarker.js?v=vertex-edit-6"></script>
|
||||
<script type="module" src="/static/js/openfsd/airport-editor/main.js?v=vertex-edit-6"></script>
|
||||
<script src="/static/js/leaflet.js?v=vertex-edit-7"></script>
|
||||
<script src="/static/js/openfsd/leaflet.rotatedmarker.js?v=vertex-edit-7"></script>
|
||||
<script type="module" src="/static/js/openfsd/airport-editor/main.js?v=vertex-edit-7"></script>
|
||||
{{ end }}
|
||||
|
||||
@@ -361,21 +361,52 @@ test('stopLeafletClickBubble sets originalEvent._stopped (Leaflet map bubble gua
|
||||
stopLeafletClickBubble({}, null);
|
||||
});
|
||||
|
||||
test('buildVertexHandleStyle: circleMarker centered on lat/lng (no iconAnchor)', () => {
|
||||
test('buildVertexHandleStyle: kind colors match surface palette', () => {
|
||||
assert.equal(VERTEX_HANDLE_RADIUS, 5);
|
||||
assert.equal(VERTEX_HANDLE_PX, VERTEX_HANDLE_RADIUS * 2);
|
||||
const style = buildVertexHandleStyle(2, { selected: false, dragging: false });
|
||||
assert.equal(style.radius, VERTEX_HANDLE_RADIUS);
|
||||
assert.equal(style.interactive, false);
|
||||
assert.equal(style.bubblingMouseEvents, false);
|
||||
assert.equal(style.fillOpacity, 1);
|
||||
assert.match(style.className, /apted-vertex-handle/);
|
||||
assert.ok(!style.className.includes('is-selected'));
|
||||
const sel = buildVertexHandleStyle(0, { selected: true, dragging: false });
|
||||
|
||||
const taxi = buildVertexHandleStyle(0, { kind: SurfaceTaxiway });
|
||||
const rwy = buildVertexHandleStyle(0, { kind: SurfaceRunway });
|
||||
const hold = buildVertexHandleStyle(0, { kind: SurfaceHold });
|
||||
const park = buildVertexHandleStyle(0, { kind: SurfaceParking });
|
||||
|
||||
// Differentiated strokes (same palette as surfaceStyle unselected).
|
||||
assert.equal(taxi.color, surfaceStyle(SurfaceTaxiway, false).color);
|
||||
assert.equal(rwy.color, surfaceStyle(SurfaceRunway, false).color);
|
||||
assert.equal(hold.color, surfaceStyle(SurfaceHold, false).color);
|
||||
assert.equal(park.color, surfaceStyle(SurfaceParking, false).color);
|
||||
assert.notEqual(taxi.color, rwy.color);
|
||||
assert.notEqual(taxi.color, hold.color);
|
||||
assert.notEqual(hold.color, park.color);
|
||||
|
||||
// Taxi/rwy/hold: white fill disc; parking: green fill like parking markers.
|
||||
assert.equal(taxi.fillColor, '#ffffff');
|
||||
assert.equal(rwy.fillColor, '#ffffff');
|
||||
assert.equal(hold.fillColor, '#ffffff');
|
||||
assert.equal(park.fillColor, surfaceStyle(SurfaceParking, false).fillColor);
|
||||
|
||||
assert.equal(taxi.interactive, false);
|
||||
assert.match(taxi.className, /apted-vertex-taxi/);
|
||||
assert.match(rwy.className, /apted-vertex-rwy/);
|
||||
assert.match(hold.className, /apted-vertex-hold/);
|
||||
assert.match(park.className, /apted-vertex-park/);
|
||||
|
||||
const sel = buildVertexHandleStyle(0, {
|
||||
kind: SurfaceTaxiway,
|
||||
selected: true,
|
||||
});
|
||||
assert.match(sel.className, /is-selected/);
|
||||
const drag = buildVertexHandleStyle(0, { selected: true, dragging: true });
|
||||
assert.equal(sel.color, taxi.color); // selection keeps kind hue
|
||||
assert.ok(sel.radius > taxi.radius);
|
||||
|
||||
const drag = buildVertexHandleStyle(0, {
|
||||
kind: SurfaceHold,
|
||||
selected: true,
|
||||
dragging: true,
|
||||
});
|
||||
assert.match(drag.className, /is-dragging/);
|
||||
// Legacy divIcon helpers still export centered anchors if ever reused.
|
||||
assert.equal(drag.color, hold.color);
|
||||
|
||||
const icon = buildVertexHandleIconOptions();
|
||||
assert.equal(icon.iconAnchor[0] * 2, icon.iconSize[0]);
|
||||
assert.equal(buildVertexHandleOptions(2).interactive, false);
|
||||
|
||||
Reference in New Issue
Block a user