Compare commits

..

38 Commits

Author SHA1 Message Date
Adorian Doran
ea25477e3d ui/pager: fix some issues 2026-02-16 02:06:39 +02:00
Adorian Doran
82576c9703 ui/pager: rewrite the pagination button distribution algorithm to better control the length 2026-02-16 01:51:32 +02:00
Adorian Doran
0b8cba78d5 ui/pager: remove unused styles 2026-02-15 21:36:53 +02:00
Adorian Doran
d8806eaa04 ui/pager: replace the page number links with buttons 2026-02-15 21:31:58 +02:00
Adorian Doran
b8bc85856b ui/pager: add prev/next page navigation buttons 2026-02-15 21:12:42 +02:00
Adorian Doran
7551d0e044 ui/cards: fix attribute name 2026-02-15 20:32:01 +02:00
Adorian Doran
489a88b8da ui/cards: refactor 2026-02-15 20:23:19 +02:00
Adorian Doran
48013dc264 ui/cards: use a better HTML structure 2026-02-15 20:14:14 +02:00
Adorian Doran
4ee9d45dfc ui/cards: implement card titles (heading) 2026-02-15 20:06:01 +02:00
Adorian Doran
e00150a876 ui/cards: do not include nesting level CSS variables if not necessary 2026-02-15 19:46:08 +02:00
Adorian Doran
71668f8f8d List collections & search results: overhaul the list UI (#8705) 2026-02-15 19:32:21 +02:00
Adorian Doran
b71424d239 Merge branch 'main' into feat/list-view-overhaul 2026-02-15 19:20:41 +02:00
Adorian Doran
d1a3bceaa6 client/list view: refactor 2026-02-15 19:15:58 +02:00
Adorian Doran
483c57029a client/list view: extract event handler 2026-02-15 19:10:31 +02:00
Adorian Doran
7e6daf5b36 style/list view: refactor 2026-02-15 18:55:23 +02:00
Adorian Doran
b658253687 style/card: refactor 2026-02-15 18:55:04 +02:00
Adorian Doran
80b488deec style/list view: tweak indentation size 2026-02-15 18:34:54 +02:00
Adorian Doran
10cf1a371e style/card: improve 2026-02-15 18:34:00 +02:00
Adorian Doran
f645d9d721 style/list view: tweak 2026-02-15 18:20:45 +02:00
Adorian Doran
900bfdff9d style/list view: refactor 2026-02-15 18:18:50 +02:00
Adorian Doran
108ca5afb5 client/list view: add support for archived items 2026-02-15 18:16:46 +02:00
Adorian Doran
3df03a551c client/list view: add support for archived items 2026-02-15 18:15:07 +02:00
Adorian Doran
6ffbe19667 client/list view: add support for tinted notes 2026-02-15 18:07:35 +02:00
Adorian Doran
fea8de89c6 style/list view: handle the title and attributes when overflowing the container 2026-02-15 14:08:50 +02:00
Adorian Doran
4afbabb977 style/list view: tweak 2026-02-15 13:51:13 +02:00
Adorian Doran
b2ebaf111f style/card: add legacy theme fallback 2026-02-15 13:43:06 +02:00
Adorian Doran
9c2b01e3c9 style/list view: tweak 2026-02-15 13:34:45 +02:00
Adorian Doran
dca201ce42 style/list view: tweak 2026-02-15 13:31:45 +02:00
Adorian Doran
3774ea3768 style/list view: tweak 2026-02-15 02:28:52 +02:00
Adorian Doran
0a9c6a3119 client/list view: add a menu button for list items 2026-02-15 02:26:23 +02:00
Adorian Doran
a6cbde88bb client/list view: add an alternate style for search results 2026-02-15 01:57:10 +02:00
Adorian Doran
9c13f36ca0 client/list view: show the contents of a note only after its rendering completes 2026-02-14 16:39:20 +02:00
Adorian Doran
fe4a11c5ad client/list view: improve appearance 2026-02-14 16:22:13 +02:00
Adorian Doran
218343ca14 client/list view: use a sectioned card to display the list items 2026-02-14 01:14:54 +02:00
Adorian Doran
61953fd713 client/ui/cards: make a property optional 2026-02-14 00:10:54 +02:00
Adorian Doran
62ddf3a11b client/ui/cards: automatically determine the opacity of the background color of nested card sections 2026-02-14 00:08:59 +02:00
Adorian Doran
be12658864 client/ui/cards: add support for custom class names and action callbacks 2026-02-14 00:08:00 +02:00
Adorian Doran
b618e5a00f client: define the foundation of sectioned cards with multi-level nesting 2026-02-13 22:50:03 +02:00
27 changed files with 854 additions and 782 deletions

View File

@@ -23,6 +23,7 @@
"@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",
@@ -50,8 +51,9 @@
"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",
@@ -70,6 +72,8 @@
"@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 { GeoMouseEvent } from "../widgets/collections/geomap/map.js";
import type { LeafletMouseEvent } from "leaflet";
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 | GeoMouseEvent): MenuItem<CommandNames>[] {
function getItems(e: ContextMenuEvent | LeafletMouseEvent): 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 | GeoMouseEvent): MenuItem<CommandNames>[]
];
}
function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEvent | GeoMouseEvent, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEvent | LeafletMouseEvent, 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 | GeoMouseEvent) {
function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
if (utils.isDesktop()) {
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
if (!subContexts) return null;

View File

@@ -291,6 +291,15 @@
--ck-editor-toolbar-button-on-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
--ck-editor-toolbar-dropdown-button-open-background: #ffffff14;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: rgba(0, 0, 0, .2);
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: black;
--calendar-coll-event-background-saturation: 25%;
--calendar-coll-event-background-lightness: 20%;
--calendar-coll-event-background-color: #3c3c3c;
@@ -304,7 +313,8 @@
* Dark color scheme tweaks
*/
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color {
--custom-color: var(--dark-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -354,7 +364,8 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%);

View File

@@ -289,6 +289,15 @@
--ck-editor-toolbar-button-on-shadow: none;
--ck-editor-toolbar-dropdown-button-open-background: #0000000f;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: #b1b1b133;
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: white;
--calendar-coll-event-background-lightness: 95%;
--calendar-coll-event-background-saturation: 80%;
--calendar-coll-event-background-color: #eaeaea;
@@ -298,7 +307,8 @@
--calendar-coll-today-background-color: #00000006;
}
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color {
--custom-color: var(--light-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -324,7 +334,8 @@
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%);

View File

@@ -751,12 +751,14 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
}
}
#left-pane .fancytree-expander {
#left-pane .fancytree-expander,
.nested-note-list-item .note-expander {
opacity: 0.65;
transition: opacity 150ms ease-in;
}
#left-pane .fancytree-expander:hover {
#left-pane .fancytree-expander:hover,
.nested-note-list-item .note-expander:hover {
opacity: 1;
transition: opacity 300ms ease-out;
}

View File

@@ -1010,7 +1010,7 @@
"no_attachments": "This note has no attachments."
},
"book": {
"no_children_help": "This collection doesn't have any child notes so there's nothing to display.",
"no_children_help": "This collection doesn't have any child notes so there's nothing to display. See <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> for details.",
"drag_locked_title": "Locked for editing",
"drag_locked_message": "Dragging not allowed since the collection is locked for editing."
},
@@ -2190,8 +2190,9 @@
"percentage": "%"
},
"pagination": {
"page_title": "Page of {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} notes"
"total_notes": "{{count}} notes",
"prev_page": "Previous page",
"next_page": "Next page"
},
"collections": {
"rendering_error": "Unable to show content due to an error."

View File

@@ -32,6 +32,26 @@ 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 */
.maplibregl-canvas-container {
.leaflet-pane {
z-index: 50;
}
/* #endregion */

View File

@@ -1,7 +1,6 @@
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";
@@ -356,14 +355,6 @@ 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;

View File

@@ -2,12 +2,8 @@
min-height: 0;
max-width: var(--max-content-width); /* Inherited from .note-split */
overflow: visible;
overflow: auto;
contain: none !important;
&.full-height {
overflow: auto;
}
}
body.prefers-centered-content .note-list-widget:not(.full-height) {
@@ -23,14 +19,3 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
.note-list-widget video {
height: 100%;
}
/* #region Pagination */
.note-list-pager {
font-size: 1rem;
span.current-page {
text-decoration: underline;
font-weight: bold;
}
}
/* #endregion */

View File

@@ -4,6 +4,8 @@ import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import { useNoteLabelInt } from "../react/hooks";
import { t } from "../../services/i18n";
import ActionButton from "../react/ActionButton";
import Button from "../react/Button";
interface PaginationContext {
page: number;
@@ -17,46 +19,79 @@ interface PaginationContext {
export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit<PaginationContext, "pageNotes">) {
if (pageCount < 2) return;
let lastPrinted = false;
let children: ComponentChildren[] = [];
for (let i = 1; i <= pageCount; i++) {
if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(page - i) <= 2) {
lastPrinted = true;
const startIndex = (i - 1) * pageSize + 1;
const endIndex = Math.min(totalNotes, i * pageSize);
if (i !== page) {
children.push((
<a
href="javascript:"
title={t("pagination.page_title", { startIndex, endIndex })}
onClick={() => setPage(i)}
>
{i}
</a>
))
} else {
// Current page
children.push(<span className="current-page">{i}</span>)
}
children.push(<>{" "}&nbsp;{" "}</>);
} else if (lastPrinted) {
children.push(<>{"... "}&nbsp;{" "}</>);
lastPrinted = false;
}
}
const children = createPageButtons(page, setPage, pageCount);
return (
<div class="note-list-pager">
{children}
<ActionButton
icon="bx bx-caret-left"
disabled={(page === 1)}
text={t("pagination.prev_page")}
onClick={() => {setPage(page - 1)}}
/>
{children}
<ActionButton
icon="bx bx-caret-right"
disabled={(page === pageCount)}
text={t("pagination.next_page")}
onClick={() => {setPage(page + 1)}}
/>
<span className="note-list-pager-total-count">({t("pagination.total_notes", { count: totalNotes })})</span>
</div>
)
}
function createPageButtons(page: number, setPage: Dispatch<StateUpdater<number>>, pageCount: number): ComponentChildren[] {
const maxButtonCount = 9;
const maxLeftRightSegmentLength = 2;
// The left-side segment
const leftLength = Math.min(pageCount, maxLeftRightSegmentLength);
const leftStart = 1;
// The middle segment
const middleMaxLength = maxButtonCount - maxLeftRightSegmentLength * 2;
const middleLength = Math.min(pageCount - leftLength, middleMaxLength);
let middleStart = page - Math.floor(middleLength / 2);
middleStart = Math.max(middleStart, leftLength + 1);
// The right-side segment
const rightLength = Math.min(pageCount - (middleLength + leftLength), maxLeftRightSegmentLength);
const rightStart = pageCount - rightLength + 1;
middleStart = Math.min(middleStart, rightStart - middleLength);
return [
...createSegment(leftStart, leftLength, page, setPage, false),
...createSegment(middleStart, middleLength, page, setPage, (middleStart - leftLength > 1)),
...createSegment(rightStart, rightLength, page, setPage, (rightStart - (middleStart + middleLength - 1) > 1)),
];
}
function createSegment(start: number, length: number, currentPage: number, setPage: Dispatch<StateUpdater<number>>, prependEllipsis: boolean): ComponentChildren[] {
const children: ComponentChildren[] = [];
if (prependEllipsis) {
children.push("...");
}
for (let i = 0; i < length; i++) {
const pageNum = start + i;
children.push((
<Button
text={pageNum.toString()}
disabled={(pageNum === currentPage)}
onClick={() => setPage(pageNum)}
/>
));
}
return children;
}
export function usePagination(note: FNote, noteIds: string[]): PaginationContext {
const [ page, setPage ] = useState(1);
const [ pageNotes, setPageNotes ] = useState<FNote[]>();

View File

@@ -1,4 +1,4 @@
import type { GeoMouseEvent } from "./map";
import type { LatLng, LeafletMouseEvent } from "leaflet";
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: { lat: number; lng: number } | null) {
export async function moveMarker(noteId: string, latLng: LatLng | null) {
const value = latLng ? [latLng.lat, latLng.lng].join(",") : "";
await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
}
export async function createNewNote(noteId: string, e: GeoMouseEvent) {
export async function createNewNote(noteId: string, e: LeafletMouseEvent) {
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 { GeoMouseEvent } from "./map.js";
import type { LatLng, LeafletMouseEvent } from "leaflet";
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: GeoMouseEvent, isEditable: boolean) {
export default function openContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) {
let items: MenuItem<keyof CommandMappings>[] = [
...buildGeoLocationItem(e),
{ kind: "separator" },
@@ -44,7 +44,7 @@ export default function openContextMenu(noteId: string, e: GeoMouseEvent, isEdit
});
}
export function openMapContextMenu(noteId: string, e: GeoMouseEvent, isEditable: boolean) {
export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) {
let items: MenuItem<keyof CommandMappings>[] = [
...buildGeoLocationItem(e)
];
@@ -71,8 +71,8 @@ export function openMapContextMenu(noteId: string, e: GeoMouseEvent, isEditable:
});
}
function buildGeoLocationItem(e: GeoMouseEvent) {
function formatGeoLocation(latlng: { lat: number; lng: number }, precision: number = 6) {
function buildGeoLocationItem(e: LeafletMouseEvent) {
function formatGeoLocation(latlng: LatLng, precision: number = 6) {
return `${latlng.lat.toFixed(precision)}, ${latlng.lng.toFixed(precision)}`;
}

View File

@@ -18,16 +18,14 @@ body.mobile .geo-view > .collection-properties {
.geo-map-container {
height: 100%;
overflow: hidden;
.maplibregl-canvas-container {
position: relative;
}
}
.maplibregl-ctrl-top-left,
.maplibregl-ctrl-top-right,
.maplibregl-ctrl-bottom-left,
.maplibregl-ctrl-bottom-right {
.leaflet-pane {
z-index: 1;
}
.leaflet-top,
.leaflet-bottom {
z-index: 997 !important;
}
@@ -35,25 +33,28 @@ body.mobile .geo-view > .collection-properties {
cursor: crosshair;
}
.geo-map-container .geo-marker {
.geo-map-container .marker-pin {
position: relative;
cursor: pointer;
}
.geo-map-container .leaflet-div-icon {
position: relative;
background: transparent;
border: 0;
overflow: visible;
}
.geo-map-container .geo-marker .marker-pin {
position: relative;
.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 .geo-marker .tn-icon {
.geo-map-container .leaflet-div-icon .tn-icon {
position: absolute;
top: 3px;
inset-inline-start: 4px;
inset-inline-start: 2px;
background-color: white;
color: var(--light-theme-custom-color, black);
padding: 2px;
@@ -61,7 +62,7 @@ body.mobile .geo-view > .collection-properties {
font-size: 17px;
}
.geo-map-container .geo-marker .title-label {
.geo-map-container .leaflet-div-icon .title-label {
display: block;
position: absolute;
top: 100%;
@@ -74,19 +75,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: nowrap;
white-space: no-wrap;
overflow: hidden;
}
body[dir=rtl] .geo-map-container .geo-marker .title-label {
body[dir=rtl] .geo-map-container .leaflet-div-icon .title-label {
transform: translateX(50%);
}
.geo-map-container .geo-marker .archived {
.geo-map-container .leaflet-div-icon .archived {
opacity: 0.5;
}
.geo-map-container.dark .geo-marker .title-label {
.geo-map-container.dark .leaflet-div-icon .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,6 +1,8 @@
import "./index.css";
import type maplibregl from "maplibre-gl";
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";
@@ -19,10 +21,9 @@ import TouchBar, { TouchBarButton, TouchBarSlider } from "../../react/TouchBar";
import { ViewModeProps } from "../interface";
import { createNewNote, moveMarker } from "./api";
import openContextMenu, { openMapContextMenu } from "./context_menu";
import Map, { GeoMouseEvent } from "./map";
import Map from "./map";
import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, MapLayer } from "./map_layer";
import Marker, { GpxTrack } from "./marker";
import { MARKER_SVG, useMarkerData } from "./marker_data";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
const DEFAULT_ZOOM = 2;
@@ -30,7 +31,7 @@ export const LOCATION_ATTRIBUTE = "geolocation";
interface MapData {
view?: {
center?: { lat: number; lng: number } | [number, number];
center?: LatLng | [number, number];
zoom?: number;
};
}
@@ -89,7 +90,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
moveMarker(noteId, null);
});
const onClick = useCallback(async (e: GeoMouseEvent) => {
const onClick = useCallback(async (e: LeafletMouseEvent) => {
if (state === State.NewNote) {
toast.closePersistent("geo-new-note");
await createNewNote(note.noteId, e);
@@ -97,15 +98,13 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
}
}, [ state ]);
const onContextMenu = useCallback((e: GeoMouseEvent) => {
const onContextMenu = useCallback((e: LeafletMouseEvent) => {
openMapContextMenu(note.noteId, e, !isReadOnly);
}, [ note.noteId, isReadOnly ]);
// Dragging
const containerRef = useRef<HTMLDivElement>(null);
const apiRef = useRef<maplibregl.Map>(null);
useMarkerData(note, apiRef);
const apiRef = useRef<L.Map>(null);
useNoteTreeDrag(containerRef, {
dragEnabled: !isReadOnly,
dragNotEnabledMessage: {
@@ -122,15 +121,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 lngLat = api.unproject([x, y]);
const latlng = api.containerPointToLatLng([ x, y ]);
const targetNote = await froca.getNote(noteId, true);
const parents = targetNote?.getParentNoteIds();
if (parents?.includes(note.noteId)) {
await moveMarker(noteId, { lat: lngLat.lat, lng: lngLat.lng });
await moveMarker(noteId, latlng);
} else {
await branches.cloneNoteToParentNote(noteId, noteId);
await moveMarker(noteId, { lat: lngLat.lat, lng: lngLat.lng });
await moveMarker(noteId, latlng);
}
}
});
@@ -164,7 +163,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
onContextMenu={onContextMenu}
scale={hasScale}
>
{/* {notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} hideLabels={hideLabels} />)} */}
{notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} hideLabels={hideLabels} />)}
</Map>}
<GeoMapTouchBar state={state} map={apiRef.current} />
</div>
@@ -229,10 +228,10 @@ function NoteMarker({ note, editable, latLng, hideLabels }: { note: FNote, edita
const [ archived ] = useNoteLabelBoolean(note, "archived");
const title = useNoteProperty(note, "title");
const iconHtml = useMemo(() => {
const icon = useMemo(() => {
const titleOrNone = hideLabels ? undefined : title;
return buildIconHtml(note.getIcon(), note.getColorClass() ?? undefined, titleOrNone, note.noteId, archived);
}, [ note, iconClass, color, title, archived, hideLabels ]);
return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, titleOrNone, note.noteId, archived);
}, [ iconClass, color, title, note.noteId, archived, hideLabels ]);
const onClick = useCallback(() => {
appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId });
@@ -247,17 +246,15 @@ function NoteMarker({ note, editable, latLng, hideLabels }: { note: FNote, edita
}
}, [ note.noteId ]);
const onDragged = useCallback((newCoordinates: { lat: number; lng: number }) => {
const onDragged = useCallback((newCoordinates: LatLng) => {
moveMarker(note.noteId, newCoordinates);
}, [ note.noteId ]);
const onContextMenu = useCallback((e: GeoMouseEvent) => openContextMenu(note.noteId, e, editable), [ note.noteId, editable ]);
const onContextMenu = useCallback((e: LeafletMouseEvent) => openContextMenu(note.noteId, e, editable), [ note.noteId, editable ]);
return latLng && <Marker
coordinates={latLng}
iconHtml={iconHtml}
iconSize={[25, 41]}
iconAnchor={[12, 41]}
icon={icon}
draggable={editable}
onMouseDown={onMouseDown}
onDragged={editable ? onDragged : undefined}
@@ -285,34 +282,40 @@ function NoteGpxTrack({ note, hideLabels }: { note: FNote, hideLabels?: boolean
const color = useNoteLabel(note, "color");
const iconClass = useNoteLabel(note, "iconClass");
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}
/>;
const options = useMemo<GPXOptions>(() => ({
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 && <GpxTrack gpxXmlString={xmlString} options={options} />;
}
function buildIconHtml(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
let html = /*html*/`\
<div class="marker-pin">${MARKER_SVG}</div>
<span class="bx ${bxIconClass} tn-icon ${colorClass ?? ""}"></span>
<img class="icon" src="${markerIcon}" />
<img class="icon-shadow" src="${markerIconShadow}" />
<span class="bx ${bxIconClass} ${colorClass ?? ""}"></span>
<span class="title-label">${title ?? ""}</span>`;
if (noteIdLink) {
html = `<div data-href="#root/${noteIdLink}" class="${archived ? "archived" : ""}">${html}</div>`;
}
return html;
return divIcon({
html,
iconSize: [25, 41],
iconAnchor: [12, 41]
});
}
function GeoMapTouchBar({ state, map }: { state: State, map: maplibregl.Map | null | undefined }) {
function GeoMapTouchBar({ state, map }: { state: State, map: L.Map | null | undefined }) {
const [ currentZoom, setCurrentZoom ] = useState<number>();
const parentComponent = useContext(ParentComponent);
@@ -324,7 +327,7 @@ function GeoMapTouchBar({ state, map }: { state: State, map: maplibregl.Map | nu
}
map.on("zoom", onZoomChanged);
return () => { map.off("zoom", onZoomChanged); };
return () => map.off("zoom", onZoomChanged);
}, [ map ]);
return map && currentZoom && (

View File

@@ -1,210 +1,137 @@
import "maplibre-gl/dist/maplibre-gl.css";
import maplibregl from "maplibre-gl";
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 { ComponentChildren, createContext, RefObject } from "preact";
import { useEffect, useImperativeHandle, useRef } from "preact/hooks";
import { useElementSize, useSyncedRef } from "../../react/hooks";
import { MapLayer } from "./map_layer";
export interface GeoMouseEvent {
latlng: { lat: number; lng: number };
originalEvent: MouseEvent;
}
export const ParentMap = createContext<maplibregl.Map | null>(null);
export const ParentMap = createContext<L.Map | null>(null);
interface MapProps {
apiRef?: RefObject<maplibregl.Map | null>;
apiRef?: RefObject<L.Map | null>;
containerRef?: RefObject<HTMLDivElement>;
coordinates: { lat: number; lng: number } | [number, number];
coordinates: LatLng | [number, number];
zoom: number;
layerData: MapLayer;
viewportChanged: (coordinates: { lat: number; lng: number }, zoom: number) => void;
viewportChanged: (coordinates: LatLng, zoom: number) => void;
children: ComponentChildren;
onClick?: (e: GeoMouseEvent) => void;
onContextMenu?: (e: GeoMouseEvent) => void;
onClick?: (e: LeafletMouseEvent) => void;
onContextMenu?: (e: LeafletMouseEvent) => 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, layerData, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) {
const mapRef = useRef<maplibregl.Map>(null);
const mapRef = useRef<L.Map>(null);
const containerRef = useSyncedRef<HTMLDivElement>(_containerRef);
useImperativeHandle(apiRef ?? null, () => mapRef.current);
// Initialize the map.
useEffect(() => {
if (!containerRef.current) return;
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: 1,
renderWorldCopies: false
const mapInstance = L.map(containerRef.current, {
worldCopyJump: false,
maxBounds: [
[-90, -180],
[90, 180]
],
minZoom: 2
});
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;
};
}, []);
// React to layer changes.
// Load the layer asynchronously.
const [ layer, setLayer ] = useState<Layer>();
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
}));
} else {
setLayer(L.tileLayer(layerData.url, {
attribution: layerData.attribution,
detectRetina: true,
noWrap: true
}));
}
}
load();
}, [ layerData ]);
// Attach layer to the map.
useEffect(() => {
const map = mapRef.current;
if (!map) return;
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"
}
]
});
}
}, [ layerData ]);
const layerToAdd = layer;
if (!map || !layerToAdd) return;
layerToAdd.addTo(map);
return () => layerToAdd.removeFrom(map);
}, [ mapRef, layer ]);
// React to coordinate changes.
useEffect(() => {
if (!mapRef.current) return;
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 ]);
mapRef.current.setView(coordinates, zoom);
}, [ mapRef, coordinates, zoom ]);
// Viewport callback.
useEffect(() => {
const map = mapRef.current;
if (!map) return;
const updateFn = () => {
const center = map.getCenter();
viewportChanged({ lat: center.lat, lng: center.lng }, map.getZoom());
};
const updateFn = () => viewportChanged(map.getBounds().getCenter(), map.getZoom());
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
return () => {
map.off("moveend", updateFn);
map.off("zoomend", updateFn);
};
}, [ viewportChanged ]);
}, [ mapRef, viewportChanged ]);
useEffect(() => {
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 ]);
if (onClick && mapRef.current) {
mapRef.current.on("click", onClick);
return () => mapRef.current?.off("click", onClick);
}
}, [ mapRef, onClick ]);
useEffect(() => {
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 ]);
if (onContextMenu && mapRef.current) {
mapRef.current.on("contextmenu", onContextMenu);
return () => mapRef.current?.off("contextmenu", onContextMenu);
}
}, [ mapRef, onContextMenu ]);
useEffect(() => {
const map = mapRef.current;
if (!onZoom || !map) return;
map.on("zoom", onZoom);
return () => { map.off("zoom", onZoom); };
}, [ onZoom ]);
if (onZoom && mapRef.current) {
mapRef.current.on("zoom", onZoom);
return () => mapRef.current?.off("zoom", onZoom);
}
}, [ mapRef, onZoom ]);
// Scale
useEffect(() => {
const map = mapRef.current;
if (!scale || !map) return;
const scaleControl = new maplibregl.ScaleControl();
map.addControl(scaleControl);
return () => { map.removeControl(scaleControl); };
}, [ scale ]);
const scaleControl = control.scale();
scaleControl.addTo(map);
return () => scaleControl.remove();
}, [ mapRef, scale ]);
// Adapt to container size changes.
const size = useElementSize(containerRef);
useEffect(() => {
mapRef.current?.resize();
mapRef.current?.invalidateSize();
}, [ size?.width, size?.height ]);
return (

View File

@@ -1,9 +1,6 @@
import { type StyleSpecification } from "maplibre-gl";
export type MapLayer = ({
type: "vector";
style: string | (() => Promise<StyleSpecification>);
styleFallback: StyleSpecification;
style: string | (() => Promise<{}>)
} | {
type: "raster";
url: string;
@@ -14,9 +11,6 @@ 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<string, MapLayer> = {
"openstreetmap": {
name: "OpenStreetMap",
@@ -27,33 +21,28 @@ export const MAP_LAYERS: Record<string, MapLayer> = {
"versatiles-colorful": {
name: "VersaTiles Colorful",
type: "vector",
style: async () => (await import("./styles/colorful/en.json")).default as unknown as StyleSpecification,
styleFallback: EMPTY_STYLE
style: async () => (await import("./styles/colorful/en.json")).default
},
"versatiles-eclipse": {
name: "VersaTiles Eclipse",
type: "vector",
style: async () => (await import("./styles/eclipse/en.json")).default as unknown as StyleSpecification,
styleFallback: EMPTY_STYLE,
style: async () => (await import("./styles/eclipse/en.json")).default,
isDarkTheme: true
},
"versatiles-graybeard": {
name: "VersaTiles Graybeard",
type: "vector",
style: async () => (await import("./styles/graybeard/en.json")).default as unknown as StyleSpecification,
styleFallback: EMPTY_STYLE,
style: async () => (await import("./styles/graybeard/en.json")).default
},
"versatiles-neutrino": {
name: "VersaTiles Neutrino",
type: "vector",
style: async () => (await import("./styles/neutrino/en.json")).default as unknown as StyleSpecification,
styleFallback: EMPTY_STYLE,
style: async () => (await import("./styles/neutrino/en.json")).default
},
"versatiles-shadow": {
name: "VersaTiles Shadow",
type: "vector",
style: async () => (await import("./styles/shadow/en.json")).default as unknown as StyleSpecification,
styleFallback: EMPTY_STYLE,
style: async () => (await import("./styles/shadow/en.json")).default,
isDarkTheme: true
}
};

View File

@@ -1,207 +1,71 @@
import { useContext, useEffect, useRef } from "preact/hooks";
import { ParentMap, GeoMouseEvent } from "./map";
import maplibregl from "maplibre-gl";
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";
export interface MarkerProps {
coordinates: [ number, number ];
iconHtml?: string;
iconSize?: [number, number];
iconAnchor?: [number, number];
icon?: Icon | DivIcon;
onClick?: () => void;
onMouseDown?: (e: MouseEvent) => void;
onDragged?: ((newCoordinates: { lat: number; lng: number }) => void);
onContextMenu: (e: GeoMouseEvent) => void;
onDragged?: ((newCoordinates: LatLng) => void);
onContextMenu: (e: LeafletMouseEvent) => void;
draggable?: boolean;
}
export default function Marker({ coordinates, iconHtml, iconSize, iconAnchor, draggable, onClick, onDragged, onMouseDown, onContextMenu }: MarkerProps) {
export default function Marker({ coordinates, icon, draggable, onClick, onDragged, onMouseDown, onContextMenu }: MarkerProps) {
const parentMap = useContext(ParentMap);
const markerRef = useRef<maplibregl.Marker>(null);
useEffect(() => {
if (!parentMap) return;
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 options: MarkerOptions = { icon };
if (draggable) {
options.draggable = true;
options.autoPan = true;
options.autoPanSpeed = 5;
}
const newMarker = new maplibregl.Marker({
element: el,
draggable: !!draggable,
anchor: "bottom"
})
.setLngLat([coordinates[1], coordinates[0]])
.addTo(parentMap);
markerRef.current = newMarker;
const newMarker = marker(coordinates, options);
if (onClick) {
el.addEventListener("click", (e) => {
e.stopPropagation();
onClick();
});
newMarker.on("click", () => onClick());
}
if (onMouseDown) {
el.addEventListener("mousedown", (e) => {
if (e.button === 1) {
e.stopPropagation();
onMouseDown(e);
}
});
newMarker.on("mousedown", e => onMouseDown(e.originalEvent));
}
if (onDragged) {
newMarker.on("dragend", () => {
const lngLat = newMarker.getLngLat();
onDragged({ lat: lngLat.lat, lng: lngLat.lng });
newMarker.on("moveend", e => {
const coordinates = (e.target as LeafletMarker).getLatLng();
onDragged(coordinates);
});
}
if (onContextMenu) {
el.addEventListener("contextmenu", (e) => {
e.stopPropagation();
e.preventDefault();
const lngLat = newMarker.getLngLat();
onContextMenu({
latlng: { lat: lngLat.lat, lng: lngLat.lng },
originalEvent: e
});
});
newMarker.on("contextmenu", e => onContextMenu(e))
}
return () => {
newMarker.remove();
markerRef.current = null;
};
}, [ parentMap, coordinates, onMouseDown, onDragged, iconHtml ]);
newMarker.addTo(parentMap);
return (<div />);
return () => newMarker.removeFrom(parentMap);
}, [ parentMap, coordinates, onMouseDown, onDragged, icon ]);
return (<div />)
}
export interface GpxTrackProps {
gpxXmlString: string;
trackColor?: string;
startIconHtml?: string;
endIconHtml?: string;
waypointIconHtml?: string;
}
export function GpxTrack({ gpxXmlString, trackColor, startIconHtml, endIconHtml, waypointIconHtml }: GpxTrackProps) {
export function GpxTrack({ gpxXmlString, options }: { gpxXmlString: string, options: GPXOptions }) {
const parentMap = useContext(ParentMap);
useEffect(() => {
if (!parentMap) return;
const markers: maplibregl.Marker[] = [];
const sourceId = `gpx-source-${Math.random().toString(36).slice(2)}`;
const layerId = `gpx-layer-${sourceId}`;
const track = new GPX(gpxXmlString, options);
track.addTo(parentMap);
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 () => track.removeFrom(parentMap);
}, [ parentMap, gpxXmlString, options ]);
return <div />;
}

View File

@@ -1,86 +0,0 @@
import { MutableRef, useEffect } from "preact/hooks";
import FNote from "../../../entities/fnote";
import { useChildNotes } from "../../react/hooks";
import { LOCATION_ATTRIBUTE } from ".";
import { buildMarkerIcon, svgToImage } from "./marker_renderer";
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<string, string>();
export function useMarkerData(note: FNote | null | undefined, apiRef: MutableRef<maplibregl.Map>) {
const childNotes = useChildNotes(note?.noteId);
async function refresh() {
const map = apiRef.current as maplibregl.Map | undefined;
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: maplibregl.GeoJSONFeature[] = [];
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
});
}));
map.addSource("points", {
type: "geojson",
data: {
type: "FeatureCollection",
features
}
});
map.addLayer({
id: "points-layer",
type: "symbol",
source: "points",
layout: {
"icon-image": [ "get", "icon" ],
"icon-size": 1,
"icon-anchor": "bottom",
"icon-allow-overlap": true
}
});
}
useEffect(() => {
refresh();
}, [ apiRef, childNotes ]);
}

View File

@@ -1,112 +0,0 @@
const iconClassToBitmapCache = new Map<string, string | undefined>();
export async function buildMarkerIcon(color: string, iconClass: string, scale = window.devicePixelRatio || 1) {
const iconUrl = iconClassToBitmapCache.get(iconClass) ?? await snapshotIcon(iconClass, 16 * scale);
return `\
<svg width="${25 * scale}" height="${41 * scale}" 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="${color}" />
<circle cx="12.5" cy="12.5" r="8" fill="white" />
<image href="${iconUrl}" x="4.5" y="4.5" width="16" height="16" preserveAspectRatio="xMidYMid meet" />
</svg>
`;
}
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, "")
};
}
export function svgToImage(svgString: string){
return new Promise<HTMLImageElement>(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;
});
}

View File

@@ -1,5 +1,5 @@
.note-list {
overflow: visible;
overflow: hidden;
position: relative;
height: 100%;
}
@@ -100,23 +100,206 @@
overflow: auto;
}
.note-expander {
font-size: x-large;
position: relative;
top: 3px;
cursor: pointer;
/* #region List view */
@keyframes note-preview-show {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.note-list-pager {
text-align: center;
.nested-note-list {
--card-nested-section-indent: 25px;
&.search-results {
--card-nested-section-indent: 32px;
}
}
.note-list.list-view .note-path {
margin-left: 0.5em;
vertical-align: middle;
opacity: 0.5;
/* List item */
.nested-note-list-item {
h5 {
display: flex;
align-items: center;
font-size: 1em;
font-weight: normal;
margin: 0;
}
.note-expander {
margin-inline-end: 4px;
font-size: x-large;
cursor: pointer;
}
.tn-icon {
margin-inline-end: 8px;
color: var(--note-list-view-icon-color);
font-size: 1.2em;
}
.note-book-title {
--link-hover-background: transparent;
--link-hover-color: currentColor;
color: inherit;
font-weight: normal;
}
.note-path {
margin-left: 0.5em;
vertical-align: middle;
opacity: 0.5;
}
.note-list-attributes {
flex-grow: 1;
margin-inline-start: 1em;
text-align: right;
font-size: .75em;
opacity: .75;
}
.nested-note-list-item-menu {
margin-inline-start: 8px;
flex-shrink: 0;
}
&.archived {
span.tn-icon + span,
.tn-icon {
opacity: .6;
}
}
&.use-note-color {
span.tn-icon + span,
.nested-note-list:not(.search-results) & .tn-icon,
.rendered-note-attributes {
color: var(--custom-color);
}
}
}
.nested-note-list:not(.search-results) h5 {
span.tn-icon + span,
.note-list-attributes {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
/* List item (search results view) */
.nested-note-list.search-results .nested-note-list-item {
span.tn-icon + span > span {
display: flex;
flex-direction: column-reverse;
align-items: flex-start;
}
small {
line-height: .85em;
}
.note-path {
margin-left: 0;
font-size: .85em;
line-height: .85em;
font-weight: 500;
letter-spacing: .5pt;
}
.tn-icon {
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
width: 1.75em;
height: 1.75em;
margin-inline-end: 12px;
border-radius: 50%;
background: var(--note-icon-custom-background-color, var(--note-list-view-large-icon-background));
font-size: 1.2em;
color: var(--note-icon-custom-color, var(--note-list-view-large-icon-color));
}
h5 .ck-find-result {
background: var(--note-list-view-search-result-highlight-background);
color: var(--note-list-view-search-result-highlight-color);
font-weight: 600;
text-decoration: underline;
}
}
/* Note content preview */
.nested-note-list .note-book-content {
display: none;
outline: 1px solid var(--note-list-view-content-background);
border-radius: 8px;
background-color: var(--note-list-view-content-background);
overflow: hidden;
user-select: text;
font-size: .85rem;
animation: note-preview-show .25s ease-out;
will-change: opacity;
&.note-book-content-ready {
display: block;
}
> .rendered-content > *:last-child {
margin-bottom: 0;
}
&.type-text {
padding: 8px 24px;
.ck-content > *:last-child {
margin-bottom: 0;
}
}
&.type-protectedSession {
padding: 20px;
}
&.type-image {
padding: 0;
}
&.type-pdf {
iframe {
height: 50vh;
}
.file-footer {
padding: 8px;
}
}
&.type-webView {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 50vh;
}
.ck-find-result {
outline: 2px solid var(--note-list-view-content-search-result-highlight-background);
border-radius: 4px;
background: var(--note-list-view-content-search-result-highlight-background);
color: var(--note-list-view-content-search-result-highlight-color);
}
}
.note-content-preview:has(.note-book-content:empty) {
display: none;
}
/* #endregion */
/* #region Grid view */
.note-list.grid-view .note-list-container {
display: flex;

View File

@@ -1,4 +1,5 @@
import "./ListOrGridView.css";
import { Card, CardSection } from "../../react/Card";
import { useEffect, useRef, useState } from "preact/hooks";
@@ -14,6 +15,11 @@ import NoteLink from "../../react/NoteLink";
import { ViewModeProps } from "../interface";
import { Pager, usePagination } from "../Pagination";
import { filterChildNotes, useFilteredNoteIds } from "./utils";
import { JSX } from "preact/jsx-runtime";
import { clsx } from "clsx";
import ActionButton from "../../react/ActionButton";
import linkContextMenuService from "../../../menus/link_context_menu";
import { TargetedMouseEvent } from "preact";
export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
const expandDepth = useExpansionDepth(note);
@@ -33,7 +39,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
{ noteIds.length > 0 && <div class="note-list-wrapper">
{!hasCollectionProperties && <Pager {...pagination} />}
<div class="note-list-container use-tn-links">
<Card className={clsx("nested-note-list", {"search-results": (noteType === "search")})}>
{pageNotes?.map(childNote => (
<ListNoteCard
key={childNote.noteId}
@@ -41,7 +47,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
expandDepth={expandDepth} highlightedTokens={highlightedTokens}
currentLevel={1} includeArchived={includeArchived} />
))}
</div>
</Card>
<Pager {...pagination} />
</div>}
@@ -93,27 +99,52 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan
// Reset expand state if switching to another note, or if user manually toggled expansion state.
useEffect(() => setExpanded(currentLevel <= expandDepth), [ note, currentLevel, expandDepth ]);
let subSections: JSX.Element | undefined = undefined;
if (isExpanded) {
subSections = <>
<CardSection className="note-content-preview">
<NoteContent note={note}
highlightedTokens={highlightedTokens}
noChildrenList
includeArchivedNotes={includeArchived} />
</CardSection>
<NoteChildren note={note}
parentNote={parentNote}
highlightedTokens={highlightedTokens}
currentLevel={currentLevel}
expandDepth={expandDepth}
includeArchived={includeArchived} />
</>
}
return (
<div
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""} ${note.isArchived ? "archived" : ""}`}
<CardSection
className={clsx("nested-note-list-item", "no-tooltip-preview", note.getColorClass(), {
"expanded": isExpanded,
"archived": note.isArchived
})}
subSections={subSections}
subSectionsVisible={isExpanded}
highlightOnHover
data-note-id={note.noteId}
>
<h5 className="note-book-header">
<span
className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
onClick={() => setExpanded(!isExpanded)}
/>
<h5>
<span className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
onClick={() => setExpanded(!isExpanded)}/>
<Icon className="note-icon" icon={note.getIcon()} />
<NoteLink className="note-book-title" notePath={notePath} noPreview showNotePath={parentNote.type === "search"} highlightedTokens={highlightedTokens} />
<NoteLink className="note-book-title"
notePath={notePath}
noPreview
showNotePath={parentNote.type === "search"}
highlightedTokens={highlightedTokens} />
<NoteAttributes note={note} />
<ActionButton className="nested-note-list-item-menu"
icon="bx bx-dots-vertical-rounded" text=""
onClick={(e) => openNoteMenu(notePath, e)}
/>
</h5>
{isExpanded && <>
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList includeArchivedNotes={includeArchived} />
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} currentLevel={currentLevel} expandDepth={expandDepth} includeArchived={includeArchived} />
</>}
</div>
</CardSection>
);
}
@@ -165,6 +196,9 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
const contentRef = useRef<HTMLDivElement>(null);
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
const [ready, setReady] = useState(false);
const [noteType, setNoteType] = useState<string>("none");
useEffect(() => {
content_renderer.getRenderedContent(note, {
trim,
@@ -179,17 +213,19 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
} else {
contentRef.current.replaceChildren();
}
contentRef.current.classList.add(`type-${type}`);
highlightSearch(contentRef.current);
setNoteType(type);
setReady(true);
})
.catch(e => {
console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`);
console.error(e);
contentRef.current?.replaceChildren(t("collections.rendering_error"));
setReady(true);
});
}, [ note, highlightedTokens ]);
return <div ref={contentRef} className="note-book-content" />;
return <div ref={contentRef} className={clsx("note-book-content", `type-${noteType}`, {"note-book-content-ready": ready})} />;
}
function NoteChildren({ note, parentNote, highlightedTokens, currentLevel, expandDepth, includeArchived }: {
@@ -238,3 +274,8 @@ function useExpansionDepth(note: FNote) {
return parseInt(expandDepth, 10);
}
function openNoteMenu(notePath, e: TargetedMouseEvent<HTMLElement>) {
linkContextMenuService.openContextMenu(notePath, e);
e.stopPropagation()
}

View File

@@ -5,7 +5,7 @@
align-items: center;
width: 100%;
max-width: unset;
font-size: 0.8rem;
font-size: 0.8em;
.dropdown-menu {
input.form-control {

View File

@@ -0,0 +1,47 @@
:where(.tn-card) {
--card-border-radius: 8px;
--card-padding-block: 8px;
--card-padding-inline: 16px;
--card-section-gap: 3px;
--card-nested-section-indent: 30px;
}
.tn-card-heading {
margin-bottom: 10px;
font-size: .75rem;
font-weight: 600;
letter-spacing: .4pt;
text-transform: uppercase;
}
.tn-card-body {
display: flex;
flex-direction: column;
gap: var(--card-section-gap);
.tn-card-section {
padding: var(--card-padding-block) var(--card-padding-inline);
border: 1px solid var(--card-border-color, var(--main-border-color));
background: var(--card-background-color);
&:first-of-type {
border-top-left-radius: var(--card-border-radius);
border-top-right-radius: var(--card-border-radius);
}
&:last-of-type {
border-bottom-left-radius: var(--card-border-radius);
border-bottom-right-radius: var(--card-border-radius);
}
&.tn-card-section-nested {
padding-left: calc(var(--card-padding-inline) + var(--card-nested-section-indent) * var(--tn-card-section-nesting-level));
background-color: color-mix(in srgb, var(--card-background-color) calc(100% / (var(--tn-card-section-nesting-level) + 1)) , transparent);
}
&.tn-card-section-highlight-on-hover:hover {
background-color: var(--card-background-hover-color);
transition: background-color .2s ease-out;
}
}
}

View File

@@ -0,0 +1,63 @@
import "./Card.css";
import { ComponentChildren, createContext } from "preact";
import { JSX } from "preact";
import { useContext } from "preact/hooks";
import clsx from "clsx";
// #region Card
export interface CardProps {
className?: string;
heading?: string;
}
export function Card(props: {children: ComponentChildren} & CardProps) {
return <div className={clsx("tn-card", props.className)}>
{props.heading && <h5 class="tn-card-heading">{props.heading}</h5>}
<div className="tn-card-body">
{props.children}
</div>
</div>;
}
// #endregion
// #region Card Section
export interface CardSectionProps {
className?: string;
subSections?: JSX.Element | JSX.Element[];
subSectionsVisible?: boolean;
highlightOnHover?: boolean;
onAction?: () => void;
}
interface CardSectionContextType {
nestingLevel: number;
}
const CardSectionContext = createContext<CardSectionContextType | undefined>(undefined);
export function CardSection(props: {children: ComponentChildren} & CardSectionProps) {
const parentContext = useContext(CardSectionContext);
const nestingLevel = (parentContext && parentContext.nestingLevel + 1) ?? 0;
return <>
<section className={clsx("tn-card-section", props.className, {
"tn-card-section-nested": nestingLevel > 0,
"tn-card-section-highlight-on-hover": props.highlightOnHover || props.onAction
})}
style={{"--tn-card-section-nesting-level": (nestingLevel) ? nestingLevel : null}}
onClick={props.onAction}>
{props.children}
</section>
{props.subSectionsVisible && props.subSections &&
<CardSectionContext.Provider value={{nestingLevel}}>
{props.subSections}
</CardSectionContext.Provider>
}
</>;
}
// #endregion

View File

@@ -1,13 +1,11 @@
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 Alert from "../react/Alert";
import { useNoteLabelWithDefault, useTriliumEvent } from "../react/hooks";
import NoItems from "../react/NoItems";
import RawHtml from "../react/RawHtml";
import { TypeWidgetProps } from "./type_widget";
import "./Book.css";
import { useEffect, useState } from "preact/hooks";
import { ViewTypeOptions } from "../collections/interface";
const VIEW_TYPES: ViewTypeOptions[] = [ "list", "grid", "presentation" ];
@@ -29,12 +27,10 @@ export default function Book({ note }: TypeWidgetProps) {
return (
<>
{shouldDisplayNoChildrenWarning && (
<>
<CollectionProperties note={note} />
<NoItems icon="bx bx-collection" text={t("book.no_children_help")} />
</>
<Alert type="warning" className="note-detail-book-empty-help">
<RawHtml html={t("book.no_children_help")} />
</Alert>
)}
</>
);
)
}

230
pnpm-lock.yaml generated
View File

@@ -200,6 +200,9 @@ 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)
@@ -281,9 +284,12 @@ importers:
knockout:
specifier: 3.5.1
version: 3.5.1
maplibre-gl:
specifier: 5.18.0
version: 5.18.0
leaflet:
specifier: 1.9.4
version: 1.9.4
leaflet-gpx:
specifier: 2.2.0
version: 2.2.0
mark.js:
specifier: 8.11.1
version: 8.11.1
@@ -336,6 +342,12 @@ 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
@@ -3974,8 +3986,8 @@ packages:
resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==}
engines: {node: '>= 0.6'}
'@mapbox/point-geometry@1.1.0':
resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==}
'@mapbox/point-geometry@0.1.0':
resolution: {integrity: sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==}
'@mapbox/tiny-sdf@2.0.7':
resolution: {integrity: sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==}
@@ -3983,26 +3995,24 @@ packages:
'@mapbox/unitbezier@0.0.1':
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
'@mapbox/vector-tile@2.0.4':
resolution: {integrity: sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==}
'@mapbox/vector-tile@1.3.1':
resolution: {integrity: sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==}
'@mapbox/whoots-js@3.1.0':
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
engines: {node: '>=6.0.0'}
'@maplibre/geojson-vt@5.0.4':
resolution: {integrity: sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ==}
'@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/maplibre-gl-style-spec@24.4.1':
resolution: {integrity: sha512-UKhA4qv1h30XT768ccSv5NjNCX+dgfoq2qlLVmKejspPcSQTYD4SrVucgqegmYcKcmwf06wcNAa/kRd0NHWbUg==}
'@maplibre/maplibre-gl-style-spec@23.3.0':
resolution: {integrity: sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==}
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==}
@@ -5777,6 +5787,9 @@ 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==}
@@ -5816,6 +5829,12 @@ 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==}
@@ -5825,6 +5844,12 @@ 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==}
@@ -5885,6 +5910,9 @@ 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==}
@@ -9098,6 +9126,9 @@ 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.*}
@@ -9229,6 +9260,10 @@ 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'}
@@ -10371,6 +10406,12 @@ 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'}
@@ -10722,8 +10763,8 @@ packages:
resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==}
engines: {node: '>=6'}
maplibre-gl@5.18.0:
resolution: {integrity: sha512-UtWxPBpHuFvEkM+5FVfcFG9ZKEWZQI6+PZkvLErr8Zs5ux+O7/KQ3JjSUvAfOlMeMgd/77qlHpOw0yHL7JU5cw==}
maplibre-gl@5.6.1:
resolution: {integrity: sha512-TTSfoTaF7RqKUR9wR5qDxCHH2J1XfZ1E85luiLOx0h8r50T/LnwAwwfV0WVNh9o8dA7rwt57Ucivf1emyeukXg==}
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
mark.js@8.11.1:
@@ -11827,8 +11868,8 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pbf@4.0.1:
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
pbf@3.3.0:
resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==}
hasBin: true
pdfjs-dist@5.4.530:
@@ -14808,6 +14849,9 @@ 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.
@@ -15015,6 +15059,11 @@ 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}
@@ -15972,8 +16021,6 @@ 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:
@@ -16114,16 +16161,12 @@ 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:
@@ -16316,8 +16359,6 @@ 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:
@@ -16327,8 +16368,6 @@ 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:
@@ -16338,8 +16377,6 @@ 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:
@@ -16373,6 +16410,8 @@ 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:
@@ -16429,6 +16468,8 @@ 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:
@@ -16453,8 +16494,6 @@ 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:
@@ -16529,8 +16568,6 @@ 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:
@@ -16590,6 +16627,8 @@ 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:
@@ -16713,6 +16752,8 @@ 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:
@@ -16725,6 +16766,8 @@ 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:
@@ -16733,6 +16776,8 @@ 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:
@@ -16787,6 +16832,8 @@ 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:
@@ -16894,6 +16941,8 @@ 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:
@@ -16906,6 +16955,8 @@ 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:
@@ -16953,6 +17004,8 @@ 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:
@@ -16965,6 +17018,8 @@ 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:
@@ -17075,6 +17130,8 @@ 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:
@@ -17094,6 +17151,8 @@ 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:
@@ -19101,23 +19160,25 @@ snapshots:
'@mapbox/jsonlint-lines-primitives@2.0.2': {}
'@mapbox/point-geometry@1.1.0': {}
'@mapbox/point-geometry@0.1.0': {}
'@mapbox/tiny-sdf@2.0.7': {}
'@mapbox/unitbezier@0.0.1': {}
'@mapbox/vector-tile@2.0.4':
'@mapbox/vector-tile@1.3.1':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@types/geojson': 7946.0.16
pbf: 4.0.1
'@mapbox/point-geometry': 0.1.0
'@mapbox/whoots-js@3.1.0': {}
'@maplibre/geojson-vt@5.0.4': {}
'@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/maplibre-gl-style-spec@24.4.1':
'@maplibre/maplibre-gl-style-spec@23.3.0':
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/unitbezier': 0.0.1
@@ -19127,20 +19188,6 @@ 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': {}
@@ -21124,6 +21171,10 @@ 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': {}
@@ -21160,6 +21211,14 @@ 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
@@ -21168,6 +21227,14 @@ 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
@@ -21234,6 +21301,8 @@ snapshots:
'@types/parse-json@4.0.2': {}
'@types/pbf@3.0.5': {}
'@types/postcss-import@14.0.3':
dependencies:
postcss: 8.5.6
@@ -25463,6 +25532,8 @@ 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: {}
@@ -25630,6 +25701,12 @@ 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: {}
@@ -26887,6 +26964,10 @@ snapshots:
leac@0.6.0: {}
leaflet-gpx@2.2.0: {}
leaflet@1.9.4: {}
less@4.1.3:
dependencies:
copy-anything: 2.0.6
@@ -27303,30 +27384,34 @@ snapshots:
dependencies:
p-defer: 1.0.0
maplibre-gl@5.18.0:
maplibre-gl@5.6.1:
dependencies:
'@mapbox/geojson-rewind': 0.5.2
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/point-geometry': 1.1.0
'@mapbox/point-geometry': 0.1.0
'@mapbox/tiny-sdf': 2.0.7
'@mapbox/unitbezier': 0.0.1
'@mapbox/vector-tile': 2.0.4
'@mapbox/vector-tile': 1.3.1
'@mapbox/whoots-js': 3.1.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
'@maplibre/maplibre-gl-style-spec': 23.3.0
'@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: 4.0.1
pbf: 3.3.0
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: {}
@@ -28745,8 +28830,9 @@ snapshots:
pathe@2.0.3: {}
pbf@4.0.1:
pbf@3.3.0:
dependencies:
ieee754: 1.2.1
resolve-protobuf-schema: 2.1.0
pdfjs-dist@5.4.530:
@@ -32210,6 +32296,12 @@ 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
@@ -32573,6 +32665,10 @@ snapshots:
dependencies:
isexe: 2.0.0
which@4.0.0:
dependencies:
isexe: 3.1.1
which@5.0.0:
dependencies:
isexe: 3.1.1