Major feature batch covering drawing-tool improvements, layer additions,
and offline-first capabilities. Largest changes in MapView.js (+1700),
main.js (+1500), public/sw.js (+367), and new modules under src/.
Drawing & editing toolkit
* Polygon Divide tool — sub-button under Split, divides a polygon into
N equal-area pieces via binary search; user picks the cutting edge
* UPN pick phase after Split and Divide — non-picked pieces have their
identifier fields cleared automatically
* Improved Merge algorithm — vertex-to-edge proximity (5 m tol.) with
hybrid lockstep extension; bold A/B labels on selected polygons
* Persistent vertex highlights — all vertices of the selected polygon
rendered as dots while edit mode is on, without subclassing ol-ext
* Toast notifications for merge/split/divide outcomes
* Shapefile import — addGeoJSONLayer now includes an image style so
Point features render (previously invisible)
Background & overlay layers
* DEAfrica Coastlines v0.4 (WMS) in Biophysical Environment
* DEAfrica Slope (SRTM 30m, style_slope) — semi-transparent background
* Contours hillshade — get_contours_hillshade.php → local SQLite cache
* OSM_roads — get_osm_roads.php → local SQLite cache, casing-stroke
style (black 3.5 px outer, #F0F1F0 1.5 px inner)
* External Source dialog — green + button in LayerSwitcher lets users
add WMS / WFS / XYZ layers at runtime
* Generic addWMSLayer / addXYZLayer with style, opacity, zIndex,
legendUrl, onlineOnly options
* TileWMS replaces ImageWMS (fixes 'Width exceeds 512' WMS errors)
* Legend panel — bottom-right, auto-shown for visible layers that
register a legendUrl
* Default base map setting in Settings, persisted in localStorage;
setBaseMap() on MapView
Offline tile cache (Phase 1 + 2)
* Service worker: per-host tile caches (osm / topo / satellite /
carto-light / carto-dark), counter-based eviction to prevent
iOS Safari memory-pressure reloads, GET_TILE_STATS /
CLEAR_TILE_CACHES message API
* pwa.js helpers: getActiveServiceWorker, onServiceWorkerControllerChange,
getTileCacheStats, clearTileCaches, getStorageEstimate
* Settings: Offline Map Tiles card with per-provider stats + clear
* Phase 2 download dialog: form to pick base map, area (current view /
district / Ghana), zoom range; live tile-count + size estimate;
progress bar with cancel; OfflineTileDownloader class with
concurrency + throttling
Local database management
* osm_roads table + saveOSMRoads / getLocalOSMRoads helpers
* CACHED_LAYER_TABLES allow-list with clearTable / clearAllCachedLayers
* Local Database Tables card: per-row Clear button (cached layers
only) + 'Refresh cached layers' header button with reload prompt
Build & infrastructure
* Shpjs lazy-loaded via dynamic import (saves ~140 kB from initial JS)
* chunkSizeWarningLimit raised to 900 kB (openlayers + sqlite3.wasm
can't be split further)
* Toast notification module (src/toast.js)
* Units module (src/units.js) for metric / imperial conversions
* PDF export module (src/pdf-export.js)
Documentation & SQL
* Topographic_Background_Layers_for_LUPMIS2.docx — research report
* OpenTopography_Workflow.svg/.png — ETL pipeline diagram
* LUPMIS2_Development_Status_Report.docx — April update section
* sql/create_landuse_parcels.sql — PostgreSQL schema for the LUSPA
land-use parcel specification (Feb 2026, revised), with PostGIS
geometry column and standard indices
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
2.9 KiB
JavaScript
99 lines
2.9 KiB
JavaScript
/**
|
|
* Lightweight toast notification system.
|
|
*
|
|
* Usage:
|
|
* import { showToast } from '../toast.js';
|
|
*
|
|
* showToast('Something went wrong', 'error');
|
|
* showToast('Merge successful!', 'success');
|
|
* showToast('Select two adjacent polygons', 'info');
|
|
*/
|
|
|
|
// ── Palette ──────────────────────────────────────────────────────────────────
|
|
|
|
const THEMES = {
|
|
success: { bg: '#10b981', icon: '\u2705' }, // green
|
|
error: { bg: '#ef4444', icon: '\u274c' }, // red
|
|
warning: { bg: '#f59e0b', icon: '\u26a0\ufe0f' }, // amber
|
|
info: { bg: '#0ea5e9', icon: '\u2139\ufe0f' }, // cyan
|
|
};
|
|
|
|
// ── Container (created once, appended to <body>) ────────────────────────────
|
|
|
|
let container = null;
|
|
|
|
function ensureContainer() {
|
|
if (container) return container;
|
|
container = document.createElement('div');
|
|
container.style.cssText = `
|
|
position: fixed;
|
|
top: 16px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 10000;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
pointer-events: none;
|
|
`;
|
|
document.body.appendChild(container);
|
|
return container;
|
|
}
|
|
|
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Display a toast notification.
|
|
*
|
|
* @param {string} message Plain-text message to show.
|
|
* @param {'success'|'error'|'warning'|'info'} [type='info']
|
|
* @param {number} [duration=4000] Auto-dismiss time in ms.
|
|
*/
|
|
export function showToast(message, type = 'info', duration = 4000) {
|
|
const parent = ensureContainer();
|
|
const theme = THEMES[type] || THEMES.info;
|
|
|
|
const el = document.createElement('div');
|
|
el.style.cssText = `
|
|
background: ${theme.bg};
|
|
color: #fff;
|
|
padding: 10px 18px;
|
|
border-radius: 8px;
|
|
font-family: var(--font-body, 'Exo', sans-serif);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
|
pointer-events: auto;
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
transform: translateY(-8px);
|
|
max-width: 420px;
|
|
text-align: center;
|
|
line-height: 1.4;
|
|
`;
|
|
el.textContent = `${theme.icon} ${message}`;
|
|
|
|
parent.appendChild(el);
|
|
|
|
// Animate in
|
|
requestAnimationFrame(() => {
|
|
el.style.opacity = '1';
|
|
el.style.transform = 'translateY(0)';
|
|
});
|
|
|
|
// Dismiss helper
|
|
const dismiss = () => {
|
|
el.style.opacity = '0';
|
|
el.style.transform = 'translateY(-8px)';
|
|
setTimeout(() => el.remove(), 300);
|
|
};
|
|
|
|
// Click to dismiss early
|
|
el.addEventListener('click', dismiss);
|
|
|
|
// Auto-dismiss
|
|
setTimeout(dismiss, duration);
|
|
}
|