Page MenuHomeSealhub

map-with-pins.stimulus.ts
No OneTemporary

map-with-pins.stimulus.ts

/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/consistent-type-assertions */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Controller } from "stimulus";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
import { addJS } from "@sealcode/add-to-head";
declare global {
interface Window {
L: typeof import("leaflet");
}
}
type Pin = {
title: string;
address: string;
coordinates: string;
button: { link: string; text: string };
};
function parseCoords(s: string): [number, number] {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const result = s.split(",").map((x) => parseFloat(x.trim())) as [number, number];
return result;
}
function decodeHTMLEntities(text: string) {
const entities = [
["amp", "&"],
["apos", "'"],
["#x27", "'"],
["#x2F", "/"],
["#39", "'"],
["#47", "/"],
["lt", "<"],
["gt", ">"],
["nbsp", " "],
["quot", '"'],
];
for (let i = 0, max = entities.length; i < max; ++i)
text = text.replace(new RegExp("&" + entities[i][0] + ";", "g"), entities[i][1]);
return text;
}
export default class MapWithPins extends Controller {
id: string;
map: L.Map;
initiated = false;
resizeObserver: ResizeObserver;
static values = {
pins: String,
};
async connect() {
if (this.initiated) {
this.map.remove();
}
await addJS("https://unpkg.com/leaflet@1.9.4/dist/leaflet.js");
this.initiateMap();
}
disconnect() {
this.map.remove();
this.initiated = false;
this.resizeObserver?.disconnect();
}
initiateMap() {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
this.map = window.L.map(this.element as HTMLElement, {
dragging: "ontouchstart" in document.documentElement,
scrollWheelZoom: false,
});
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver(() => {
this.map.invalidateSize();
});
}
this.resizeObserver?.observe(this.element);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
window.L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution:
'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(this.map);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const pins = JSON.parse(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
decodeHTMLEntities(this.element.getAttribute("data-map-with-pins-pins-value"))
) as Pin[];
pins.forEach((pin) => this.addPin(pin));
this.initiated = true;
}
async pinsValueChanged() {
if (this.initiated) {
await this.connect();
}
}
addPin(pin: Pin) {
const pinIcon = window.L.icon({
iconUrl: "/pin-icon.svg",
iconSize: [29, 41],
iconAnchor: [14, 40],
popupAnchor: [-3, 14],
});
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
window.L.marker(parseCoords(pin.coordinates), {
icon: pinIcon,
}).addTo(this.map);
window.L.popup({
closeButton: false,
autoClose: false,
closeOnEscapeKey: false,
closeOnClick: false,
className: "popup",
offset: [0, -32],
})
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
.setLatLng(parseCoords(pin.coordinates))
.setContent(
/* HTML */ `<div class="popup-content">
<p class="title">${pin.title}</p>
<p class="address">${pin.address}</p>
<a class="button" href="${pin.button.link}"> ${pin.button.text} </a>
</div> `
)
.addTo(this.map);
this.map.setView(parseCoords(pin.coordinates), 13);
}
}

File Metadata

Mime Type
text/html
Expires
Mon, Feb 24, 22:52 (1 d, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
610353
Default Alt Text
map-with-pins.stimulus.ts (3 KB)

Event Timeline