mirror of
https://github.com/renorris/openfsd
synced 2026-08-14 17:41:41 +08:00
Load APT/AIR, render OSM map layers, select for inspector view, fit bounds. Hard scope: no draw tools or Blob download yet.
204 lines
10 KiB
HTML
204 lines
10 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). Essential data path without JS: paste apt_text / air_text + form
|
|
CSRF echo-download. 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).
|
|
*/}}
|
|
<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">
|
|
|
|
<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-hint" data-js="chip-counts"></span>
|
|
</div>
|
|
<p class="apted-hint apted-title-hint">
|
|
Integrated .apt + .air · map requires JavaScript · text download works without JS
|
|
</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-secondary" data-js="fit">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>
|
|
</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">
|
|
Map editing requires JavaScript. Use the text fallback below to
|
|
paste and download <code>.apt</code> / <code>.air</code> files.
|
|
</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">Parse errors</span>
|
|
</div>
|
|
<div class="apted-panel-bd" data-js="panel-validate">
|
|
<p class="apted-hint">Load .apt / .air to see parse issues.</p>
|
|
</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">Read-only preview</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">View only</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>
|
|
|
|
{{/* No-JS fallback: always present in HTML (not JS-injected) */}}
|
|
<section class="apted-fallback" aria-labelledby="apted-fallback-heading">
|
|
<h2 id="apted-fallback-heading">Text fallback (works without JavaScript)</h2>
|
|
<p class="apted-hint">
|
|
Map editing requires JavaScript. Paste or type file text below, then download.
|
|
Echo-download only — the server does not persist these files.
|
|
</p>
|
|
|
|
<form method="post" action="/airport-editor/download-apt" class="apted-form">
|
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
|
<div class="apted-field">
|
|
<label for="apt_text">Airport .apt</label>
|
|
<textarea id="apt_text" name="apt_text" rows="12" spellcheck="false"
|
|
placeholder="icao=KBTV …"></textarea>
|
|
</div>
|
|
<div class="apted-field">
|
|
<label for="apt_filename">Download filename</label>
|
|
<input type="text" id="apt_filename" name="filename" value="airport.apt"
|
|
autocomplete="off" spellcheck="false" maxlength="64"
|
|
pattern="[A-Za-z0-9._\-]{1,64}">
|
|
</div>
|
|
<button type="submit" class="btn btn-sm btn-primary">Download .apt</button>
|
|
</form>
|
|
|
|
<form method="post" action="/airport-editor/download-air" class="apted-form">
|
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
|
<div class="apted-field">
|
|
<label for="air_text">Scenario .air</label>
|
|
<textarea id="air_text" name="air_text" rows="8" spellcheck="false"
|
|
placeholder="CALLSIGN:TYPE:…"></textarea>
|
|
</div>
|
|
<div class="apted-field">
|
|
<label for="air_filename">Download filename</label>
|
|
<input type="text" id="air_filename" name="filename" value="scenario.air"
|
|
autocomplete="off" spellcheck="false" maxlength="64"
|
|
pattern="[A-Za-z0-9._\-]{1,64}">
|
|
</div>
|
|
<button type="submit" class="btn btn-sm btn-primary">Download .air</button>
|
|
</form>
|
|
</section>
|
|
</main>
|
|
|
|
<script src="/static/js/leaflet.js"></script>
|
|
<script src="/static/js/openfsd/leaflet.rotatedmarker.js"></script>
|
|
<script type="module" src="/static/js/openfsd/airport-editor/main.js"></script>
|
|
{{ end }}
|