A logged-in user can now trigger, from a new "Offline data" sidebar panel, what was previously a fully manual go-pmtiles workflow (docs/development.adoc): regenerate either file for the current map viewport, no area-picker (same "visible area is the area" convention the Overpass POI plugin already uses). - lib/maptool/pmtiles_builder.rb: two independent background jobs (Thread + Mutex-guarded status Hash, same shape as app.rb's own GNSS poller), each writing to a .tmp path and atomically renaming into place so a build never serves a partially-written file mid-run. offline.pmtiles is a single `pmtiles extract` against Protomaps' public daily basemap build (byte-range extraction, not a full ~120GB download). terrain.pmtiles has no equivalent public Terrarium-encoded archive to extract from, so it fetches individual Terrarium tiles itself, packs them into an intermediate MBTiles (new sqlite3 gem), then the same `pmtiles convert`. - 4 new app.rb routes (POST .../build, GET .../build-status) plus size/mtime added to the existing availability endpoints. - public/js/offline_data.js: new sidebar panel, polling build-status the same self-rescheduling-setTimeout shape livetrack.js already uses for its own refresh loop. - Both network-heavy pieces (the Protomaps build index, per-tile fetches) are injectable, so test/test_pmtiles_builder.rb exercises the actual build pipeline against a fake `pmtiles` binary without hitting a real network, matching this suite's existing keep-it-offline convention for external services.
103 lines
3.9 KiB
ERB
103 lines
3.9 KiB
ERB
<div id="app">
|
|
<header class="topbar">
|
|
<span class="topbar__brand" title="rev <%= revision %>">MapTool</span>
|
|
<span class="topbar__center">
|
|
<span class="topbar__coords"></span>
|
|
<span class="topbar__sun" hidden></span>
|
|
</span>
|
|
<span class="topbar__offline-status" hidden></span>
|
|
<div class="topbar__auth">
|
|
<% if logged_in %>
|
|
<span><%= user %></span>
|
|
<a href="/logout">Log out</a>
|
|
<% else %>
|
|
<a href="/login">Log in</a>
|
|
<% end %>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="app-body">
|
|
<nav id="sidebar" class="sidebar"></nav>
|
|
<div id="map"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="profile-panel" class="profile-panel" hidden>
|
|
<div class="profile-panel__header">
|
|
<strong class="profile-panel__title"></strong>
|
|
<span class="profile-panel__stats"></span>
|
|
<button type="button" class="profile-panel__close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="profile-panel__charts"></div>
|
|
</div>
|
|
|
|
<div id="basemap-panel" class="basemap-panel" hidden>
|
|
<div class="basemap-panel__header">
|
|
<strong>Base map</strong>
|
|
<button type="button" class="basemap-panel__close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="basemap-panel__list">
|
|
<button type="button" class="basemap-panel__option" data-style="online">
|
|
<span>Online</span>
|
|
<span class="basemap-panel__check">✓</span>
|
|
</button>
|
|
<button type="button" class="basemap-panel__option" data-style="offline">
|
|
<span>Offline</span>
|
|
<span class="basemap-panel__check">✓</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="gpx-panel" class="gpx-panel" hidden>
|
|
<div class="gpx-panel__header">
|
|
<strong>Tracks & Points</strong>
|
|
<div class="gpx-panel__actions">
|
|
<button type="button" class="gpx-panel__action" data-action="new-folder" title="New folder" aria-label="New folder"></button>
|
|
<button type="button" class="gpx-panel__action" data-action="upload" title="Upload GPX/FIT/GeoJSON file" aria-label="Upload GPX/FIT/GeoJSON file"></button>
|
|
<button type="button" class="gpx-panel__close" aria-label="Close">×</button>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="file" class="gpx-file-input" accept=".gpx,.fit,.geojson,.zip" multiple hidden>
|
|
<p class="gpx-upload__status" hidden></p>
|
|
|
|
<div class="gpx-tree"></div>
|
|
<div class="gpx-legend" hidden></div>
|
|
</div>
|
|
|
|
<div id="overlays-panel" class="basemap-panel" hidden>
|
|
<div class="basemap-panel__header">
|
|
<strong>Overlays</strong>
|
|
<button type="button" class="basemap-panel__close" aria-label="Close">×</button>
|
|
</div>
|
|
<!-- Each item is a live layer refetched/toggled from its own source as
|
|
the map moves, not a file to add/remove/reorder — a plain on/off
|
|
list, no upload/new-folder actions the way Tracks & Points has.
|
|
Populated by OverlaysControl#mount (public/js/overlays.js), not
|
|
here — Radar's item needs its own scrub/forecast markup built by
|
|
RadarControl itself (radar.js), so all three items go through the
|
|
same "control owns its own <li>" path rather than half static
|
|
markup, half dynamic. -->
|
|
<ul class="overlays-panel__list"></ul>
|
|
</div>
|
|
|
|
<div id="livetrack-panel" class="basemap-panel" hidden>
|
|
<div class="basemap-panel__header">
|
|
<strong>LiveTrack sessions</strong>
|
|
<button type="button" class="basemap-panel__close" aria-label="Close">×</button>
|
|
</div>
|
|
<ul class="livetrack-panel__list"></ul>
|
|
</div>
|
|
|
|
<div id="offline-data-panel" class="basemap-panel" hidden>
|
|
<div class="basemap-panel__header">
|
|
<strong>Offline data</strong>
|
|
<button type="button" class="basemap-panel__close" aria-label="Close">×</button>
|
|
</div>
|
|
<!-- Two rows (map data / terrain data), each its own status text +
|
|
"Regenerate for current view" button — built by
|
|
OfflineDataControl#mount (public/js/offline_data.js), same
|
|
"control owns its content" story as the Overlays panel. -->
|
|
<div class="offline-data-panel__body"></div>
|
|
</div>
|