mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 22:46:32 +00:00
0331d9950f
A tour can now be a ride (HOR-01). The planner offers "Horse ride" as a tour type with its own horseshoe, and a Horse routes card beside the waypoints looks, when asked, for the route=horse relations OpenStreetMap knows in the map view. A route opens in a dialog that shows it on a map in the sections it is made of, split at forks, gaps and crossings, so nothing is ever joined across a gap. The rider picks a section and its direction, sees what the horse=* tags along it say (open, limited, closed, or unknown where untagged) and whether it is signposted the other way, and takes it as one undoable step. A section that does not start where the ride ends is only joined by a straight manual stretch once the rider switched that on. Nothing of a ride is routed: every leg follows a section or is drawn by hand, and the waypoint list and the map say which (a section as a solid line with its route, a manual stretch dashed and marked unchecked). A point at a section stays where the route has it, a ride works out no riding time, and distance, heights, day assignment, GPX import and export, the 3D view and the reversed ride keep working with each leg's origin saved and opened again. A failed lookup leaves the ride and the routes already found as they were. The server reads the routes through the shared Overpass client (GET /api/trips/:tripId/tours/horse-routes, Tours addon, trip access), keeps an answer twelve hours per grid-aligned view, and the tour contract refuses a routed leg, a via ferrata or a riding time on a ride and a horse route section on any other type. A migration enables the horse tour type.
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import { useCallback, useMemo } from 'react';
|
|
import { validDraft, type DraftPayload } from './tourPlannerDraft';
|
|
import { normalizePlannerWaypoints } from './tourPlannerWaypoints';
|
|
|
|
/**
|
|
* The planner's draft kept in this browser between visits, one per trip: the
|
|
* draft found when the planner opens (null for none, or one that does not
|
|
* pass the check), keeping the current one, and forgetting it once it is
|
|
* saved or thrown away. Storage that is full or blocked keeps nothing.
|
|
*/
|
|
export function useTourDraftStorage(tripId: number | string) {
|
|
const storageKey = `tour-draft-${tripId}`;
|
|
const restored = useMemo(() => {
|
|
try {
|
|
const parsed = JSON.parse(localStorage.getItem(storageKey) || 'null') as unknown;
|
|
return validDraft(parsed)
|
|
? { ...parsed, waypoints: normalizePlannerWaypoints(parsed.waypoints, parsed.tourType) }
|
|
: null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}, [storageKey]);
|
|
|
|
const keep = useCallback(
|
|
(draft: DraftPayload) => {
|
|
try {
|
|
localStorage.setItem(storageKey, JSON.stringify(draft));
|
|
} catch {
|
|
// Full or blocked storage: the draft lives on in the page until it is saved.
|
|
}
|
|
},
|
|
[storageKey]
|
|
);
|
|
|
|
const forget = useCallback(() => {
|
|
try {
|
|
localStorage.removeItem(storageKey);
|
|
} catch {
|
|
// Blocked storage held nothing to forget.
|
|
}
|
|
}, [storageKey]);
|
|
|
|
return { restored, keep, forget };
|
|
}
|