trails/apps/planner/app/components/SessionView.tsx
Ullrich Schäfer 14b179bb0c Fix waypoint loading in Edit in Planner flow
Three issues fixed:

1. GPX parser used browser DOMParser which doesn't exist in Node/Vite SSR.
   Added async parseGpxAsync() using linkedom for server-side parsing.

2. Server-side session initialization stored waypoints in a Yjs doc
   instance separate from the Vite WebSocket plugin's doc store.
   Moved waypoint initialization to client-side: API returns parsed
   waypoints, client adds them to Yjs after sync.

3. GPX was encoded in URL params causing HTTP 431. Now the Journal
   creates a Planner session via API (POST body), and only passes
   compact waypoint coordinates in URL params.

Verified: Journal route → Edit in Planner → 4 waypoints loaded,
route computed (79.3km), elevation profile, Save to Journal ready.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00:00

107 lines
3.9 KiB
TypeScript

import { Suspense, lazy, useState, useCallback } from "react";
import { useYjs } from "~/lib/use-yjs";
import { useRouting } from "~/lib/use-routing";
import { ProfileSelector } from "~/components/ProfileSelector";
import { ExportButton } from "~/components/ExportButton";
import { SaveToJournalButton } from "~/components/SaveToJournalButton";
import { YjsDebugPanel } from "~/components/YjsDebugPanel";
const PlannerMap = lazy(() =>
import("~/components/PlannerMap").then((m) => ({ default: m.PlannerMap })),
);
const WaypointSidebar = lazy(() =>
import("~/components/WaypointSidebar").then((m) => ({ default: m.WaypointSidebar })),
);
const ElevationChart = lazy(() =>
import("~/components/ElevationChart").then((m) => ({ default: m.ElevationChart })),
);
interface SessionViewProps {
sessionId: string;
callbackUrl?: string;
callbackToken?: string;
returnUrl?: string;
initialWaypoints?: Array<{ lat: number; lon: number; name?: string }>;
}
export function SessionView({ sessionId, callbackUrl, callbackToken, returnUrl, initialWaypoints }: SessionViewProps) {
const yjs = useYjs(sessionId, initialWaypoints);
const { isHost, computing, routeStats, requestRoute } = useRouting(yjs);
const [highlightPosition, setHighlightPosition] = useState<[number, number] | null>(null);
const handleElevationHover = useCallback((pos: [number, number] | null) => {
setHighlightPosition(pos);
}, []);
if (!yjs) {
return (
<div className="flex h-full items-center justify-center">
<p className="text-gray-500">Connecting...</p>
</div>
);
}
return (
<>
<header className="flex items-center justify-between border-b border-gray-200 px-4 py-2">
<div className="flex items-center gap-4">
<h1 className="text-lg font-semibold text-gray-900">trails.cool Planner</h1>
<ProfileSelector yjs={yjs} />
</div>
<div className="flex items-center gap-3">
{callbackUrl && callbackToken && (
<SaveToJournalButton
yjs={yjs}
callbackUrl={callbackUrl}
callbackToken={callbackToken}
returnUrl={returnUrl}
/>
)}
<ExportButton yjs={yjs} />
{computing && (
<span className="text-xs text-blue-600">Computing route...</span>
)}
{isHost && (
<span className="rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700">
Host
</span>
)}
<span className="text-sm text-gray-500">
{yjs.connected ? "Connected" : "Connecting..."} · {sessionId.slice(0, 8)}
</span>
</div>
</header>
<div className="flex flex-1 overflow-hidden">
<main className="flex-1 flex flex-col">
<div className="relative flex-1">
{computing && (
<div className="absolute inset-x-0 top-0 z-[1000]">
<div className="h-1 w-full overflow-hidden bg-blue-100">
<div className="h-full w-1/3 animate-[slide_1s_ease-in-out_infinite] bg-blue-500" />
</div>
</div>
)}
<Suspense
fallback={
<div className="flex h-full items-center justify-center bg-gray-100 text-gray-500">
Loading map...
</div>
}
>
<PlannerMap yjs={yjs} onRouteRequest={requestRoute} highlightPosition={highlightPosition} />
</Suspense>
</div>
<Suspense fallback={null}>
<ElevationChart yjs={yjs} onHover={handleElevationHover} />
</Suspense>
</main>
<aside className="w-72 border-l border-gray-200 bg-white">
<Suspense fallback={null}>
<WaypointSidebar yjs={yjs} routeStats={routeStats} />
</Suspense>
</aside>
</div>
<YjsDebugPanel yjs={yjs} />
</>
);
}