From b25f3094b7a6a4bb3d1a4a262c9bbdca7efa8317 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Sep 2025 22:48:00 +0300 Subject: [PATCH] refactor(react/collections): reintroduce gpx tracks --- .../src/widgets/collections/geomap/index.tsx | 46 +++++++++++++++++-- .../src/widgets/collections/geomap/marker.tsx | 18 +++++++- .../widgets/view_widgets/geo_view/index.ts | 12 ----- .../widgets/view_widgets/geo_view/markers.ts | 38 --------------- 4 files changed, 59 insertions(+), 55 deletions(-) delete mode 100644 apps/client/src/widgets/view_widgets/geo_view/markers.ts diff --git a/apps/client/src/widgets/collections/geomap/index.tsx b/apps/client/src/widgets/collections/geomap/index.tsx index cc17d5691..4651abddb 100644 --- a/apps/client/src/widgets/collections/geomap/index.tsx +++ b/apps/client/src/widgets/collections/geomap/index.tsx @@ -1,11 +1,11 @@ import Map from "./map"; import "./index.css"; import { ViewModeProps } from "../interface"; -import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useSpacedUpdate, useTriliumEvent } from "../../react/hooks"; +import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useSpacedUpdate, useTriliumEvent } from "../../react/hooks"; import { DEFAULT_MAP_LAYER_NAME } from "./map_layer"; -import { divIcon, LatLng, LeafletMouseEvent } from "leaflet"; +import { divIcon, GPXOptions, LatLng, LeafletMouseEvent } from "leaflet"; import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; -import Marker from "./marker"; +import Marker, { GpxTrack } from "./marker"; import froca from "../../../services/froca"; import FNote from "../../../entities/fnote"; import markerIcon from "leaflet/dist/images/marker-icon.png"; @@ -15,6 +15,7 @@ import { createNewNote, moveMarker } from "./api"; import openContextMenu, { openMapContextMenu } from "./context_menu"; import toast from "../../../services/toast"; import { t } from "../../../services/i18n"; +import server from "../../../services/server"; const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659]; const DEFAULT_ZOOM = 2; @@ -99,7 +100,11 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM onContextMenu={onContextMenu} scale={hasScale} > - {notes.map(note => )} + {notes.map(note => ( + note.mime !== "application/gpx+xml" + ? + : + ))} ); @@ -148,6 +153,39 @@ function NoteMarker({ note, editable }: { note: FNote, editable: boolean }) { /> } +function NoteGpxTrack({ note }: { note: FNote }) { + const [ xmlString, setXmlString ] = useState(); + const blob = useNoteBlob(note); + + useEffect(() => { + server.get(`notes/${note.noteId}/open`, undefined, true).then(xmlResponse => { + if (xmlResponse instanceof Uint8Array) { + setXmlString(new TextDecoder().decode(xmlResponse)); + } else { + setXmlString(xmlResponse); + } + }); + }, [ blob ]); + + // React to changes + const color = useNoteLabel(note, "color"); + const iconClass = useNoteLabel(note, "iconClass"); + + const options = useMemo(() => ({ + markers: { + startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title), + endIcon: buildIcon("bxs-flag-checkered"), + wptIcons: { + "": buildIcon("bx bx-pin") + } + }, + polyline_options: { + color: note.getLabelValue("color") ?? "blue" + } + }), [ color, iconClass ]); + return xmlString && +} + function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string) { let html = /*html*/`\ diff --git a/apps/client/src/widgets/collections/geomap/marker.tsx b/apps/client/src/widgets/collections/geomap/marker.tsx index 7f9a7cda9..2a2142d1c 100644 --- a/apps/client/src/widgets/collections/geomap/marker.tsx +++ b/apps/client/src/widgets/collections/geomap/marker.tsx @@ -1,6 +1,7 @@ import { useContext, useEffect } from "preact/hooks"; import { ParentMap } from "./map"; -import { DivIcon, Icon, LatLng, Marker as LeafletMarker, LeafletMouseEvent, marker, MarkerOptions } from "leaflet"; +import { DivIcon, GPX, GPXOptions, Icon, LatLng, Marker as LeafletMarker, LeafletMouseEvent, marker, MarkerOptions } from "leaflet"; +import "leaflet-gpx"; export interface MarkerProps { coordinates: [ number, number ]; @@ -53,3 +54,18 @@ export default function Marker({ coordinates, icon, draggable, onClick, onDragge return (
) } + +export function GpxTrack({ gpxXmlString, options }: { gpxXmlString: string, options: GPXOptions }) { + const parentMap = useContext(ParentMap); + + useEffect(() => { + if (!parentMap) return; + + const track = new GPX(gpxXmlString, options); + track.addTo(parentMap); + + return () => track.removeFrom(parentMap); + }, [ parentMap, gpxXmlString, options ]); + + return
; +} diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts index a9bf023f3..46f535afe 100644 --- a/apps/client/src/widgets/view_widgets/geo_view/index.ts +++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts @@ -83,18 +83,6 @@ export default class GeoView extends ViewMode { this.currentMarkerData = {}; const notes = await this.parentNote.getSubtreeNotes(); const draggable = !this.isReadOnly; - for (const childNote of notes) { - if (childNote.mime === "application/gpx+xml") { - const track = await processNoteWithGpxTrack(this.map, childNote); - this.currentTrackData[childNote.noteId] = track; - continue; - } - - if (latLng) { - const marker = processNoteWithMarker(this.map, childNote, latLng, draggable); - this.currentMarkerData[childNote.noteId] = marker; - } - } } #changeState(newState: State) { diff --git a/apps/client/src/widgets/view_widgets/geo_view/markers.ts b/apps/client/src/widgets/view_widgets/geo_view/markers.ts deleted file mode 100644 index 0e8e9f4f1..000000000 --- a/apps/client/src/widgets/view_widgets/geo_view/markers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { marker, latLng, divIcon, Map, type Marker } from "leaflet"; -import type FNote from "../../../entities/fnote.js"; -import openContextMenu from "./context_menu.js"; -import server from "../../../services/server.js"; -import { moveMarker } from "./editing.js"; -import L from "leaflet"; - -let gpxLoaded = false; - -export async function processNoteWithGpxTrack(map: Map, note: FNote) { - if (!gpxLoaded) { - const GPX = await import("leaflet-gpx"); - gpxLoaded = true; - } - - const xmlResponse = await server.get(`notes/${note.noteId}/open`, undefined, true); - let stringResponse: string; - if (xmlResponse instanceof Uint8Array) { - stringResponse = new TextDecoder().decode(xmlResponse); - } else { - stringResponse = xmlResponse; - } - - const track = new L.GPX(stringResponse, { - markers: { - startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title), - endIcon: buildIcon("bxs-flag-checkered"), - wptIcons: { - "": buildIcon("bx bx-pin") - } - }, - polyline_options: { - color: note.getLabelValue("color") ?? "blue" - } - }); - track.addTo(map); - return track; -}