import ViewMode, { ViewModeArgs } from "../view_mode.js"; import L from "leaflet"; import type { GPX, LatLng, Map, Marker } from "leaflet"; import SpacedUpdate from "../../../services/spaced_update.js"; import { t } from "../../../services/i18n.js"; import processNoteWithMarker from "./markers.js"; import froca from "../../../services/froca.js"; const TPL = /*html*/`
`; interface MapData { view?: { center?: LatLng | [number, number]; zoom?: number; }; } const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659]; const DEFAULT_ZOOM = 2; const LOCATION_ATTRIBUTE = "geolocation"; const CHILD_NOTE_ICON = "bx bx-pin"; export default class GeoView extends ViewMode { private args: ViewModeArgs; private $root: JQuery; private $container!: JQuery; private map?: Map; private spacedUpdate: SpacedUpdate; private currentMarkerData: Record; private currentTrackData: Record; constructor(args: ViewModeArgs) { super(args, "geoMap"); this.args = args; this.$root = $(TPL); this.$container = this.$root.find(".geo-map-container"); this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000); this.currentMarkerData = {}; this.currentTrackData = {}; args.$parent.append(this.$root); } async renderList() { this.renderMap(); return this.$root; } async renderMap() { const map = L.map(this.$container[0], { worldCopyJump: true }); L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: '© OpenStreetMap contributors', detectRetina: true }).addTo(map); this.map = map; this.#onMapInitialized(); } async #onMapInitialized() { const map = this.map; if (!map) { throw new Error(t("geo-map.unable-to-load-map")); } this.#restoreViewportAndZoom(); const updateFn = () => this.spacedUpdate.scheduleUpdate(); map.on("moveend", updateFn); map.on("zoomend", updateFn); // map.on("click", (e) => this.#onMapClicked(e)); this.#reloadMarkers(); } async #restoreViewportAndZoom() { const map = this.map; if (!map) { return; } const parsedContent = await this.viewStorage.restore(); // Restore viewport position & zoom const center = parsedContent?.view?.center ?? DEFAULT_COORDINATES; const zoom = parsedContent?.view?.zoom ?? DEFAULT_ZOOM; map.setView(center, zoom); } private onSave() { const map = this.map; let data: MapData = {}; if (map) { data = { view: { center: map.getBounds().getCenter(), zoom: map.getZoom() } }; } this.viewStorage.store(data); } async #reloadMarkers() { if (!this.map) { return; } // Delete all existing markers for (const marker of Object.values(this.currentMarkerData)) { marker.remove(); } // Delete all existing tracks for (const track of Object.values(this.currentTrackData)) { track.remove(); } // Add the new markers. this.currentMarkerData = {}; for (const noteId of this.args.noteIds) { const childNote = await froca.getNote(noteId); if (!childNote) { continue; } if (childNote.mime === "application/gpx+xml") { // this.#processNoteWithGpxTrack(childNote); continue; } const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE); if (latLng) { const marker = processNoteWithMarker(this.map, childNote, latLng); this.currentMarkerData[childNote.noteId] = marker; } } } get isFullHeight(): boolean { return true; } }