trails/apps/planner/app/lib/use-gpx-drop.ts
Ullrich Schäfer 9e2ca5595e
Centralize waypoint Yjs serialization in waypointFromYMap/waypointToYMap
Introduce waypoint-ymap.ts with typed helpers so all Yjs↔Waypoint
conversions go through one place. New Waypoint fields now only need
to be added once rather than in every consumer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:02:27 +02:00

73 lines
2.3 KiB
TypeScript

import { useState, useRef, useCallback } from "react";
import * as Y from "yjs";
import { useTranslation } from "react-i18next";
import { parseGpxAsync, extractWaypoints } from "@trails-cool/gpx";
import type { YjsState } from "~/lib/use-yjs";
import { waypointToYMap } from "~/lib/waypoint-ymap";
export function useGpxDrop(yjs: YjsState, onImportError?: (message: string) => void) {
const { t } = useTranslation("planner");
const [draggingOver, setDraggingOver] = useState(false);
const dragCounterRef = useRef(0);
const handleDragEnter = useCallback((e: React.DragEvent) => {
e.preventDefault();
dragCounterRef.current++;
if (dragCounterRef.current === 1) setDraggingOver(true);
}, []);
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault();
dragCounterRef.current--;
if (dragCounterRef.current === 0) setDraggingOver(false);
}, []);
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
}, []);
const handleDrop = useCallback(async (e: React.DragEvent) => {
e.preventDefault();
dragCounterRef.current = 0;
setDraggingOver(false);
const file = e.dataTransfer.files[0];
if (!file) return;
if (!file.name.toLowerCase().endsWith(".gpx")) {
onImportError?.(t("importGpxError"));
return;
}
try {
const text = await file.text();
const gpxData = await parseGpxAsync(text);
const newWaypoints = extractWaypoints(gpxData);
if (newWaypoints.length < 2) return;
if (!window.confirm(t("replaceRouteConfirm"))) return;
yjs.doc.transact(() => {
yjs.waypoints.delete(0, yjs.waypoints.length);
for (const wp of newWaypoints) {
yjs.waypoints.push([waypointToYMap(wp)]);
}
yjs.noGoAreas.delete(0, yjs.noGoAreas.length);
for (const area of gpxData.noGoAreas) {
const yMap = new Y.Map();
yMap.set("points", area.points);
yjs.noGoAreas.push([yMap]);
}
if (gpxData.description) {
yjs.notes.delete(0, yjs.notes.length);
yjs.notes.insert(0, gpxData.description);
}
}, "local");
} catch {
onImportError?.(t("importGpxError"));
}
}, [yjs, t, onImportError]);
return { draggingOver, handleDragEnter, handleDragLeave, handleDragOver, handleDrop };
}