class MapHandler { map: L.Map; overlays: OverlayState; layers: TileLayerWrapper[]; modals: ModalCollection; static instance: MapHandler | null = null; private constructor(map: L.Map, overlays: OverlayState, layers: TileLayerWrapper[], modals: ModalCollection) { this.map = map; this.overlays = overlays; this.layers = layers; this.modals = modals; } static init(map: L.Map, overlays: OverlayState, layers: TileLayerWrapper[], modals: ModalCollection): void { if (!MapHandler.instance) { MapHandler.instance = new MapHandler(map, overlays, layers, modals); } } static setButtonClick(btnId: string, handler: any): void { const button = document.getElementById(btnId); if (button) { button.onclick = handler; } } static resetMapClick(): void { const self = MapHandler.instance; if (self) { try { const addPointBtn = document.getElementById("addPoint-btn"); if (addPointBtn) { addPointBtn.classList.remove("activeBtn"); } self.map.off("click", MapHandler.addMarker); } catch {} try { const addCircleBtn = document.getElementById("addCircle-btn"); if (addCircleBtn) { addCircleBtn.classList.remove("activeBtn"); } self.map.off("click", MapHandler.addCircle); } catch {} try { const addPolygonBtn = document.getElementById("addPolygon-btn"); if (addPolygonBtn) { addPolygonBtn.classList.remove("activeBtn"); } self.map.off("click", MapHandler.polygonAddPoint); } catch {} try { const saveBtn = document.getElementById("save-btn"); if (saveBtn) { saveBtn.classList.remove("activeBtn"); } } catch {} try { const clearBtn = document.getElementById("clear-btn"); if (clearBtn) { clearBtn.classList.remove("activeBtn"); } } catch {} try { const resetBtn = document.getElementById("restore-btn"); if (resetBtn) { resetBtn.classList.remove("activeBtn"); } } catch {} try { const menuBtn = document.getElementById("menu-btn"); if (menuBtn) { menuBtn.classList.remove("activeBtn"); } } catch {} self.overlays.polyline.clearPoints(); } } static addMarker(e: any): void { const self = MapHandler.instance; if (self) { self.modals.cancel.setVisible(false); self.modals.createOverlay.setState(OverlayType.POINT, { latlng: e.latlng, map: self.map, overlays: self.overlays, }); MapHandler.resetMapClick(); self.modals.createOverlay.setVisible(true); } } static editOverlay(overlay: OverlayBase, type: OverlayType): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); self.modals.createOverlay.setState(type, { self: overlay, map: self.map, overlays: self.overlays, }); MapHandler.resetMapClick(); self.modals.createOverlay.setVisible(true); } } static addCircle(e: any): void { const self = MapHandler.instance; if (self) { self.modals.cancel.setVisible(false); self.modals.createOverlay.setState(OverlayType.CIRCLE, { latlng: e.latlng, map: self.map, overlays: self.overlays, }); MapHandler.resetMapClick(); self.modals.createOverlay.setVisible(true); } } static addPolygon(e: any): void { const self = MapHandler.instance; if (self) { self.modals.okCancel.setVisible(false); self.modals.createOverlay.setState(OverlayType.POLYGON, { points: self.overlays.polyline.points, map: self.map, overlays: self.overlays, }); MapHandler.resetMapClick(); self.overlays.polyline.clearPoints(); self.modals.createOverlay.setVisible(true); } } static circleCollect(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); MapHandler.resetMapClick(); (e.target as HTMLElement).classList.add("activeBtn"); self.map.on("click", MapHandler.addCircle); const cancelBtn = self.modals.cancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = ()=> { self.modals.closeAll(); MapHandler.resetMapClick(); }; } self.modals.cancel.setMsg("Placing circle"); self.modals.cancel.setVisible(true); } } static markerCollect(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); MapHandler.resetMapClick(); (e.target as HTMLElement).classList.add("activeBtn"); self.map.on("click", MapHandler.addMarker); const cancelBtn = self.modals.cancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = ()=> { self.modals.closeAll(); MapHandler.resetMapClick(); }; } self.modals.cancel.setMsg("Placing marker"); self.modals.cancel.setVisible(true); } } static polygonCollect(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); MapHandler.resetMapClick(); (e.target as HTMLElement).classList.add("activeBtn"); // show cancel -- on cancel, clear polyline and reset map handling const cancelBtn = self.modals.cancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = MapHandler.polygonClearPoints; } self.modals.cancel.setMsg("Creating path"); self.modals.cancel.setVisible(true); self.map.on("click", MapHandler.polygonAddPoint); } } static polygonClearPoints(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); self.overlays.polyline.clearPoints(); MapHandler.resetMapClick(); } } static polygonAddPoint(e: any): void { const self = MapHandler.instance; if (self) { self.overlays.polyline.insertPoint(e.latlng); if (self.overlays.polyline.numPoints() >= 3) { self.modals.cancel.setVisible(false); const okBtn = self.modals.okCancel.okBtn(); if (okBtn) { okBtn.onclick = MapHandler.addPolygon; } const cancelBtn = self.modals.okCancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = MapHandler.polygonClearPoints; } self.modals.okCancel.setMsg("Creating path"); self.modals.okCancel.setVisible(true); } } } static overlaySave(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); MapHandler.resetMapClick(); (e.target as HTMLElement).classList.add("activeBtn"); self.modals.okCancel.setMsg("Save current map overlays?"); const okBtn = self.modals.okCancel.okBtn(); if (okBtn) { okBtn.onclick = ()=> { OverlayState.save(self.overlays); self.modals.okCancel.setVisible(false); MapHandler.resetMapClick(); self.modals.info.setMsg("Save complete"); self.modals.info.setVisible(true); } } const cancelBtn = self.modals.okCancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = () => { self.modals.okCancel.setVisible(false); MapHandler.resetMapClick(); } } self.modals.okCancel.setVisible(true); } } static overlayReset(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); MapHandler.resetMapClick(); (e.target as HTMLElement).classList.add("activeBtn"); self.modals.okCancel.setMsg("Restore overlays from saved data?"); const okBtn = self.modals.okCancel.okBtn(); if (okBtn) { okBtn.onclick = ()=> { self.overlays = OverlayState.clear(self.overlays, self.map); self.overlays = OverlayState.load(); self.overlays.markers.forEach(m=>m.add(self.map)); self.overlays.circles.forEach(m=>m.add(self.map)); self.overlays.paths.forEach(m=>m.add(self.map)); self.overlays.polyline.add(self.map); self.modals.okCancel.setVisible(false); MapHandler.resetMapClick(); self.modals.info.setMsg("Restored"); self.modals.info.setVisible(true); } } const cancelBtn = self.modals.okCancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = () => { self.modals.okCancel.setVisible(false); MapHandler.resetMapClick(); } } self.modals.okCancel.setVisible(true); } } static overlayClear(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); MapHandler.resetMapClick(); (e.target as HTMLElement).classList.add("activeBtn"); self.modals.okCancel.setMsg("Clear all map overlays (will not affect saved data)?"); const okBtn = self.modals.okCancel.okBtn(); if (okBtn) { okBtn.onclick = ()=> { self.overlays = OverlayState.clear(self.overlays, self.map); MapHandler.resetMapClick(); self.modals.okCancel.setVisible(false); } } const cancelBtn = self.modals.okCancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = () => { MapHandler.resetMapClick(); self.modals.okCancel.setVisible(false); } } self.modals.okCancel.setVisible(true); } } static swapTiles(e: any): void { const self = MapHandler.instance; if (self) { if (TileLayerWrapper.getActiveLayer() == "satelliteLayer") { TileLayerWrapper.enableOnly("streetLayer", self.map); } else { TileLayerWrapper.enableOnly("satelliteLayer", self.map); } } } static setHome(e: any): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); const okCancel = self.modals.okCancel; okCancel.setMsg("Set Home to current coordinates?"); const okBtn = okCancel.okBtn(); if (okBtn) { okBtn.onclick = (e: any) => { okCancel.setVisible(false); localStorage.setItem("home", JSON.stringify(self.map.getCenter() as Point)); const info = self.modals.info; info.setMsg("Home coordinates set"); info.setVisible(true); } } const cancelBtn = okCancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = () => { okCancel.setVisible(false); } } okCancel.setVisible(true); } } static goHome(e: any): void { const self = MapHandler.instance; if (self) { const homeData = localStorage.getItem("home"); if (homeData) { const home = JSON.parse(homeData); if (home) { self.map.setView(home, 13); } } } } static toggleMenu(e: any): void { const self = MapHandler.instance; if (self) { const visible = self.modals.overlayMgr.visible(); self.modals.closeAll(); MapHandler.resetMapClick(); self.modals.overlayMgr.setVisible(!visible); if (!visible) { (e.target as HTMLElement).classList.add("activeBtn"); } } } static confirmDelete(overlay: OverlayBase): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); self.modals.okCancel.setMsg(`Delete "${overlay.name}"?`); const okBtn = self.modals.okCancel.okBtn(); if (okBtn) { okBtn.onclick = () => { self.modals.closeAll(); overlay.remove(self.map); self.modals.info.setMsg(`"${overlay.name}" deleted`); self.modals.info.setVisible(true); } } const cancelBtn = self.modals.okCancel.cancelBtn(); if (cancelBtn) { cancelBtn.onclick = () => { self.modals.closeAll(); } } self.modals.okCancel.setVisible(true); } } static exportSingle(overlay: OverlayBase): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); self.modals.importExport.setTitle("Export Overlay"); self.modals.importExport.setErrMsg("", false); const okBtn = self.modals.importExport.okBtn(); if (okBtn) { okBtn.innerText = "Copy to clipboard"; okBtn.onclick = () => { self.modals.importExport.copyTextArea(); self.modals.closeAll(); self.modals.info.setMsg("Copied the data to the clipboard"); self.modals.info.setVisible(true); } } self.modals.importExport.setTextArea(OverlayState.exportSingle(overlay), true); self.modals.importExport.setVisible(true); } } static exportAll(): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); self.modals.importExport.setTitle("Export All Overlays"); self.modals.importExport.setErrMsg("", false); const okBtn = self.modals.importExport.okBtn(); if (okBtn) { okBtn.innerText = "Copy to clipboard"; okBtn.onclick = () => { self.modals.importExport.copyTextArea(); self.modals.closeAll(); self.modals.info.setMsg("Copied the data to the clipboard"); self.modals.info.setVisible(true); } } self.modals.importExport.setTextArea(OverlayState.export(self.overlays), true); self.modals.importExport.setVisible(true); } } static import(): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); self.modals.importExport.setTitle("Import Overlay Data"); self.modals.importExport.setErrMsg("", false); self.modals.importExport.setTextArea("", false); const okBtn = self.modals.importExport.okBtn(); if (okBtn) { okBtn.innerText = "Import"; okBtn.onclick = () => { MapHandler.doImport(self.modals.importExport.getText()); } } self.modals.importExport.setVisible(true); } } static doImport(data: string): void { const self = MapHandler.instance; if (self) { if (OverlayState.importWrapper(data, self.overlays, self.map)) { self.modals.closeAll(); self.modals.info.setMsg("Import successful"); self.modals.info.setVisible(true); } else { self.modals.importExport.setErrMsg("The data was malformed — please check that it is valid JSON exported from ONYX/scry", true); } } } static closeImportExport(): void { const self = MapHandler.instance; if (self) { self.modals.closeAll(); } } }