mirror of
https://github.com/renorris/openfsd
synced 2026-08-14 09:25:51 +08:00
Root cause: clicking a taxiway selected it (blue highlight) then the same DOM click re-fired on the map and cleared selection, so handles never stayed visible and vertex drag could not run. Leaflet only stops map re-fire when originalEvent._stopped is set; native stopPropagation alone does not set that flag. Set _stopped, disable path bubblingMouseEvents, and gate map clicks after feature clicks. Disable boxZoom (blue rectangle). Cache-bust static module URLs (assets are go:embed).
219 lines
12 KiB
HTML
219 lines
12 KiB
HTML
{{ define "title" }}Airport Editor{{ end }}
|
|
|
|
{{ define "body" }}
|
|
{{/*
|
|
JS budget exception (airport map editor) — complexity-gate exception:
|
|
This route loads Leaflet (vendor) + first-party airport-editor modules.
|
|
Map geometry authoring is JS-primary (documented exception to the general
|
|
PE bar): open/edit/download requires JavaScript. No SPA router / global
|
|
server store / hydration. Files are never written to server disk or DB.
|
|
|
|
e2e blank tiles: set data-test-tiles="blank" on #apted-root (no OSM/Esri).
|
|
That is the only blank-tile hook for this page (query ?e2e=1 is not used).
|
|
*/}}
|
|
<link href="/static/css/leaflet.css" rel="stylesheet">
|
|
<link href="/static/css/openfsd/airport-editor.css" rel="stylesheet">
|
|
|
|
<main id="apted-root" class="apted container-fluid"
|
|
data-js="airport-editor"
|
|
data-validate-apt="/api/v1/editor/validate-apt"
|
|
data-validate-air="/api/v1/editor/validate-air">
|
|
|
|
<header class="apted-titlebar">
|
|
<h1>Airport Editor</h1>
|
|
<div class="apted-chips" aria-label="Document status">
|
|
<span class="apted-chip apted-chip-icao" data-js="chip-icao">—</span>
|
|
<span class="apted-chip" data-js="chip-apt" title="Airport geometry">APT</span>
|
|
<span class="apted-chip" data-js="chip-air" title="Scenario aircraft">AIR</span>
|
|
<span class="apted-chip apted-chip-issues is-empty" data-js="chip-issues"
|
|
title="Load APT/AIR to validate">—</span>
|
|
<span class="apted-hint" data-js="chip-counts"></span>
|
|
</div>
|
|
<p class="apted-hint apted-title-hint">
|
|
Integrated .apt + .air · requires JavaScript · downloads via toolbar (Blob)
|
|
</p>
|
|
</header>
|
|
|
|
{{ if .FlashSuccess }}
|
|
<div class="apted-flash apted-flash-ok" role="status">{{ .FlashSuccess }}</div>
|
|
{{ end }}
|
|
{{ if .FlashError }}
|
|
<div class="apted-flash apted-flash-err" role="alert">{{ .FlashError }}</div>
|
|
{{ end }}
|
|
|
|
<p class="apted-flash" data-js="status" hidden role="status"></p>
|
|
|
|
<div class="apted-toolbar" role="toolbar" aria-label="Editor tools">
|
|
<label class="btn btn-sm btn-outline-dark apted-file-btn">
|
|
Open .apt
|
|
<input type="file" accept=".apt,text/plain" data-js="open-apt" hidden>
|
|
</label>
|
|
<label class="btn btn-sm btn-outline-dark apted-file-btn">
|
|
Open .air
|
|
<input type="file" accept=".air,text/plain" data-js="open-air" hidden>
|
|
</label>
|
|
<span class="apted-toolbar-sep" aria-hidden="true"></span>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" data-js="dl-apt"
|
|
title="Download .apt (Blob)">DL .apt</button>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" data-js="dl-air"
|
|
title="Download .air (Blob)">DL .air</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-js="new"
|
|
title="New empty document">New</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-js="mark-clean"
|
|
title="Mark APT/AIR clean without download">Mark clean</button>
|
|
<span class="apted-toolbar-sep" aria-hidden="true"></span>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-js="fit"
|
|
title="Fit bounds (F)">Fit</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-js="layer"
|
|
aria-label="Basemap layer: OSM. Click to switch.">Layer: OSM</button>
|
|
<span class="apted-toolbar-sep" aria-hidden="true"></span>
|
|
<div class="apted-mode-group" role="group" aria-label="Edit mode" data-js="mode-group">
|
|
<button type="button" class="btn btn-sm btn-outline-dark apted-mode-btn is-active"
|
|
data-mode="select" aria-pressed="true" title="Select (1)">1 Select</button>
|
|
<button type="button" class="btn btn-sm btn-outline-dark apted-mode-btn"
|
|
data-mode="park" aria-pressed="false" title="Parking (2)">2 Park</button>
|
|
<button type="button" class="btn btn-sm btn-outline-dark apted-mode-btn"
|
|
data-mode="taxi" aria-pressed="false" title="Taxiway (3)">3 Taxi</button>
|
|
<button type="button" class="btn btn-sm btn-outline-dark apted-mode-btn"
|
|
data-mode="runway" aria-pressed="false" title="Runway (4)">4 Rwy</button>
|
|
<button type="button" class="btn btn-sm btn-outline-dark apted-mode-btn"
|
|
data-mode="hold" aria-pressed="false" title="Hold (5)">5 Hold</button>
|
|
<button type="button" class="btn btn-sm btn-outline-dark apted-mode-btn"
|
|
data-mode="aircraft" aria-pressed="false" title="Aircraft (6)">6 Acft</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="apted-body">
|
|
<div id="apted-map" class="apted-map" role="application"
|
|
aria-label="Airport map editor (requires JavaScript)">
|
|
<p class="apted-map-placeholder">
|
|
Airport editor requires JavaScript to open, edit, and download
|
|
<code>.apt</code> / <code>.air</code> files on the map.
|
|
</p>
|
|
</div>
|
|
|
|
<aside class="apted-rail" data-js="rail" aria-label="Editor panels">
|
|
<div class="apted-tabs" role="tablist" aria-label="Editor tabs" data-js="rail-tabs">
|
|
<button type="button" class="apted-tab is-active" role="tab"
|
|
data-tab="airport" aria-selected="true" id="apted-tab-airport">Airport</button>
|
|
<button type="button" class="apted-tab" role="tab"
|
|
data-tab="surfaces" aria-selected="false" id="apted-tab-surfaces">Surfaces</button>
|
|
<button type="button" class="apted-tab" role="tab"
|
|
data-tab="aircraft" aria-selected="false" id="apted-tab-aircraft">Aircraft</button>
|
|
<button type="button" class="apted-tab" role="tab"
|
|
data-tab="validate" aria-selected="false" id="apted-tab-validate">Validate</button>
|
|
<button type="button" class="apted-tab" role="tab"
|
|
data-tab="raw" aria-selected="false" id="apted-tab-raw">Raw</button>
|
|
</div>
|
|
|
|
<div class="apted-rail-scroll">
|
|
<section class="apted-panel apted-tab-panel is-active" data-js-tab-panel="airport"
|
|
role="tabpanel" aria-labelledby="apted-tab-airport">
|
|
<div class="apted-panel-hd">
|
|
<h2>Airport</h2>
|
|
<span class="apted-hint">Headers</span>
|
|
</div>
|
|
<div class="apted-panel-bd" data-js="panel-airport">
|
|
<p class="apted-hint">Open a .apt file to view airport headers.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="apted-panel apted-tab-panel" data-js-tab-panel="surfaces" hidden
|
|
role="tabpanel" aria-labelledby="apted-tab-surfaces">
|
|
<div class="apted-panel-hd">
|
|
<h2>Surfaces</h2>
|
|
<span class="apted-hint">Click to select</span>
|
|
</div>
|
|
<div class="apted-panel-bd" data-js="panel-surfaces">
|
|
<p class="apted-hint">No surfaces.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="apted-panel apted-tab-panel" data-js-tab-panel="aircraft" hidden
|
|
role="tabpanel" aria-labelledby="apted-tab-aircraft">
|
|
<div class="apted-panel-hd">
|
|
<h2>Aircraft</h2>
|
|
<span class="apted-hint">Click to select</span>
|
|
</div>
|
|
<div class="apted-panel-bd" data-js="panel-aircraft">
|
|
<p class="apted-hint">No aircraft.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="apted-panel apted-tab-panel" data-js-tab-panel="validate" hidden
|
|
role="tabpanel" aria-labelledby="apted-tab-validate">
|
|
<div class="apted-panel-hd">
|
|
<h2>Validate</h2>
|
|
<span class="apted-hint">Client + server</span>
|
|
</div>
|
|
<div class="apted-panel-bd" data-js="panel-validate">
|
|
<p class="apted-hint apted-validate-summary" data-js="validate-summary">
|
|
No APT/AIR loaded. Open files or draw on the map to see live parse
|
|
errors and soft cross-file warnings.
|
|
</p>
|
|
<div class="apted-server-validate" data-js="server-validate">
|
|
<h3 class="apted-subhd">Server confirm</h3>
|
|
<p class="apted-hint">
|
|
Optional Go ParseAPT / ParseAIR check via Admin API (does not save).
|
|
</p>
|
|
<div class="apted-btn-row">
|
|
<button type="button" class="btn btn-sm btn-outline-primary"
|
|
data-action="confirm-server" data-js="confirm-server"
|
|
disabled
|
|
title="Requires JavaScript and a loaded document">
|
|
Confirm with server
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="apted-handoff" data-js="sweatbox-handoff">
|
|
<p class="apted-hint">
|
|
When ready: Download files, then load in
|
|
<a href="/sweatbox" data-js="sweatbox-link">Sweatbox</a>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="apted-panel apted-tab-panel" data-js-tab-panel="raw" hidden
|
|
role="tabpanel" aria-labelledby="apted-tab-raw">
|
|
<div class="apted-panel-hd">
|
|
<h2>Raw</h2>
|
|
<span class="apted-hint">Preview · Apply to re-parse</span>
|
|
</div>
|
|
<div class="apted-panel-bd apted-raw-panel">
|
|
<div class="apted-field">
|
|
<label for="apted-raw-apt">.apt (formatted)</label>
|
|
<textarea id="apted-raw-apt" data-js="raw-apt" rows="10"
|
|
readonly spellcheck="false"></textarea>
|
|
</div>
|
|
<div class="apted-field">
|
|
<label for="apted-raw-air">.air (formatted)</label>
|
|
<textarea id="apted-raw-air" data-js="raw-air" rows="6"
|
|
readonly spellcheck="false"></textarea>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="apted-panel apted-inspector-panel" aria-labelledby="apted-inspector-heading">
|
|
<div class="apted-panel-hd">
|
|
<h2 id="apted-inspector-heading">Inspector</h2>
|
|
<span class="apted-hint">Edit fields · Del deletes</span>
|
|
</div>
|
|
<div class="apted-panel-bd" data-js="inspector">
|
|
<p class="apted-hint">Select a surface or aircraft on the map or in a list.</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</main>
|
|
|
|
{{/*
|
|
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-3"></script>
|
|
<script src="/static/js/openfsd/leaflet.rotatedmarker.js?v=vertex-edit-3"></script>
|
|
<script type="module" src="/static/js/openfsd/airport-editor/main.js?v=vertex-edit-3"></script>
|
|
{{ end }}
|