pwaLUPMIS2/src/toast.js
ekke ef12e4477b Offline tile cache, polygon Divide, topographic layer integrations
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>
2026-05-26 10:55:30 +02:00

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);
}