Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
89ea0a2730 Replace Leaflet with MapLibre GL for geo map collection
- Remove leaflet, leaflet-gpx, @maplibre/maplibre-gl-leaflet and related @types packages
- Add maplibre-gl 5.18.0 as dependency
- Rewrite map.tsx to use MapLibre GL directly (native vector+raster support)
- Rewrite marker.tsx with MapLibre GL HTML markers
- Rewrite GPX track rendering using DOMParser + GeoJSON layers
- Update api.ts, context_menu.ts, index.tsx, map_layer.ts to use GeoMouseEvent
- Update link_context_menu.ts to remove LeafletMouseEvent dependency
- Update types-lib.d.ts to remove Leaflet module augmentation
- Update CSS for MapLibre GL marker classes
- Replace Leaflet marker PNG with inline SVG marker pin

Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2026-02-12 20:26:37 +00:00
copilot-swe-agent[bot]
d847d966bc Initial plan 2026-02-12 20:16:25 +00:00
12 changed files with 468 additions and 375 deletions

View File

@@ -23,7 +23,6 @@
"@fullcalendar/list": "6.1.20",
"@fullcalendar/multimonth": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
@@ -51,9 +50,8 @@
"jsplumb": "2.15.6",
"katex": "0.16.28",
"knockout": "3.5.1",
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"maplibre-gl": "5.18.0",
"marked": "17.0.2",
"mermaid": "11.12.2",
"mind-elixir": "5.8.0",
@@ -72,8 +70,6 @@
"@prefresh/vite": "2.4.11",
"@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.33",
"@types/leaflet": "1.9.21",
"@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12",
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",

View File

@@ -1,4 +1,4 @@
import type { LeafletMouseEvent } from "leaflet";
import type { GeoMouseEvent } from "../widgets/collections/geomap/map.js";
import appContext, { type CommandNames } from "../components/app_context.js";
import { t } from "../services/i18n.js";
@@ -16,7 +16,7 @@ function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewS
});
}
function getItems(e: ContextMenuEvent | LeafletMouseEvent): MenuItem<CommandNames>[] {
function getItems(e: ContextMenuEvent | GeoMouseEvent): MenuItem<CommandNames>[] {
const ntxId = getNtxId(e);
const isMobileSplitOpen = isMobile() && appContext.tabManager.getNoteContextById(ntxId).getMainContext().getSubContexts().length > 1;
@@ -28,7 +28,7 @@ function getItems(e: ContextMenuEvent | LeafletMouseEvent): MenuItem<CommandName
];
}
function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEvent | LeafletMouseEvent, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEvent | GeoMouseEvent, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
if (!hoistedNoteId) {
hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId ?? null;
}
@@ -52,7 +52,7 @@ function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEv
return false;
}
function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
function getNtxId(e: ContextMenuEvent | GeoMouseEvent) {
if (utils.isDesktop()) {
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
if (!subContexts) return null;

View File

@@ -32,26 +32,6 @@ declare module "katex/contrib/auto-render" {
export default renderMathInElement;
}
import * as L from "leaflet";
declare module "leaflet" {
interface GPXMarker {
startIcon?: DivIcon | Icon | string | undefined;
endIcon?: DivIcon | Icon | string | undefined;
wptIcons?: {
[key: string]: DivIcon | Icon | string;
};
wptTypeIcons?: {
[key: string]: DivIcon | Icon | string;
};
pointMatchers?: Array<{ regex: RegExp; icon: DivIcon | Icon | string}>;
}
interface GPXOptions {
markers?: GPXMarker | undefined;
}
}
declare global {
interface Navigator {
/** Returns a boolean indicating whether the browser is running in standalone mode. Available on Apple's iOS Safari only. */

View File

@@ -89,7 +89,7 @@
/* #endregion */
/* #region Geo map buttons */
.leaflet-pane {
.maplibregl-canvas-container {
z-index: 50;
}
/* #endregion */

View File

@@ -1,4 +1,4 @@
import type { LatLng, LeafletMouseEvent } from "leaflet";
import type { GeoMouseEvent } from "./map";
import { LOCATION_ATTRIBUTE } from ".";
import attributes from "../../../services/attributes";
import { prompt } from "../../../services/dialog";
@@ -8,12 +8,12 @@ import { CreateChildrenResponse } from "@triliumnext/commons";
const CHILD_NOTE_ICON = "bx bx-pin";
export async function moveMarker(noteId: string, latLng: LatLng | null) {
export async function moveMarker(noteId: string, latLng: { lat: number; lng: number } | null) {
const value = latLng ? [latLng.lat, latLng.lng].join(",") : "";
await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
}
export async function createNewNote(noteId: string, e: LeafletMouseEvent) {
export async function createNewNote(noteId: string, e: GeoMouseEvent) {
const title = await prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
if (title?.trim()) {

View File

@@ -1,4 +1,4 @@
import type { LatLng, LeafletMouseEvent } from "leaflet";
import type { GeoMouseEvent } from "./map.js";
import appContext, { type CommandMappings } from "../../../components/app_context.js";
import contextMenu, { type MenuItem } from "../../../menus/context_menu.js";
import linkContextMenu from "../../../menus/link_context_menu.js";
@@ -8,7 +8,7 @@ import { createNewNote } from "./api.js";
import { copyTextWithToast } from "../../../services/clipboard_ext.js";
import link from "../../../services/link.js";
export default function openContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) {
export default function openContextMenu(noteId: string, e: GeoMouseEvent, isEditable: boolean) {
let items: MenuItem<keyof CommandMappings>[] = [
...buildGeoLocationItem(e),
{ kind: "separator" },
@@ -44,7 +44,7 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent, is
});
}
export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) {
export function openMapContextMenu(noteId: string, e: GeoMouseEvent, isEditable: boolean) {
let items: MenuItem<keyof CommandMappings>[] = [
...buildGeoLocationItem(e)
];
@@ -71,8 +71,8 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEdita
});
}
function buildGeoLocationItem(e: LeafletMouseEvent) {
function formatGeoLocation(latlng: LatLng, precision: number = 6) {
function buildGeoLocationItem(e: GeoMouseEvent) {
function formatGeoLocation(latlng: { lat: number; lng: number }, precision: number = 6) {
return `${latlng.lat.toFixed(precision)}, ${latlng.lng.toFixed(precision)}`;
}

View File

@@ -16,12 +16,10 @@
overflow: hidden;
}
.leaflet-pane {
z-index: 1;
}
.leaflet-top,
.leaflet-bottom {
.maplibregl-ctrl-top-left,
.maplibregl-ctrl-top-right,
.maplibregl-ctrl-bottom-left,
.maplibregl-ctrl-bottom-right {
z-index: 997 !important;
}
@@ -29,28 +27,25 @@
cursor: crosshair;
}
.geo-map-container .marker-pin {
.geo-map-container .geo-marker {
position: relative;
}
.geo-map-container .leaflet-div-icon {
position: relative;
background: transparent;
border: 0;
cursor: pointer;
overflow: visible;
}
.geo-map-container .leaflet-div-icon .icon-shadow {
position: absolute;
top: 0;
inset-inline-start: 0;
z-index: -1;
.geo-map-container .geo-marker .marker-pin {
position: relative;
}
.geo-map-container .leaflet-div-icon .tn-icon {
.geo-map-container .geo-marker .marker-pin svg {
display: block;
filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.3));
}
.geo-map-container .geo-marker .tn-icon {
position: absolute;
top: 3px;
inset-inline-start: 2px;
inset-inline-start: 4px;
background-color: white;
color: var(--light-theme-custom-color, black);
padding: 2px;
@@ -58,7 +53,7 @@
font-size: 17px;
}
.geo-map-container .leaflet-div-icon .title-label {
.geo-map-container .geo-marker .title-label {
display: block;
position: absolute;
top: 100%;
@@ -71,19 +66,19 @@
text-align: center;
text-overflow: ellipsis;
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
white-space: no-wrap;
white-space: nowrap;
overflow: hidden;
}
body[dir=rtl] .geo-map-container .leaflet-div-icon .title-label {
body[dir=rtl] .geo-map-container .geo-marker .title-label {
transform: translateX(50%);
}
.geo-map-container .leaflet-div-icon .archived {
.geo-map-container .geo-marker .archived {
opacity: 0.5;
}
.geo-map-container.dark .leaflet-div-icon .title-label {
.geo-map-container.dark .geo-marker .title-label {
color: white;
text-shadow: -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black, 1px 1px 0 black;
}

View File

@@ -1,8 +1,5 @@
import "./index.css";
import { divIcon, GPXOptions, LatLng, LeafletMouseEvent } from "leaflet";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import appContext from "../../../components/app_context";
@@ -21,9 +18,10 @@ import TouchBar, { TouchBarButton, TouchBarSlider } from "../../react/TouchBar";
import { ViewModeProps } from "../interface";
import { createNewNote, moveMarker } from "./api";
import openContextMenu, { openMapContextMenu } from "./context_menu";
import Map from "./map";
import Map, { GeoMouseEvent } from "./map";
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
import Marker, { GpxTrack } from "./marker";
import type maplibregl from "maplibre-gl";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
const DEFAULT_ZOOM = 2;
@@ -31,7 +29,7 @@ export const LOCATION_ATTRIBUTE = "geolocation";
interface MapData {
view?: {
center?: LatLng | [number, number];
center?: { lat: number; lng: number } | [number, number];
zoom?: number;
};
}
@@ -89,7 +87,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
moveMarker(noteId, null);
});
const onClick = useCallback(async (e: LeafletMouseEvent) => {
const onClick = useCallback(async (e: GeoMouseEvent) => {
if (state === State.NewNote) {
toast.closePersistent("geo-new-note");
await createNewNote(note.noteId, e);
@@ -97,13 +95,13 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
}
}, [ state ]);
const onContextMenu = useCallback((e: LeafletMouseEvent) => {
const onContextMenu = useCallback((e: GeoMouseEvent) => {
openMapContextMenu(note.noteId, e, !isReadOnly);
}, [ note.noteId, isReadOnly ]);
// Dragging
const containerRef = useRef<HTMLDivElement>(null);
const apiRef = useRef<L.Map>(null);
const apiRef = useRef<maplibregl.Map>(null);
useNoteTreeDrag(containerRef, {
dragEnabled: !isReadOnly,
dragNotEnabledMessage: {
@@ -120,15 +118,15 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
const offset = containerRef.current?.getBoundingClientRect();
const x = e.clientX - (offset?.left ?? 0);
const y = e.clientY - (offset?.top ?? 0);
const latlng = api.containerPointToLatLng([ x, y ]);
const lngLat = api.unproject([x, y]);
const targetNote = await froca.getNote(noteId, true);
const parents = targetNote?.getParentNoteIds();
if (parents?.includes(note.noteId)) {
await moveMarker(noteId, latlng);
await moveMarker(noteId, { lat: lngLat.lat, lng: lngLat.lng });
} else {
await branches.cloneNoteToParentNote(noteId, noteId);
await moveMarker(noteId, latlng);
await moveMarker(noteId, { lat: lngLat.lat, lng: lngLat.lng });
}
}
});
@@ -201,8 +199,8 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
const [ archived ] = useNoteLabelBoolean(note, "archived");
const title = useNoteProperty(note, "title");
const icon = useMemo(() => {
return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, title, note.noteId, archived);
const iconHtml = useMemo(() => {
return buildIconHtml(note.getIcon(), note.getColorClass() ?? undefined, title, note.noteId, archived);
}, [ iconClass, color, title, note.noteId, archived]);
const onClick = useCallback(() => {
@@ -218,15 +216,17 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
}
}, [ note.noteId ]);
const onDragged = useCallback((newCoordinates: LatLng) => {
const onDragged = useCallback((newCoordinates: { lat: number; lng: number }) => {
moveMarker(note.noteId, newCoordinates);
}, [ note.noteId ]);
const onContextMenu = useCallback((e: LeafletMouseEvent) => openContextMenu(note.noteId, e, editable), [ note.noteId, editable ]);
const onContextMenu = useCallback((e: GeoMouseEvent) => openContextMenu(note.noteId, e, editable), [ note.noteId, editable ]);
return latLng && <Marker
coordinates={latLng}
icon={icon}
iconHtml={iconHtml}
iconSize={[25, 41]}
iconAnchor={[12, 41]}
draggable={editable}
onMouseDown={onMouseDown}
onDragged={editable ? onDragged : undefined}
@@ -254,40 +254,40 @@ function NoteGpxTrack({ note }: { note: FNote }) {
const color = useNoteLabel(note, "color");
const iconClass = useNoteLabel(note, "iconClass");
const options = useMemo<GPXOptions>(() => ({
markers: {
startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title),
endIcon: buildIcon("bxs-flag-checkered"),
wptIcons: {
"": buildIcon("bx bx-pin")
}
},
polyline_options: {
color: note.getLabelValue("color") ?? "blue"
}
}), [ color, iconClass ]);
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />;
const trackColor = useMemo(() => note.getLabelValue("color") ?? "blue", [ color ]);
const startIconHtml = useMemo(() => buildIconHtml(note.getIcon(), note.getColorClass() ?? undefined, note.title), [ iconClass, color ]);
const endIconHtml = useMemo(() => buildIconHtml("bxs-flag-checkered"), [ ]);
const waypointIconHtml = useMemo(() => buildIconHtml("bx bx-pin"), [ ]);
return xmlString && <GpxTrack
gpxXmlString={xmlString}
trackColor={trackColor}
startIconHtml={startIconHtml}
endIconHtml={endIconHtml}
waypointIconHtml={waypointIconHtml}
/>;
}
function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
// SVG marker pin shape (replaces the Leaflet marker PNG).
const MARKER_SVG = `<svg width="25" height="41" viewBox="0 0 25 41" xmlns="http://www.w3.org/2000/svg">` +
`<path d="M12.5 0C5.6 0 0 5.6 0 12.5C0 21.9 12.5 41 12.5 41S25 21.9 25 12.5C25 5.6 19.4 0 12.5 0Z" fill="#2A81CB" />` +
`<circle cx="12.5" cy="12.5" r="8" fill="white" />` +
`</svg>`;
function buildIconHtml(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
let html = /*html*/`\
<img class="icon" src="${markerIcon}" />
<img class="icon-shadow" src="${markerIconShadow}" />
<span class="bx ${bxIconClass} ${colorClass ?? ""}"></span>
<div class="marker-pin">${MARKER_SVG}</div>
<span class="bx ${bxIconClass} tn-icon ${colorClass ?? ""}"></span>
<span class="title-label">${title ?? ""}</span>`;
if (noteIdLink) {
html = `<div data-href="#root/${noteIdLink}" class="${archived ? "archived" : ""}">${html}</div>`;
}
return divIcon({
html,
iconSize: [25, 41],
iconAnchor: [12, 41]
});
return html;
}
function GeoMapTouchBar({ state, map }: { state: State, map: L.Map | null | undefined }) {
function GeoMapTouchBar({ state, map }: { state: State, map: maplibregl.Map | null | undefined }) {
const [ currentZoom, setCurrentZoom ] = useState<number>();
const parentComponent = useContext(ParentComponent);
@@ -299,7 +299,7 @@ function GeoMapTouchBar({ state, map }: { state: State, map: L.Map | null | unde
}
map.on("zoom", onZoomChanged);
return () => map.off("zoom", onZoomChanged);
return () => { map.off("zoom", onZoomChanged); };
}, [ map ]);
return map && currentZoom && (

View File

@@ -1,139 +1,210 @@
import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
import L, { control, LatLng, Layer, LeafletMouseEvent } from "leaflet";
import "leaflet/dist/leaflet.css";
import { useEffect, useImperativeHandle, useRef } from "preact/hooks";
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { MAP_LAYERS } from "./map_layer";
import { ComponentChildren, createContext, RefObject } from "preact";
import { useElementSize, useSyncedRef } from "../../react/hooks";
export const ParentMap = createContext<L.Map | null>(null);
export interface GeoMouseEvent {
latlng: { lat: number; lng: number };
originalEvent: MouseEvent;
}
export const ParentMap = createContext<maplibregl.Map | null>(null);
interface MapProps {
apiRef?: RefObject<L.Map | null>;
apiRef?: RefObject<maplibregl.Map | null>;
containerRef?: RefObject<HTMLDivElement>;
coordinates: LatLng | [number, number];
coordinates: { lat: number; lng: number } | [number, number];
zoom: number;
layerName: string;
viewportChanged: (coordinates: LatLng, zoom: number) => void;
viewportChanged: (coordinates: { lat: number; lng: number }, zoom: number) => void;
children: ComponentChildren;
onClick?: (e: LeafletMouseEvent) => void;
onContextMenu?: (e: LeafletMouseEvent) => void;
onClick?: (e: GeoMouseEvent) => void;
onContextMenu?: (e: GeoMouseEvent) => void;
onZoom?: () => void;
scale: boolean;
}
function toMapLibreEvent(e: maplibregl.MapMouseEvent): GeoMouseEvent {
return {
latlng: { lat: e.lngLat.lat, lng: e.lngLat.lng },
originalEvent: e.originalEvent
};
}
export default function Map({ coordinates, zoom, layerName, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) {
const mapRef = useRef<L.Map>(null);
const mapRef = useRef<maplibregl.Map>(null);
const containerRef = useSyncedRef<HTMLDivElement>(_containerRef);
useImperativeHandle(apiRef ?? null, () => mapRef.current);
// Initialize the map.
useEffect(() => {
if (!containerRef.current) return;
const mapInstance = L.map(containerRef.current, {
worldCopyJump: false,
maxBounds: [
[-90, -180],
[90, 180]
],
minZoom: 2
const layerData = MAP_LAYERS[layerName];
let style: maplibregl.StyleSpecification | string;
if (layerData.type === "vector") {
style = typeof layerData.style === "string"
? layerData.style
: layerData.styleFallback;
} else {
style = {
version: 8,
sources: {
"raster-tiles": {
type: "raster",
tiles: [layerData.url],
tileSize: 256,
attribution: layerData.attribution
}
},
layers: [
{
id: "raster-layer",
type: "raster",
source: "raster-tiles"
}
]
};
}
const center = Array.isArray(coordinates)
? [coordinates[1], coordinates[0]] as [number, number]
: [coordinates.lng, coordinates.lat] as [number, number];
const mapInstance = new maplibregl.Map({
container: containerRef.current,
style,
center,
zoom,
minZoom: 2,
maxBounds: [[-180, -90], [180, 90]]
});
mapRef.current = mapInstance;
// Load async vector style if needed.
if (layerData.type === "vector" && typeof layerData.style !== "string") {
layerData.style().then(asyncStyle => {
mapInstance.setStyle(asyncStyle as maplibregl.StyleSpecification);
});
}
return () => {
mapInstance.off();
mapInstance.remove();
mapRef.current = null;
};
}, []);
// Load the layer asynchronously.
const [ layer, setLayer ] = useState<Layer>();
useEffect(() => {
async function load() {
const layerData = MAP_LAYERS[layerName];
if (layerData.type === "vector") {
const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style());
await import("@maplibre/maplibre-gl-leaflet");
setLayer(L.maplibreGL({
style: style as any
}));
} else {
setLayer(L.tileLayer(layerData.url, {
attribution: layerData.attribution,
detectRetina: true,
noWrap: true
}));
}
}
load();
}, [ layerName ]);
// Attach layer to the map.
// React to layer changes.
useEffect(() => {
const map = mapRef.current;
const layerToAdd = layer;
if (!map || !layerToAdd) return;
layerToAdd.addTo(map);
return () => layerToAdd.removeFrom(map);
}, [ mapRef, layer ]);
if (!map) return;
const layerData = MAP_LAYERS[layerName];
if (layerData.type === "vector") {
if (typeof layerData.style === "string") {
map.setStyle(layerData.style);
} else {
layerData.style().then(asyncStyle => {
map.setStyle(asyncStyle as maplibregl.StyleSpecification);
});
}
} else {
map.setStyle({
version: 8,
sources: {
"raster-tiles": {
type: "raster",
tiles: [layerData.url],
tileSize: 256,
attribution: layerData.attribution
}
},
layers: [
{
id: "raster-layer",
type: "raster",
source: "raster-tiles"
}
]
});
}
}, [ layerName ]);
// React to coordinate changes.
useEffect(() => {
if (!mapRef.current) return;
mapRef.current.setView(coordinates, zoom);
}, [ mapRef, coordinates, zoom ]);
const center = Array.isArray(coordinates)
? [coordinates[1], coordinates[0]] as [number, number]
: [coordinates.lng, coordinates.lat] as [number, number];
mapRef.current.setCenter(center);
mapRef.current.setZoom(zoom);
}, [ coordinates, zoom ]);
// Viewport callback.
useEffect(() => {
const map = mapRef.current;
if (!map) return;
const updateFn = () => viewportChanged(map.getBounds().getCenter(), map.getZoom());
const updateFn = () => {
const center = map.getCenter();
viewportChanged({ lat: center.lat, lng: center.lng }, map.getZoom());
};
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
return () => {
map.off("moveend", updateFn);
map.off("zoomend", updateFn);
};
}, [ mapRef, viewportChanged ]);
}, [ viewportChanged ]);
useEffect(() => {
if (onClick && mapRef.current) {
mapRef.current.on("click", onClick);
return () => mapRef.current?.off("click", onClick);
}
}, [ mapRef, onClick ]);
const map = mapRef.current;
if (!onClick || !map) return;
const handler = (e: maplibregl.MapMouseEvent) => onClick(toMapLibreEvent(e));
map.on("click", handler);
return () => { map.off("click", handler); };
}, [ onClick ]);
useEffect(() => {
if (onContextMenu && mapRef.current) {
mapRef.current.on("contextmenu", onContextMenu);
return () => mapRef.current?.off("contextmenu", onContextMenu);
}
}, [ mapRef, onContextMenu ]);
const map = mapRef.current;
if (!onContextMenu || !map) return;
const handler = (e: maplibregl.MapMouseEvent) => {
e.preventDefault();
onContextMenu(toMapLibreEvent(e));
};
map.on("contextmenu", handler);
return () => { map.off("contextmenu", handler); };
}, [ onContextMenu ]);
useEffect(() => {
if (onZoom && mapRef.current) {
mapRef.current.on("zoom", onZoom);
return () => mapRef.current?.off("zoom", onZoom);
}
}, [ mapRef, onZoom ]);
const map = mapRef.current;
if (!onZoom || !map) return;
map.on("zoom", onZoom);
return () => { map.off("zoom", onZoom); };
}, [ onZoom ]);
// Scale
useEffect(() => {
const map = mapRef.current;
if (!scale || !map) return;
const scaleControl = control.scale();
scaleControl.addTo(map);
return () => scaleControl.remove();
}, [ mapRef, scale ]);
const scaleControl = new maplibregl.ScaleControl();
map.addControl(scaleControl);
return () => { map.removeControl(scaleControl); };
}, [ scale ]);
// Adapt to container size changes.
const size = useElementSize(containerRef);
useEffect(() => {
mapRef.current?.invalidateSize();
mapRef.current?.resize();
}, [ size?.width, size?.height ]);
return (

View File

@@ -5,7 +5,8 @@ export interface MapLayer {
interface VectorLayer extends MapLayer {
type: "vector";
style: string | (() => Promise<{}>)
style: string | (() => Promise<{}>);
styleFallback: {};
}
interface RasterLayer extends MapLayer {
@@ -14,6 +15,9 @@ interface RasterLayer extends MapLayer {
attribution: string;
}
// Minimal empty style used as a placeholder while the real style loads asynchronously.
const EMPTY_STYLE = { version: 8, sources: {}, layers: [] };
export const MAP_LAYERS: Record<string, VectorLayer | RasterLayer> = {
"openstreetmap": {
name: "OpenStreetMap",
@@ -24,28 +28,33 @@ export const MAP_LAYERS: Record<string, VectorLayer | RasterLayer> = {
"versatiles-colorful": {
name: "VersaTiles Colorful",
type: "vector",
style: async () => (await import("./styles/colorful/en.json")).default
style: async () => (await import("./styles/colorful/en.json")).default,
styleFallback: EMPTY_STYLE
},
"versatiles-eclipse": {
name: "VersaTiles Eclipse",
type: "vector",
style: async () => (await import("./styles/eclipse/en.json")).default,
styleFallback: EMPTY_STYLE,
isDarkTheme: true
},
"versatiles-graybeard": {
name: "VersaTiles Graybeard",
type: "vector",
style: async () => (await import("./styles/graybeard/en.json")).default
style: async () => (await import("./styles/graybeard/en.json")).default,
styleFallback: EMPTY_STYLE
},
"versatiles-neutrino": {
name: "VersaTiles Neutrino",
type: "vector",
style: async () => (await import("./styles/neutrino/en.json")).default
style: async () => (await import("./styles/neutrino/en.json")).default,
styleFallback: EMPTY_STYLE
},
"versatiles-shadow": {
name: "VersaTiles Shadow",
type: "vector",
style: async () => (await import("./styles/shadow/en.json")).default,
styleFallback: EMPTY_STYLE,
isDarkTheme: true
}
};

View File

@@ -1,71 +1,207 @@
import { useContext, useEffect } from "preact/hooks";
import { ParentMap } from "./map";
import { DivIcon, GPX, GPXOptions, Icon, LatLng, Marker as LeafletMarker, LeafletMouseEvent, marker, MarkerOptions } from "leaflet";
import "leaflet-gpx";
import { useContext, useEffect, useRef } from "preact/hooks";
import { ParentMap, GeoMouseEvent } from "./map";
import maplibregl from "maplibre-gl";
export interface MarkerProps {
coordinates: [ number, number ];
icon?: Icon | DivIcon;
iconHtml?: string;
iconSize?: [number, number];
iconAnchor?: [number, number];
onClick?: () => void;
onMouseDown?: (e: MouseEvent) => void;
onDragged?: ((newCoordinates: LatLng) => void);
onContextMenu: (e: LeafletMouseEvent) => void;
onDragged?: ((newCoordinates: { lat: number; lng: number }) => void);
onContextMenu: (e: GeoMouseEvent) => void;
draggable?: boolean;
}
export default function Marker({ coordinates, icon, draggable, onClick, onDragged, onMouseDown, onContextMenu }: MarkerProps) {
export default function Marker({ coordinates, iconHtml, iconSize, iconAnchor, draggable, onClick, onDragged, onMouseDown, onContextMenu }: MarkerProps) {
const parentMap = useContext(ParentMap);
const markerRef = useRef<maplibregl.Marker>(null);
useEffect(() => {
if (!parentMap) return;
const options: MarkerOptions = { icon };
if (draggable) {
options.draggable = true;
options.autoPan = true;
options.autoPanSpeed = 5;
const el = document.createElement("div");
el.className = "geo-marker";
if (iconHtml) {
el.innerHTML = iconHtml;
}
if (iconSize) {
el.style.width = `${iconSize[0]}px`;
el.style.height = `${iconSize[1]}px`;
}
const newMarker = marker(coordinates, options);
const newMarker = new maplibregl.Marker({
element: el,
draggable: !!draggable,
anchor: "bottom"
})
.setLngLat([coordinates[1], coordinates[0]])
.addTo(parentMap);
markerRef.current = newMarker;
if (onClick) {
newMarker.on("click", () => onClick());
el.addEventListener("click", (e) => {
e.stopPropagation();
onClick();
});
}
if (onMouseDown) {
newMarker.on("mousedown", e => onMouseDown(e.originalEvent));
el.addEventListener("mousedown", (e) => {
if (e.button === 1) {
e.stopPropagation();
onMouseDown(e);
}
});
}
if (onDragged) {
newMarker.on("moveend", e => {
const coordinates = (e.target as LeafletMarker).getLatLng();
onDragged(coordinates);
newMarker.on("dragend", () => {
const lngLat = newMarker.getLngLat();
onDragged({ lat: lngLat.lat, lng: lngLat.lng });
});
}
if (onContextMenu) {
newMarker.on("contextmenu", e => onContextMenu(e))
el.addEventListener("contextmenu", (e) => {
e.stopPropagation();
e.preventDefault();
const lngLat = newMarker.getLngLat();
onContextMenu({
latlng: { lat: lngLat.lat, lng: lngLat.lng },
originalEvent: e
});
});
}
newMarker.addTo(parentMap);
return () => {
newMarker.remove();
markerRef.current = null;
};
}, [ parentMap, coordinates, onMouseDown, onDragged, iconHtml ]);
return () => newMarker.removeFrom(parentMap);
}, [ parentMap, coordinates, onMouseDown, onDragged, icon ]);
return (<div />)
return (<div />);
}
export function GpxTrack({ gpxXmlString, options }: { gpxXmlString: string, options: GPXOptions }) {
export interface GpxTrackProps {
gpxXmlString: string;
trackColor?: string;
startIconHtml?: string;
endIconHtml?: string;
waypointIconHtml?: string;
}
export function GpxTrack({ gpxXmlString, trackColor, startIconHtml, endIconHtml, waypointIconHtml }: GpxTrackProps) {
const parentMap = useContext(ParentMap);
useEffect(() => {
if (!parentMap) return;
const track = new GPX(gpxXmlString, options);
track.addTo(parentMap);
const markers: maplibregl.Marker[] = [];
const sourceId = `gpx-source-${Math.random().toString(36).slice(2)}`;
const layerId = `gpx-layer-${sourceId}`;
return () => track.removeFrom(parentMap);
}, [ parentMap, gpxXmlString, options ]);
function addGpxToMap() {
const parser = new DOMParser();
const gpxDoc = parser.parseFromString(gpxXmlString, "application/xml");
// Parse tracks.
const coordinates: [number, number][] = [];
const trackPoints = gpxDoc.querySelectorAll("trkpt, rtept");
for (const pt of trackPoints) {
const lat = parseFloat(pt.getAttribute("lat") ?? "0");
const lon = parseFloat(pt.getAttribute("lon") ?? "0");
coordinates.push([lon, lat]);
}
// Add GeoJSON line for the track.
if (coordinates.length > 0) {
parentMap.addSource(sourceId, {
type: "geojson",
data: {
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates
}
}
});
parentMap.addLayer({
id: layerId,
type: "line",
source: sourceId,
paint: {
"line-color": trackColor ?? "blue",
"line-width": 3
}
});
// Start marker
if (startIconHtml) {
const startEl = document.createElement("div");
startEl.className = "geo-marker";
startEl.innerHTML = startIconHtml;
const startMarker = new maplibregl.Marker({ element: startEl, anchor: "bottom" })
.setLngLat(coordinates[0])
.addTo(parentMap);
markers.push(startMarker);
}
// End marker
if (endIconHtml && coordinates.length > 1) {
const endEl = document.createElement("div");
endEl.className = "geo-marker";
endEl.innerHTML = endIconHtml;
const endMarker = new maplibregl.Marker({ element: endEl, anchor: "bottom" })
.setLngLat(coordinates[coordinates.length - 1])
.addTo(parentMap);
markers.push(endMarker);
}
}
// Parse waypoints.
const waypoints = gpxDoc.querySelectorAll("wpt");
for (const wpt of waypoints) {
const lat = parseFloat(wpt.getAttribute("lat") ?? "0");
const lon = parseFloat(wpt.getAttribute("lon") ?? "0");
if (waypointIconHtml) {
const wptEl = document.createElement("div");
wptEl.className = "geo-marker";
wptEl.innerHTML = waypointIconHtml;
const wptMarker = new maplibregl.Marker({ element: wptEl, anchor: "bottom" })
.setLngLat([lon, lat])
.addTo(parentMap);
markers.push(wptMarker);
}
}
}
if (parentMap.isStyleLoaded()) {
addGpxToMap();
} else {
parentMap.once("style.load", addGpxToMap);
}
return () => {
for (const m of markers) {
m.remove();
}
try {
if (parentMap.getLayer(layerId)) {
parentMap.removeLayer(layerId);
}
if (parentMap.getSource(sourceId)) {
parentMap.removeSource(sourceId);
}
} catch {
// Map may be already removed.
}
};
}, [ parentMap, gpxXmlString, trackColor, startIconHtml, endIconHtml, waypointIconHtml ]);
return <div />;
}

232
pnpm-lock.yaml generated
View File

@@ -200,9 +200,6 @@ importers:
'@fullcalendar/timegrid':
specifier: 6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
'@maplibre/maplibre-gl-leaflet':
specifier: 0.1.3
version: 0.1.3(@types/leaflet@1.9.21)(leaflet@1.9.4)(maplibre-gl@5.6.1)
'@mermaid-js/layout-elk':
specifier: 0.2.0
version: 0.2.0(mermaid@11.12.2)
@@ -284,12 +281,9 @@ importers:
knockout:
specifier: 3.5.1
version: 3.5.1
leaflet:
specifier: 1.9.4
version: 1.9.4
leaflet-gpx:
specifier: 2.2.0
version: 2.2.0
maplibre-gl:
specifier: 5.18.0
version: 5.18.0
mark.js:
specifier: 8.11.1
version: 8.11.1
@@ -342,12 +336,6 @@ importers:
'@types/jquery':
specifier: 3.5.33
version: 3.5.33
'@types/leaflet':
specifier: 1.9.21
version: 1.9.21
'@types/leaflet-gpx':
specifier: 1.3.8
version: 1.3.8
'@types/mark.js':
specifier: 8.11.12
version: 8.11.12
@@ -3990,8 +3978,8 @@ packages:
resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==}
engines: {node: '>= 0.6'}
'@mapbox/point-geometry@0.1.0':
resolution: {integrity: sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==}
'@mapbox/point-geometry@1.1.0':
resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==}
'@mapbox/tiny-sdf@2.0.7':
resolution: {integrity: sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==}
@@ -3999,24 +3987,26 @@ packages:
'@mapbox/unitbezier@0.0.1':
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
'@mapbox/vector-tile@1.3.1':
resolution: {integrity: sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==}
'@mapbox/vector-tile@2.0.4':
resolution: {integrity: sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==}
'@mapbox/whoots-js@3.1.0':
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
engines: {node: '>=6.0.0'}
'@maplibre/maplibre-gl-leaflet@0.1.3':
resolution: {integrity: sha512-9+hp1PSJcxuuj5/Zta9zbQ8+ZvN4doWXPtlY7ikNtUZY1VbkamY0uTqzHp9kxRPqpgeKGrI7MjzXvwzU88wWCw==}
peerDependencies:
'@types/leaflet': ^1.9.0
leaflet: ^1.9.3
maplibre-gl: ^2.4.0 || ^3.3.1 || ^4.3.2 || ^5.0.0
'@maplibre/geojson-vt@5.0.4':
resolution: {integrity: sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ==}
'@maplibre/maplibre-gl-style-spec@23.3.0':
resolution: {integrity: sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==}
'@maplibre/maplibre-gl-style-spec@24.4.1':
resolution: {integrity: sha512-UKhA4qv1h30XT768ccSv5NjNCX+dgfoq2qlLVmKejspPcSQTYD4SrVucgqegmYcKcmwf06wcNAa/kRd0NHWbUg==}
hasBin: true
'@maplibre/mlt@1.1.6':
resolution: {integrity: sha512-rgtY3x65lrrfXycLf6/T22ZnjTg5WgIOsptOIoCaMZy4O4UAKTyZlYY0h6v8le721pTptF94U65yMDQkug+URw==}
'@maplibre/vt-pbf@4.2.1':
resolution: {integrity: sha512-IxZBGq/+9cqf2qdWlFuQ+ZfoMhWpxDUGQZ/poPHOJBvwMUT1GuxLo6HgYTou+xxtsOsjfbcjI8PZaPCtmt97rA==}
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
@@ -5794,9 +5784,6 @@ packages:
'@types/fs-extra@9.0.13':
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
'@types/geojson-vt@3.2.5':
resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==}
'@types/geojson@7946.0.16':
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
@@ -5836,12 +5823,6 @@ packages:
'@types/keyv@3.1.4':
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
'@types/leaflet-gpx@1.3.8':
resolution: {integrity: sha512-woIh3APM4FbrEQ+go3yaa4k5j4yn49YLVa1xfSB+T5aYwJn+O3pYhBBQvuxQJW68jpjcaAX/PTJRJLFJ+XT6ow==}
'@types/leaflet@1.9.21':
resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==}
'@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
@@ -5851,12 +5832,6 @@ packages:
'@types/luxon@3.6.2':
resolution: {integrity: sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==}
'@types/mapbox__point-geometry@0.1.4':
resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==}
'@types/mapbox__vector-tile@1.3.4':
resolution: {integrity: sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==}
'@types/mark.js@8.11.12':
resolution: {integrity: sha512-244ZnaIBpz4c6xutliAnYVZp6xJlmC569jZqnR3ElO1Y01ooYASSVQEqpd2x0A2UfrgVMs5V9/9tUAdZaDMytQ==}
@@ -5914,9 +5889,6 @@ packages:
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
'@types/pbf@3.0.5':
resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==}
'@types/postcss-import@14.0.3':
resolution: {integrity: sha512-raZhRVTf6Vw5+QbmQ7LOHSDML71A5rj4+EqDzAbrZPfxfoGzFxMHRCq16VlddGIZpHELw0BG4G0YE2ANkdZiIQ==}
@@ -9130,9 +9102,6 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
geojson-vt@4.0.2:
resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
@@ -9264,10 +9233,6 @@ packages:
resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==}
engines: {node: '>=6'}
global-prefix@4.0.0:
resolution: {integrity: sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==}
engines: {node: '>=16'}
globals@15.15.0:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
engines: {node: '>=18'}
@@ -10410,12 +10375,6 @@ packages:
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
leaflet-gpx@2.2.0:
resolution: {integrity: sha512-iVUx6o0ydLn2ikYSVLuWnr0k/CDAOIUtmvQ91AI24/PXuIFIb+iEIJMHTQfvGhMCKcFrwd9ZaFYH7P/46tgGhw==}
leaflet@1.9.4:
resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==}
less@4.1.3:
resolution: {integrity: sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==}
engines: {node: '>=6'}
@@ -10767,8 +10726,8 @@ packages:
resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==}
engines: {node: '>=6'}
maplibre-gl@5.6.1:
resolution: {integrity: sha512-TTSfoTaF7RqKUR9wR5qDxCHH2J1XfZ1E85luiLOx0h8r50T/LnwAwwfV0WVNh9o8dA7rwt57Ucivf1emyeukXg==}
maplibre-gl@5.18.0:
resolution: {integrity: sha512-UtWxPBpHuFvEkM+5FVfcFG9ZKEWZQI6+PZkvLErr8Zs5ux+O7/KQ3JjSUvAfOlMeMgd/77qlHpOw0yHL7JU5cw==}
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
mark.js@8.11.1:
@@ -11872,8 +11831,8 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pbf@3.3.0:
resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==}
pbf@4.0.1:
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
hasBin: true
pdfjs-dist@5.4.530:
@@ -14849,9 +14808,6 @@ packages:
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
vt-pbf@3.1.3:
resolution: {integrity: sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==}
w3c-hr-time@1.0.2:
resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
deprecated: Use your platform's native performance.now() and performance.timeOrigin.
@@ -15059,11 +15015,6 @@ packages:
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
hasBin: true
which@4.0.0:
resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==}
engines: {node: ^16.13.0 || >=18.0.0}
hasBin: true
which@5.0.0:
resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==}
engines: {node: ^18.17.0 || >=20.5.0}
@@ -16021,6 +15972,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.4.0
'@ckeditor/ckeditor5-upload': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-ai@47.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
@@ -16161,12 +16114,16 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
'@ckeditor/ckeditor5-widget': 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-cloud-services@47.4.0':
dependencies:
'@ckeditor/ckeditor5-core': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies:
@@ -16232,6 +16189,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
'@ckeditor/ckeditor5-watchdog': 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@54.3.3(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
@@ -16357,6 +16316,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-classic@47.4.0':
dependencies:
@@ -16366,6 +16327,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-decoupled@47.4.0':
dependencies:
@@ -16375,6 +16338,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-inline@47.4.0':
dependencies:
@@ -16408,8 +16373,6 @@ snapshots:
'@ckeditor/ckeditor5-table': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-emoji@47.4.0':
dependencies:
@@ -16466,8 +16429,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-export-word@47.4.0':
dependencies:
@@ -16492,6 +16453,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-font@47.4.0':
dependencies:
@@ -16566,6 +16529,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
'@ckeditor/ckeditor5-widget': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-html-embed@47.4.0':
dependencies:
@@ -16625,8 +16590,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-indent@47.4.0':
dependencies:
@@ -16750,8 +16713,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-merge-fields@47.4.0':
dependencies:
@@ -16764,8 +16725,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-minimap@47.4.0':
dependencies:
@@ -16774,8 +16733,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-operations-compressor@47.4.0':
dependencies:
@@ -16830,8 +16787,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
'@ckeditor/ckeditor5-widget': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-pagination@47.4.0':
dependencies:
@@ -16939,8 +16894,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-slash-command@47.4.0':
dependencies:
@@ -16953,8 +16906,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-source-editing-enhanced@47.4.0':
dependencies:
@@ -17002,8 +16953,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-table@47.4.0':
dependencies:
@@ -17016,8 +16965,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-template@47.4.0':
dependencies:
@@ -17128,8 +17075,6 @@ snapshots:
'@ckeditor/ckeditor5-engine': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-widget@47.4.0':
dependencies:
@@ -17149,8 +17094,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@codemirror/autocomplete@6.18.6':
dependencies:
@@ -19160,25 +19103,23 @@ snapshots:
'@mapbox/jsonlint-lines-primitives@2.0.2': {}
'@mapbox/point-geometry@0.1.0': {}
'@mapbox/point-geometry@1.1.0': {}
'@mapbox/tiny-sdf@2.0.7': {}
'@mapbox/unitbezier@0.0.1': {}
'@mapbox/vector-tile@1.3.1':
'@mapbox/vector-tile@2.0.4':
dependencies:
'@mapbox/point-geometry': 0.1.0
'@mapbox/point-geometry': 1.1.0
'@types/geojson': 7946.0.16
pbf: 4.0.1
'@mapbox/whoots-js@3.1.0': {}
'@maplibre/maplibre-gl-leaflet@0.1.3(@types/leaflet@1.9.21)(leaflet@1.9.4)(maplibre-gl@5.6.1)':
dependencies:
'@types/leaflet': 1.9.21
leaflet: 1.9.4
maplibre-gl: 5.6.1
'@maplibre/geojson-vt@5.0.4': {}
'@maplibre/maplibre-gl-style-spec@23.3.0':
'@maplibre/maplibre-gl-style-spec@24.4.1':
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/unitbezier': 0.0.1
@@ -19188,6 +19129,20 @@ snapshots:
rw: 1.3.3
tinyqueue: 3.0.0
'@maplibre/mlt@1.1.6':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@maplibre/vt-pbf@4.2.1':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@mapbox/vector-tile': 2.0.4
'@maplibre/geojson-vt': 5.0.4
'@types/geojson': 7946.0.16
'@types/supercluster': 7.1.3
pbf: 4.0.1
supercluster: 8.0.1
'@marijn/find-cluster-break@1.0.2': {}
'@mdi/font@7.4.47': {}
@@ -21178,10 +21133,6 @@ snapshots:
'@types/node': 24.10.10
optional: true
'@types/geojson-vt@3.2.5':
dependencies:
'@types/geojson': 7946.0.16
'@types/geojson@7946.0.16': {}
'@types/har-format@1.2.16': {}
@@ -21218,14 +21169,6 @@ snapshots:
dependencies:
'@types/node': 24.10.10
'@types/leaflet-gpx@1.3.8':
dependencies:
'@types/leaflet': 1.9.21
'@types/leaflet@1.9.21':
dependencies:
'@types/geojson': 7946.0.16
'@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.16
@@ -21234,14 +21177,6 @@ snapshots:
'@types/luxon@3.6.2': {}
'@types/mapbox__point-geometry@0.1.4': {}
'@types/mapbox__vector-tile@1.3.4':
dependencies:
'@types/geojson': 7946.0.16
'@types/mapbox__point-geometry': 0.1.4
'@types/pbf': 3.0.5
'@types/mark.js@8.11.12':
dependencies:
'@types/jquery': 3.5.33
@@ -21304,8 +21239,6 @@ snapshots:
'@types/parse-json@4.0.2': {}
'@types/pbf@3.0.5': {}
'@types/postcss-import@14.0.3':
dependencies:
postcss: 8.5.6
@@ -25539,8 +25472,6 @@ snapshots:
gensync@1.0.0-beta.2: {}
geojson-vt@4.0.2: {}
get-caller-file@2.0.5: {}
get-east-asian-width@1.4.0: {}
@@ -25708,12 +25639,6 @@ snapshots:
kind-of: 6.0.3
which: 1.3.1
global-prefix@4.0.0:
dependencies:
ini: 4.1.3
kind-of: 6.0.3
which: 4.0.0
globals@15.15.0: {}
globals@16.5.0: {}
@@ -26971,10 +26896,6 @@ snapshots:
leac@0.6.0: {}
leaflet-gpx@2.2.0: {}
leaflet@1.9.4: {}
less@4.1.3:
dependencies:
copy-anything: 2.0.6
@@ -27391,34 +27312,30 @@ snapshots:
dependencies:
p-defer: 1.0.0
maplibre-gl@5.6.1:
maplibre-gl@5.18.0:
dependencies:
'@mapbox/geojson-rewind': 0.5.2
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/point-geometry': 0.1.0
'@mapbox/point-geometry': 1.1.0
'@mapbox/tiny-sdf': 2.0.7
'@mapbox/unitbezier': 0.0.1
'@mapbox/vector-tile': 1.3.1
'@mapbox/vector-tile': 2.0.4
'@mapbox/whoots-js': 3.1.0
'@maplibre/maplibre-gl-style-spec': 23.3.0
'@maplibre/geojson-vt': 5.0.4
'@maplibre/maplibre-gl-style-spec': 24.4.1
'@maplibre/mlt': 1.1.6
'@maplibre/vt-pbf': 4.2.1
'@types/geojson': 7946.0.16
'@types/geojson-vt': 3.2.5
'@types/mapbox__point-geometry': 0.1.4
'@types/mapbox__vector-tile': 1.3.4
'@types/pbf': 3.0.5
'@types/supercluster': 7.1.3
earcut: 3.0.2
geojson-vt: 4.0.2
gl-matrix: 3.4.4
global-prefix: 4.0.0
kdbush: 4.0.2
murmurhash-js: 1.0.0
pbf: 3.3.0
pbf: 4.0.1
potpack: 2.1.0
quickselect: 3.0.0
supercluster: 8.0.1
tinyqueue: 3.0.0
vt-pbf: 3.1.3
mark.js@8.11.1: {}
@@ -28837,9 +28754,8 @@ snapshots:
pathe@2.0.3: {}
pbf@3.3.0:
pbf@4.0.1:
dependencies:
ieee754: 1.2.1
resolve-protobuf-schema: 2.1.0
pdfjs-dist@5.4.530:
@@ -32298,12 +32214,6 @@ snapshots:
vscode-uri@3.1.0: {}
vt-pbf@3.1.3:
dependencies:
'@mapbox/point-geometry': 0.1.0
'@mapbox/vector-tile': 1.3.1
pbf: 3.3.0
w3c-hr-time@1.0.2:
dependencies:
browser-process-hrtime: 1.0.0
@@ -32667,10 +32577,6 @@ snapshots:
dependencies:
isexe: 2.0.0
which@4.0.0:
dependencies:
isexe: 3.1.1
which@5.0.0:
dependencies:
isexe: 3.1.1