mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 15:51:00 +00:00
feat(ferratas): keep loaded via ferratas in a tile cache on server and device, group segments, own look and count badge
This commit is contained in:
@@ -2,62 +2,102 @@ import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type { Ferrata, FerrataBbox } from '@trek/shared';
|
||||
|
||||
import {
|
||||
type FerrataGroup,
|
||||
ferrataGroupOf,
|
||||
ferrataGroupsInView,
|
||||
ferrataPointsBbox,
|
||||
groupFerratas,
|
||||
} from './ferrataGroups';
|
||||
import type { FerrataSelectHandler } from './useFerrataLayerGL';
|
||||
import { type FerrataStatus, useTripFerratas } from './useTripFerratas';
|
||||
|
||||
/**
|
||||
* The via ferrata layer as a map hands it around: whether it is on, the switch
|
||||
* the layer pill shows, what the status pill says, the lines to draw and what
|
||||
* a click on one of them does. Both flat renderers and the 3D terrain map take
|
||||
* this one object, so the three cannot drift apart on what the layer is.
|
||||
* the layer pill shows with its count badge, the ferratas to draw (grouped
|
||||
* from OSM's segments), the one open in the card, and what a click on a line
|
||||
* does. Both flat renderers and the 3D terrain map take this one object, so
|
||||
* the three cannot drift apart on what the layer is.
|
||||
*/
|
||||
export interface FerrataMapLayer {
|
||||
active: boolean;
|
||||
onToggle: () => void;
|
||||
status: FerrataStatus;
|
||||
ferratas: readonly Ferrata[];
|
||||
/** How many ferratas reach into the view on screen. */
|
||||
count: number;
|
||||
groups: readonly FerrataGroup[];
|
||||
/** The ferrata open in the card, drawn highlighted. */
|
||||
selectedId: string | null;
|
||||
onSelect: FerrataSelectHandler;
|
||||
}
|
||||
|
||||
/** The ferrata open in the card, and the OSM way that was picked on it. */
|
||||
export interface FerrataSelection {
|
||||
group: FerrataGroup;
|
||||
segment: Ferrata;
|
||||
}
|
||||
|
||||
/** The box around every point of every line the stage hands over, or null for none. */
|
||||
export function ferrataAreaOf(lines: ReadonlyArray<ReadonlyArray<readonly [number, number]>>): FerrataBbox | null {
|
||||
return ferrataPointsBbox(lines.flat());
|
||||
}
|
||||
|
||||
/**
|
||||
* The via ferrata layer of one map surface: off by default, and nothing is
|
||||
* asked for while it is off.
|
||||
* read or asked for while it is off.
|
||||
*
|
||||
* `onViewportChange` takes the visible area from whichever map is on screen
|
||||
* (the flat planner map or the 3D terrain). While the layer is off the area is
|
||||
* only remembered, not put into state, so panning a map with the layer off
|
||||
* re-renders nothing; switching it on starts from the last area the map
|
||||
* reported instead of waiting for the next pan. `selected` is the ferrata
|
||||
* whose card is open, closed again when the layer is switched off.
|
||||
* reported instead of waiting for the next pan. `areaLines` are the lines the
|
||||
* trip's area is made of (tour routes, waypoints, places), prefetched once
|
||||
* the layer is on. The selection names the OSM way that was clicked, so it
|
||||
* follows its ferrata when more segments load and join it, and closes when the
|
||||
* layer is switched off.
|
||||
*/
|
||||
export function useFerrataMapLayer(tripId: number | null | undefined) {
|
||||
export function useFerrataMapLayer(
|
||||
tripId: number | null | undefined,
|
||||
areaLines: ReadonlyArray<ReadonlyArray<readonly [number, number]>> = []
|
||||
) {
|
||||
const [active, setActive] = useState(false);
|
||||
const [bbox, setBbox] = useState<FerrataBbox | null>(null);
|
||||
const [selected, setSelected] = useState<Ferrata | null>(null);
|
||||
const [selectedSegment, setSelectedSegment] = useState<string | null>(null);
|
||||
const lastArea = useRef<FerrataBbox | null>(null);
|
||||
const activeRef = useRef(false);
|
||||
const { ferratas, status } = useTripFerratas(tripId, bbox, active);
|
||||
const area = ferrataAreaOf(areaLines);
|
||||
const { ferratas, status } = useTripFerratas(tripId, bbox, active, area);
|
||||
const groups = useMemo(() => groupFerratas(ferratas), [ferratas]);
|
||||
const count = active ? ferrataGroupsInView(groups, bbox) : 0;
|
||||
|
||||
const onViewportChange = useCallback((area: FerrataBbox) => {
|
||||
lastArea.current = area;
|
||||
if (activeRef.current) setBbox(area);
|
||||
const onViewportChange = useCallback((next: FerrataBbox) => {
|
||||
lastArea.current = next;
|
||||
if (activeRef.current) setBbox(next);
|
||||
}, []);
|
||||
|
||||
const onToggle = useCallback(() => {
|
||||
const next = !activeRef.current;
|
||||
activeRef.current = next;
|
||||
setActive(next);
|
||||
setSelected(null);
|
||||
setSelectedSegment(null);
|
||||
if (next) setBbox(lastArea.current);
|
||||
}, []);
|
||||
|
||||
const close = useCallback(() => setSelected(null), []);
|
||||
const onSelect = useCallback<FerrataSelectHandler>((ferrata) => setSelected(ferrata), []);
|
||||
const close = useCallback(() => setSelectedSegment(null), []);
|
||||
const onSelect = useCallback<FerrataSelectHandler>((_group, segment) => setSelectedSegment(segment.id), []);
|
||||
|
||||
const selected = useMemo<FerrataSelection | null>(() => {
|
||||
if (!active) return null;
|
||||
const group = ferrataGroupOf(groups, selectedSegment);
|
||||
const segment = group?.segments.find((candidate) => candidate.id === selectedSegment);
|
||||
return group && segment ? { group, segment } : null;
|
||||
}, [active, groups, selectedSegment]);
|
||||
const selectedId = selected?.group.id ?? null;
|
||||
|
||||
const layer = useMemo<FerrataMapLayer>(
|
||||
() => ({ active, onToggle, status, ferratas, onSelect }),
|
||||
[active, onToggle, status, ferratas, onSelect]
|
||||
() => ({ active, onToggle, status, count, groups, selectedId, onSelect }),
|
||||
[active, onToggle, status, count, groups, selectedId, onSelect]
|
||||
);
|
||||
|
||||
return { layer, onViewportChange, selected: active ? selected : null, close };
|
||||
return { layer, onViewportChange, selected, close };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user