diff --git a/apps/client/package.json b/apps/client/package.json index f0ce125f498..330deda32ac 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -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", diff --git a/apps/client/src/menus/link_context_menu.ts b/apps/client/src/menus/link_context_menu.ts index 607ff6c56a0..afc76108d07 100644 --- a/apps/client/src/menus/link_context_menu.ts +++ b/apps/client/src/menus/link_context_menu.ts @@ -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[] { +function getItems(e: ContextMenuEvent | GeoMouseEvent): MenuItem[] { const ntxId = getNtxId(e); const isMobileSplitOpen = isMobile() && appContext.tabManager.getNoteContextById(ntxId).getMainContext().getSubContexts().length > 1; @@ -28,7 +28,7 @@ function getItems(e: ContextMenuEvent | LeafletMouseEvent): MenuItemwiki for details.", + "no_children_help": "This collection doesn't have any child notes so there's nothing to display.", "drag_locked_title": "Locked for editing", "drag_locked_message": "Dragging not allowed since the collection is locked for editing." }, diff --git a/apps/client/src/types-lib.d.ts b/apps/client/src/types-lib.d.ts index aa125f389dc..39498731967 100644 --- a/apps/client/src/types-lib.d.ts +++ b/apps/client/src/types-lib.d.ts @@ -1,3 +1,5 @@ +export {}; + // TODO: Use real @types/ but that one generates a lot of errors. declare module "draggabilly" { type DraggabillyEventData = {}; @@ -32,26 +34,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. */ diff --git a/apps/client/src/widgets/FloatingButtons.css b/apps/client/src/widgets/FloatingButtons.css index b61df46daf3..5d3f4bb65b4 100644 --- a/apps/client/src/widgets/FloatingButtons.css +++ b/apps/client/src/widgets/FloatingButtons.css @@ -88,12 +88,6 @@ } /* #endregion */ -/* #region Geo map buttons */ -.leaflet-pane { - z-index: 50; -} -/* #endregion */ - /* #region Close floating buttons */ .close-floating-buttons { margin-inline-start: 5px !important; diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index c49802a7a49..1b2cc8f8ccf 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -1,6 +1,7 @@ import "./NoteDetail.css"; import clsx from "clsx"; +import { note } from "mermaid/dist/rendering-util/rendering-elements/shapes/note.js"; import { isValidElement, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; @@ -355,6 +356,14 @@ export function checkFullHeight(noteContext: NoteContext | undefined, type: Exte // https://github.com/zadam/trilium/issues/2522 const isBackendNote = noteContext?.noteId === "_backendLog"; const isFullHeightNoteType = type && TYPE_MAPPINGS[type].isFullHeight; + + // Allow vertical centering when there are no results. + if (type === "book" && + [ "grid", "list" ].includes(noteContext.note?.getLabelValue("viewType") ?? "grid") && + !noteContext.note?.hasChildren()) { + return true; + } + return (!noteContext?.hasNoteList() && isFullHeightNoteType) || noteContext?.viewScope?.viewMode === "attachments" || isBackendNote; diff --git a/apps/client/src/widgets/collections/NoteList.css b/apps/client/src/widgets/collections/NoteList.css index 449f45d97c6..198bc1bdaff 100644 --- a/apps/client/src/widgets/collections/NoteList.css +++ b/apps/client/src/widgets/collections/NoteList.css @@ -2,8 +2,12 @@ min-height: 0; max-width: var(--max-content-width); /* Inherited from .note-split */ - overflow: auto; + overflow: visible; contain: none !important; + + &.full-height { + overflow: auto; + } } body.prefers-centered-content .note-list-widget:not(.full-height) { diff --git a/apps/client/src/widgets/collections/geomap/context_menu.ts b/apps/client/src/widgets/collections/geomap/ContextMenus.ts similarity index 62% rename from apps/client/src/widgets/collections/geomap/context_menu.ts rename to apps/client/src/widgets/collections/geomap/ContextMenus.ts index 47026566fc7..9c457a8510b 100644 --- a/apps/client/src/widgets/collections/geomap/context_menu.ts +++ b/apps/client/src/widgets/collections/geomap/ContextMenus.ts @@ -1,14 +1,51 @@ -import type { LatLng, LeafletMouseEvent } from "leaflet"; +import { MapMouseEvent } from "maplibre-gl"; +import { useCallback, useContext, useEffect } from "preact/hooks"; + import appContext, { type CommandMappings } from "../../../components/app_context.js"; +import FNote from "../../../entities/fnote.js"; import contextMenu, { type MenuItem } from "../../../menus/context_menu.js"; -import linkContextMenu from "../../../menus/link_context_menu.js"; import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker.jsx"; -import { t } from "../../../services/i18n.js"; -import { createNewNote } from "./api.js"; +import linkContextMenu from "../../../menus/link_context_menu.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; +import { t } from "../../../services/i18n.js"; import link from "../../../services/link.js"; +import { createNewNote } from "./api.js"; +import { type GeoMouseEvent,ParentMap, toMapLibreEvent } from "./map.js"; +import { MARKER_LAYER } from "./Markers.js"; + +export default function ContextMenus({ note, isReadOnly }: { note: FNote, isReadOnly }) { + const map = useContext(ParentMap); -export default function openContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) { + const onContextMenu = useCallback((e: GeoMouseEvent) => { + if (!map) return; + const features = map.queryRenderedFeatures(e.point, { + layers: [ MARKER_LAYER ] + }); + + if (features.length > 0) { + // Marker context menu. + openContextMenu(features[0].properties.id, e, !isReadOnly); + } else { + // Empty area context menu. + openMapContextMenu(note.noteId, e, !isReadOnly); + } + }, [ map, note.noteId, isReadOnly ]); + + useEffect(() => { + if (!onContextMenu || !map) return; + + const handler = (e: MapMouseEvent) => { + e.preventDefault(); + onContextMenu(toMapLibreEvent(e)); + }; + map.on("contextmenu", handler); + return () => { map.off("contextmenu", handler); }; + }, [ map, onContextMenu ]); + + return null; +} + +export function openContextMenu(noteId: string, e: GeoMouseEvent, isEditable: boolean) { let items: MenuItem[] = [ ...buildGeoLocationItem(e), { kind: "separator" }, @@ -44,7 +81,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[] = [ ...buildGeoLocationItem(e) ]; @@ -58,7 +95,7 @@ export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEdita handler: () => createNewNote(noteId, e), uiIcon: "bx bx-plus" } - ] + ]; } contextMenu.show({ @@ -71,8 +108,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)}`; } diff --git a/apps/client/src/widgets/collections/geomap/Markers.tsx b/apps/client/src/widgets/collections/geomap/Markers.tsx new file mode 100644 index 00000000000..f5dcf82cc5b --- /dev/null +++ b/apps/client/src/widgets/collections/geomap/Markers.tsx @@ -0,0 +1,250 @@ +import { AddLayerObject, type GeoJSONSource } from "maplibre-gl"; +import { useCallback, useContext, useEffect, useState } from "preact/hooks"; + +import FNote from "../../../entities/fnote"; +import { useChildNotes } from "../../react/hooks"; +import { ParentMap } from "./map"; + +export const LOCATION_ATTRIBUTE = "geolocation"; +export const MARKER_LAYER = "points-layer"; +const MARKER_SOURCE = "points"; +const DEFAULT_MARKER_COLOR = "#2A81CB"; + +// SVG marker pin shape (replaces the Leaflet marker PNG). +export const MARKER_SVG = "foo"; // TODO: Fix +const iconSvgCache = new Map(); +const iconClassToBitmapCache = new Map(); + +const LABEL_LAYOUT: Extract["layout"] = { + "text-field": ["get", "name"], + "text-font": ["Open Sans Regular"], + "text-size": 12, + "text-anchor": "top", + "text-allow-overlap": true, +}; + +const MARKER_WIDTH = 25; +const MARKER_ICON_SIZE = 20; +const MARKER_ICON_X = (MARKER_WIDTH / 2) - (MARKER_ICON_SIZE / 2); + +export default function Markers({ note, hideLabels }: { note: FNote, hideLabels: boolean }) { + const map = useContext(ParentMap); + const childNotes = useChildNotes(note?.noteId); + + // Add the source and layer. + useEffect(() => { + if (!map) return; + + // Wait for styles to be loaded. + map.on("style.load", () => { + map.addSource(MARKER_SOURCE, { + type: "geojson", + data: { + type: "FeatureCollection", + features: [] + } + }); + map.addLayer({ + id: MARKER_LAYER, + type: "symbol", + source: MARKER_SOURCE, + layout: { + "icon-image": [ "get", "icon" ], + "icon-size": 1, + "icon-anchor": "bottom", + "icon-allow-overlap": true, + ...hideLabels ? [] : LABEL_LAYOUT + }, + paint: { + "text-color": "#333", + "text-halo-color": "#fff", + "text-halo-width": 1, + } + }); + }); + + return () => { + if (map.getLayer(MARKER_LAYER)) { + map.removeLayer(MARKER_LAYER); + } + if (map.getSource(MARKER_SOURCE)) { + map.removeSource(MARKER_SOURCE); + } + }; + }, [ map ]); + + const refresh = useCallback(async () => { + if (!map) return; + + async function ensureIcon(color: string, iconClass: string) { + const key = `marker-${color}-${iconClass}`; + + if (!iconSvgCache.has(key)) { + const svg = await buildMarkerIcon(color, iconClass); + iconSvgCache.set(key, svg); + } + + return key; + } + + const features: GeoJSON.Feature[] = []; + for (const childNote of childNotes) { + const location = childNote.getLabelValue(LOCATION_ATTRIBUTE); + const latLng = location?.split(",", 2).map((el) => parseFloat(el)) as [ number, number ] | undefined; + if (!latLng) continue; + latLng.reverse(); + + const color = childNote.getLabelValue("color") ?? DEFAULT_MARKER_COLOR; + features.push({ + type: "Feature", + geometry: { + type: "Point", + coordinates: latLng + }, + properties: { + id: childNote.noteId, + name: childNote.title, + icon: await ensureIcon(color, childNote.getIcon()) + } + }); + } + + // Build all the icons. + await Promise.all(iconSvgCache.entries().map(async ([ key, svg ]) => { + const image = await svgToImage(svg); + map.addImage(key, image, { + pixelRatio: window.devicePixelRatio + }); + })); + + // Update the source + const source = map.getSource(MARKER_SOURCE); + source?.setData({ + type: "FeatureCollection", + features + }); + }, [ childNotes, map ]); + + // Refresh the data. + useEffect(() => { + refresh(); + }, [ refresh ]); + return null; +} + +//#region Renderer +async function buildMarkerIcon(color: string, iconClass: string, scale = window.devicePixelRatio || 1) { + const iconUrl = iconClassToBitmapCache.get(iconClass) ?? await snapshotIcon(iconClass, MARKER_ICON_SIZE * scale); + return `\ + + + + + + + + + + `; +} + +async function snapshotIcon(iconClass: string, size: number) { + const cachedIcon = iconClassToBitmapCache.get(iconClass); + if (cachedIcon) return cachedIcon; + + await document.fonts.ready; + const glyph = getGlyphFromClass(iconClass); + const rendered = renderMarkerCanvas({ + color: "black", + glyph, + size + }); + const dataUrl = rendered?.toDataURL(); + iconClassToBitmapCache.set(iconClass, dataUrl); + return dataUrl; +} + +function renderMarkerCanvas({ + color, + glyph, // e.g. "\uf123" + size = 32, + scale = window.devicePixelRatio || 1 +}) { + const canvas = document.createElement("canvas"); + + // High-DPI canvas + canvas.width = size * scale; + canvas.height = size * scale; + + const ctx = canvas.getContext("2d"); + if (!ctx) return null; + + // Scale for retina + ctx.scale(scale, scale); + + ctx.clearRect(0, 0, size, size); + + // Set font + ctx.font = `${size}px ${glyph.fontFamily}`; + ctx.fillStyle = color; + + // Measure glyph + const metrics = ctx.measureText(glyph.content); + + const glyphWidth = + metrics.actualBoundingBoxLeft + + metrics.actualBoundingBoxRight; + + const glyphHeight = + metrics.actualBoundingBoxAscent + + metrics.actualBoundingBoxDescent; + + // Center position + const x = (size - glyphWidth) / 2 + metrics.actualBoundingBoxLeft; + const y = (size - glyphHeight) / 2 + metrics.actualBoundingBoxAscent; + + // Draw + ctx.fillText(glyph.content, x, y); + + return canvas; +} + +function getGlyphFromClass(iconClass: string) { + const el = document.createElement("span"); + el.className = iconClass; + + document.body.appendChild(el); + + const style = window.getComputedStyle(el, "::before"); + const content = style.getPropertyValue("content"); + const fontFamily = style.getPropertyValue("font-family"); + + document.body.removeChild(el); + + if (!content || content === "none") { + return null; + } + + // content is usually quoted like: '"\f123"' + return { + fontFamily, + content: content.replace(/^["']|["']$/g, "") + }; +} + +function svgToImage(svgString: string){ + return new Promise(resolve => { + const svgBlob = new Blob([svgString], { type: "image/svg+xml" }); + const url = URL.createObjectURL(svgBlob); + + const img = new Image(); + + img.onload = () => { + URL.revokeObjectURL(url); + resolve(img); + }; + + img.src = url; + }); +} +//#endregion diff --git a/apps/client/src/widgets/collections/geomap/Tooltips.tsx b/apps/client/src/widgets/collections/geomap/Tooltips.tsx new file mode 100644 index 00000000000..387056a4f65 --- /dev/null +++ b/apps/client/src/widgets/collections/geomap/Tooltips.tsx @@ -0,0 +1,44 @@ +import { type MapGeoJSONFeature, MapMouseEvent, Popup } from "maplibre-gl"; +import { useContext, useEffect } from "preact/hooks"; + +import { ParentMap } from "./map"; +import { MARKER_LAYER } from "./Markers"; + +export default function Tooltips() { + const map = useContext(ParentMap); + + useEffect(() => { + if (!map) return; + + const tooltip = new Popup({ + closeButton: false, + closeOnClick: false, + offset: 12, + className: "marker-tooltip" + }); + + function onMouseEnter(e: MapMouseEvent & { features?: MapGeoJSONFeature[]; }) { + const feature = e.features?.[0]; + if (!feature || !map || feature.geometry.type !== "Point") return; + + tooltip + .setLngLat(feature.geometry.coordinates as [ number, number ]) + .setHTML(`${feature.properties.name}`) + .addTo(map); + } + + function onMouseLeave() { + tooltip.remove(); + } + + map.on("mouseenter", MARKER_LAYER, onMouseEnter); + map.on("mouseleave", MARKER_LAYER, onMouseLeave); + + return () => { + map.off("mouseenter", MARKER_LAYER, onMouseEnter); + map.off("mouseleave", MARKER_LAYER, onMouseLeave); + }; + }, [ map ]); + + return null; +} diff --git a/apps/client/src/widgets/collections/geomap/api.ts b/apps/client/src/widgets/collections/geomap/api.ts index 5f73415605a..8705f6e6da2 100644 --- a/apps/client/src/widgets/collections/geomap/api.ts +++ b/apps/client/src/widgets/collections/geomap/api.ts @@ -1,19 +1,20 @@ -import type { LatLng, LeafletMouseEvent } from "leaflet"; -import { LOCATION_ATTRIBUTE } from "."; +import { CreateChildrenResponse } from "@triliumnext/commons"; + import attributes from "../../../services/attributes"; import { prompt } from "../../../services/dialog"; -import server from "../../../services/server"; import { t } from "../../../services/i18n"; -import { CreateChildrenResponse } from "@triliumnext/commons"; +import server from "../../../services/server"; +import type { GeoMouseEvent } from "./map"; +import { LOCATION_ATTRIBUTE } from "./Markers"; 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()) { diff --git a/apps/client/src/widgets/collections/geomap/index.css b/apps/client/src/widgets/collections/geomap/index.css index a30842c9b60..5e7cc85ca3c 100644 --- a/apps/client/src/widgets/collections/geomap/index.css +++ b/apps/client/src/widgets/collections/geomap/index.css @@ -7,54 +7,41 @@ > .collection-properties { position: relative; - z-index: 998; } } -body.mobile .geo-view > .collection-properties { - z-index: 2500; -} - .geo-map-container { height: 100%; overflow: hidden; -} -.leaflet-pane { - z-index: 1; -} - -.leaflet-top, -.leaflet-bottom { - z-index: 997 !important; + .maplibregl-canvas-container { + position: relative; + } } .geo-view.placing-note .geo-map-container { cursor: crosshair; } -.geo-map-container .marker-pin { +.geo-map-container .geo-marker { position: relative; + cursor: pointer; + overflow: visible; } -.geo-map-container .leaflet-div-icon { +.geo-map-container .geo-marker .marker-pin { position: relative; - background: transparent; - border: 0; - 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 svg { + display: block; + filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.3)); } -.geo-map-container .leaflet-div-icon .tn-icon { +.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; @@ -62,7 +49,7 @@ body.mobile .geo-view > .collection-properties { font-size: 17px; } -.geo-map-container .leaflet-div-icon .title-label { +.geo-map-container .geo-marker .title-label { display: block; position: absolute; top: 100%; @@ -75,19 +62,19 @@ body.mobile .geo-view > .collection-properties { 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; } diff --git a/apps/client/src/widgets/collections/geomap/index.tsx b/apps/client/src/widgets/collections/geomap/index.tsx index 8be547fa3a7..4e03d3925b3 100644 --- a/apps/client/src/widgets/collections/geomap/index.tsx +++ b/apps/client/src/widgets/collections/geomap/index.tsx @@ -1,8 +1,6 @@ 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 type { Map as MapLibreGL } from "maplibre-gl"; import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; import appContext from "../../../components/app_context"; @@ -15,23 +13,24 @@ import toast from "../../../services/toast"; import CollectionProperties from "../../note_bars/CollectionProperties"; import ActionButton from "../../react/ActionButton"; import { ButtonOrActionButton } from "../../react/Button"; -import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTriliumEvent } from "../../react/hooks"; +import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTooltip, useTriliumEvent } from "../../react/hooks"; import { ParentComponent } from "../../react/react_utils"; 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 ContextMenus from "./ContextMenus"; +import Map, { GeoMouseEvent } from "./map"; import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, MapLayer } from "./map_layer"; import Marker, { GpxTrack } from "./marker"; +import Markers, { LOCATION_ATTRIBUTE } from "./Markers"; +import Tooltips from "./Tooltips"; const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659]; const DEFAULT_ZOOM = 2; -export const LOCATION_ATTRIBUTE = "geolocation"; interface MapData { view?: { - center?: LatLng | [number, number]; + center?: { lat: number; lng: number } | [number, number]; zoom?: number; }; } @@ -90,7 +89,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); @@ -98,13 +97,9 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM } }, [ state ]); - const onContextMenu = useCallback((e: LeafletMouseEvent) => { - openMapContextMenu(note.noteId, e, !isReadOnly); - }, [ note.noteId, isReadOnly ]); - // Dragging const containerRef = useRef(null); - const apiRef = useRef(null); + const apiRef = useRef(null); useNoteTreeDrag(containerRef, { dragEnabled: !isReadOnly, dragNotEnabledMessage: { @@ -121,15 +116,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 }); } } }); @@ -160,10 +155,11 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM spacedUpdate.scheduleUpdate(); }} onClick={onClick} - onContextMenu={onContextMenu} scale={hasScale} > - {notes.map(note => )} + + + } @@ -228,10 +224,10 @@ function NoteMarker({ note, editable, latLng, hideLabels }: { note: FNote, edita const [ archived ] = useNoteLabelBoolean(note, "archived"); const title = useNoteProperty(note, "title"); - const icon = useMemo(() => { + const iconHtml = useMemo(() => { const titleOrNone = hideLabels ? undefined : title; - return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, titleOrNone, note.noteId, archived); - }, [ iconClass, color, title, note.noteId, archived, hideLabels ]); + return buildIconHtml(note.getIcon(), note.getColorClass() ?? undefined, titleOrNone, note.noteId, archived); + }, [ note, iconClass, color, title, archived, hideLabels ]); const onClick = useCallback(() => { appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId }); @@ -246,15 +242,17 @@ function NoteMarker({ note, editable, latLng, hideLabels }: { note: FNote, edita } }, [ 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 && (() => ({ - markers: { - startIcon: buildIcon(note.getIcon(), note.getColorClass(), hideLabels ? undefined : note.title), - endIcon: buildIcon("bxs-flag-checkered"), - wptIcons: { - "": buildIcon("bx bx-pin") - } - }, - polyline_options: { - color: note.getLabelValue("color") ?? "blue" - } - }), [ color, iconClass, hideLabels ]); - return xmlString && ; + 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 && ; } -function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) { +function buildIconHtml(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) { let html = /*html*/`\ - - - +
${MARKER_SVG}
+ ${title ?? ""}`; if (noteIdLink) { html = `
${html}
`; } - 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 | null | undefined }) { const [ currentZoom, setCurrentZoom ] = useState(); const parentComponent = useContext(ParentComponent); @@ -327,7 +319,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 && ( diff --git a/apps/client/src/widgets/collections/geomap/map.tsx b/apps/client/src/widgets/collections/geomap/map.tsx index 19a4586c83b..9919cb330f8 100644 --- a/apps/client/src/widgets/collections/geomap/map.tsx +++ b/apps/client/src/widgets/collections/geomap/map.tsx @@ -1,145 +1,208 @@ -import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks"; -import L, { control, LatLng, Layer, LeafletMouseEvent } from "leaflet"; -import "leaflet/dist/leaflet.css"; -import { MAP_LAYERS, type MapLayer } from "./map_layer"; +import "maplibre-gl/dist/maplibre-gl.css"; + +import { Map as MapLibreGLMap, MapMouseEvent, NavigationControl, type Point, ScaleControl, StyleSpecification } from "maplibre-gl"; import { ComponentChildren, createContext, RefObject } from "preact"; +import { useEffect, useImperativeHandle, useState } from "preact/hooks"; + import { useElementSize, useSyncedRef } from "../../react/hooks"; +import { MapLayer } from "./map_layer"; + +export interface GeoMouseEvent { + latlng: { lat: number; lng: number }; + originalEvent: MouseEvent; + point: Point; +} -export const ParentMap = createContext(null); +export const ParentMap = createContext(null); interface MapProps { - apiRef?: RefObject; + apiRef?: RefObject; containerRef?: RefObject; - coordinates: LatLng | [number, number]; + coordinates: { lat: number; lng: number } | [number, number]; zoom: number; layerData: MapLayer; - 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; } +export function toMapLibreEvent(e: MapMouseEvent): GeoMouseEvent { + return { + latlng: { lat: e.lngLat.lat, lng: e.lngLat.lng }, + originalEvent: e.originalEvent, + point: e.point + }; +} + export default function Map({ coordinates, zoom, layerData, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) { - const mapRef = useRef(null); + const [ map, setMap ] = useState(null); const containerRef = useSyncedRef(_containerRef); - useImperativeHandle(apiRef ?? null, () => mapRef.current); + useImperativeHandle(apiRef ?? null, () => map); + // Initialize the map. useEffect(() => { if (!containerRef.current) return; - const mapInstance = L.map(containerRef.current, { - worldCopyJump: false, - maxBounds: [ - [-90, -180], - [90, 180] - ], - minZoom: 2 + + let style: 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 MapLibreGLMap({ + container: containerRef.current, + style, + center, + zoom, + minZoom: 1, + renderWorldCopies: false }); - mapRef.current = mapInstance; + // Add navigation buttons. + mapInstance.addControl(new NavigationControl({ + showCompass: false, + showZoom: true + }), "top-left"); + + setMap(mapInstance); + + // Load async vector style if needed. + if (layerData.type === "vector" && typeof layerData.style !== "string") { + layerData.style().then(asyncStyle => { + mapInstance.setStyle(asyncStyle as StyleSpecification); + }); + } + return () => { - mapInstance.off(); mapInstance.remove(); + setMap(null); }; }, []); - // Load the layer asynchronously. - const [ layer, setLayer ] = useState(); + // React to layer changes. useEffect(() => { - async function load() { - 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 - })); + if (!map) return; + + if (layerData.type === "vector") { + if (typeof layerData.style === "string") { + map.setStyle(layerData.style); } else { - setLayer(L.tileLayer(layerData.url, { - attribution: layerData.attribution, - detectRetina: true, - noWrap: true - })); + layerData.style().then(asyncStyle => { + map.setStyle(asyncStyle as 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" + } + ] + }); } - - load(); - }, [ layerData ]); - - // Attach layer to the map. - useEffect(() => { - const map = mapRef.current; - const layerToAdd = layer; - if (!map || !layerToAdd) return; - layerToAdd.addTo(map); - return () => layerToAdd.removeFrom(map); - }, [ mapRef, layer ]); + }, [ map, layerData ]); // React to coordinate changes. useEffect(() => { - if (!mapRef.current) return; - mapRef.current.setView(coordinates, zoom); - }, [ mapRef, coordinates, zoom ]); + if (!map) return; + const center = Array.isArray(coordinates) + ? [coordinates[1], coordinates[0]] as [number, number] + : [coordinates.lng, coordinates.lat] as [number, number]; + + map.setCenter(center); + map.setZoom(zoom); + }, [ map, 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 ]); + }, [ map, viewportChanged ]); useEffect(() => { - if (onClick && mapRef.current) { - mapRef.current.on("click", onClick); - return () => mapRef.current?.off("click", onClick); - } - }, [ mapRef, onClick ]); + if (!onClick || !map) return; - useEffect(() => { - if (onContextMenu && mapRef.current) { - mapRef.current.on("contextmenu", onContextMenu); - return () => mapRef.current?.off("contextmenu", onContextMenu); - } - }, [ mapRef, onContextMenu ]); + const handler = (e: MapMouseEvent) => onClick(toMapLibreEvent(e)); + map.on("click", handler); + return () => { map.off("click", handler); }; + }, [ map, onClick ]); useEffect(() => { - if (onZoom && mapRef.current) { - mapRef.current.on("zoom", onZoom); - return () => mapRef.current?.off("zoom", onZoom); - } - }, [ mapRef, onZoom ]); + if (!onZoom || !map) return; + + map.on("zoom", onZoom); + return () => { map.off("zoom", onZoom); }; + }, [ map, 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 ScaleControl(); + map.addControl(scaleControl); + return () => { map.removeControl(scaleControl); }; + }, [ map, scale ]); // Adapt to container size changes. const size = useElementSize(containerRef); useEffect(() => { - mapRef.current?.invalidateSize(); - }, [ size?.width, size?.height ]); + map?.resize(); + }, [ map, size?.width, size?.height ]); return (
- + {children}
diff --git a/apps/client/src/widgets/collections/geomap/map_layer.ts b/apps/client/src/widgets/collections/geomap/map_layer.ts index bb5f6174e63..3859122f286 100644 --- a/apps/client/src/widgets/collections/geomap/map_layer.ts +++ b/apps/client/src/widgets/collections/geomap/map_layer.ts @@ -1,6 +1,9 @@ +import { type StyleSpecification } from "maplibre-gl"; + export type MapLayer = ({ type: "vector"; - style: string | (() => Promise<{}>) + style: string | (() => Promise); + styleFallback: StyleSpecification; } | { type: "raster"; url: string; @@ -11,6 +14,9 @@ export type MapLayer = ({ isDarkTheme?: boolean; }; +// Minimal empty style used as a placeholder while the real style loads asynchronously. +const EMPTY_STYLE: StyleSpecification = { version: 8, sources: {}, layers: [] }; + export const MAP_LAYERS: Record = { "openstreetmap": { name: "OpenStreetMap", @@ -21,28 +27,33 @@ export const MAP_LAYERS: Record = { "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 as unknown as StyleSpecification, + styleFallback: EMPTY_STYLE }, "versatiles-eclipse": { name: "VersaTiles Eclipse", type: "vector", - style: async () => (await import("./styles/eclipse/en.json")).default, + style: async () => (await import("./styles/eclipse/en.json")).default as unknown as StyleSpecification, + 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 as unknown as StyleSpecification, + 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 as unknown as StyleSpecification, + styleFallback: EMPTY_STYLE, }, "versatiles-shadow": { name: "VersaTiles Shadow", type: "vector", - style: async () => (await import("./styles/shadow/en.json")).default, + style: async () => (await import("./styles/shadow/en.json")).default as unknown as StyleSpecification, + styleFallback: EMPTY_STYLE, isDarkTheme: true } }; diff --git a/apps/client/src/widgets/collections/geomap/marker.tsx b/apps/client/src/widgets/collections/geomap/marker.tsx index 8b6cb137193..ea693ca1f53 100644 --- a/apps/client/src/widgets/collections/geomap/marker.tsx +++ b/apps/client/src/widgets/collections/geomap/marker.tsx @@ -1,71 +1,208 @@ -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 { Marker as MapLibreMarker } from "maplibre-gl"; +import { useContext, useEffect, useRef } from "preact/hooks"; + +import { GeoMouseEvent,ParentMap } from "./map"; 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(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 MapLibreMarker({ + 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 (
); +} - return (
) +export interface GpxTrackProps { + gpxXmlString: string; + trackColor?: string; + startIconHtml?: string; + endIconHtml?: string; + waypointIconHtml?: string; } -export function GpxTrack({ gpxXmlString, options }: { gpxXmlString: string, options: GPXOptions }) { +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: MapLibreMarker[] = []; + const sourceId = `gpx-source-${Math.random().toString(36).slice(2)}`; + const layerId = `gpx-layer-${sourceId}`; + + 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 MapLibreMarker({ 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 MapLibreMarker({ 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 MapLibreMarker({ element: wptEl, anchor: "bottom" }) + .setLngLat([lon, lat]) + .addTo(parentMap); + markers.push(wptMarker); + } + } + } + + if (parentMap.isStyleLoaded()) { + addGpxToMap(); + } else { + parentMap.once("style.load", addGpxToMap); + } - return () => track.removeFrom(parentMap); - }, [ parentMap, gpxXmlString, options ]); + 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
; } diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.css b/apps/client/src/widgets/collections/legacy/ListOrGridView.css index 722e747ef0d..86f1afd3ce8 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.css +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.css @@ -1,5 +1,5 @@ .note-list { - overflow: hidden; + overflow: visible; position: relative; height: 100%; } diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.css b/apps/client/src/widgets/note_bars/CollectionProperties.css index 99700f77a73..c8802a42a01 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.css +++ b/apps/client/src/widgets/note_bars/CollectionProperties.css @@ -5,7 +5,7 @@ align-items: center; width: 100%; max-width: unset; - font-size: 0.8em; + font-size: 0.8rem; .dropdown-menu { input.form-control { diff --git a/apps/client/src/widgets/type_widgets/Book.tsx b/apps/client/src/widgets/type_widgets/Book.tsx index 8dd1030c5d7..05a1e2f058a 100644 --- a/apps/client/src/widgets/type_widgets/Book.tsx +++ b/apps/client/src/widgets/type_widgets/Book.tsx @@ -1,11 +1,13 @@ -import { t } from "../../services/i18n"; -import Alert from "../react/Alert"; -import { useNoteLabelWithDefault, useTriliumEvent } from "../react/hooks"; -import RawHtml from "../react/RawHtml"; -import { TypeWidgetProps } from "./type_widget"; import "./Book.css"; + import { useEffect, useState } from "preact/hooks"; + +import { t } from "../../services/i18n"; import { ViewTypeOptions } from "../collections/interface"; +import CollectionProperties from "../note_bars/CollectionProperties"; +import { useNoteLabelWithDefault, useTriliumEvent } from "../react/hooks"; +import NoItems from "../react/NoItems"; +import { TypeWidgetProps } from "./type_widget"; const VIEW_TYPES: ViewTypeOptions[] = [ "list", "grid", "presentation" ]; @@ -27,10 +29,12 @@ export default function Book({ note }: TypeWidgetProps) { return ( <> {shouldDisplayNoChildrenWarning && ( - - - + <> + + + + )} - ) + ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a088a22ca1..e7c7f332961 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 @@ -3986,8 +3974,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==} @@ -3995,24 +3983,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==} @@ -5787,9 +5777,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==} @@ -5829,12 +5816,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==} @@ -5844,12 +5825,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==} @@ -5910,9 +5885,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==} @@ -9126,9 +9098,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.*} @@ -9260,10 +9229,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'} @@ -10406,12 +10371,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'} @@ -10763,8 +10722,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: @@ -11868,8 +11827,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: @@ -16359,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: @@ -16368,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: @@ -16377,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: @@ -16410,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: @@ -16468,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: @@ -16494,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: @@ -16568,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: @@ -16627,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: @@ -16752,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: @@ -16766,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: @@ -16776,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: @@ -16832,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: @@ -16941,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: @@ -16955,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: @@ -17004,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: @@ -17018,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: @@ -17130,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: @@ -17151,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 +19101,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 +19127,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': {} @@ -21171,10 +21124,6 @@ snapshots: '@types/node': 24.10.13 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': {} @@ -21211,14 +21160,6 @@ snapshots: dependencies: '@types/node': 24.10.13 - '@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 @@ -21227,14 +21168,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 @@ -21301,8 +21234,6 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/pbf@3.0.5': {} - '@types/postcss-import@14.0.3': dependencies: postcss: 8.5.6 @@ -25532,8 +25463,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: {} @@ -25701,12 +25630,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: {} @@ -26964,10 +26887,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 @@ -27384,34 +27303,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: {} @@ -28830,9 +28745,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: @@ -32296,12 +32210,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 @@ -32665,10 +32573,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