Verschiebemodus eingebaut
This commit is contained in:
@@ -155,6 +155,7 @@ export function App() {
|
||||
const [routeLoading, setRouteLoading] = useState(false);
|
||||
const [routeError, setRouteError] = useState<string | null>(null);
|
||||
const [mapReady, setMapReady] = useState(false);
|
||||
const [followGpsPosition, setFollowGpsPosition] = useState(false);
|
||||
const [mapFocusRequest, setMapFocusRequest] = useState<{
|
||||
key: string;
|
||||
coordinate: Coordinate;
|
||||
@@ -273,6 +274,11 @@ export function App() {
|
||||
setActiveTool("route");
|
||||
setWorkspaceSheetState("half");
|
||||
}, [anchorWatch.phase, compass, courseAssistant, gps, route]);
|
||||
useEffect(() => {
|
||||
if (!courseAssistant.active) {
|
||||
setFollowGpsPosition(false);
|
||||
}
|
||||
}, [courseAssistant.active]);
|
||||
const anchorPanelVisible = activeTool === "anchor";
|
||||
const closeWorkspace = useCallback(() => setActiveTool(null), []);
|
||||
const selectTool = useCallback((tool: NavigationToolId) => {
|
||||
@@ -830,8 +836,11 @@ export function App() {
|
||||
waypoints={waypoints}
|
||||
pickMode={pickMode}
|
||||
route={route}
|
||||
navigationActive={courseAssistant.active}
|
||||
guidanceActive={courseAssistant.active && Boolean(activeGuidance)}
|
||||
guidanceTarget={activeGuidance?.lookaheadPoint ?? null}
|
||||
followPosition={followGpsPosition}
|
||||
onFollowPositionChange={setFollowGpsPosition}
|
||||
anchorPoint={anchorWatch.anchorPoint}
|
||||
anchorAlarmRadiusM={anchorWatch.anchorPoint ? anchorWatch.settings.alarmRadiusM : null}
|
||||
anchorWatchActive={anchorWatch.phase === "armed"}
|
||||
|
||||
@@ -16,8 +16,11 @@ type MapViewProps = {
|
||||
waypoints?: Coordinate[];
|
||||
pickMode: "start" | "destination" | "waypoint" | null;
|
||||
route: RouteResult | null;
|
||||
navigationActive?: boolean;
|
||||
guidanceActive?: boolean;
|
||||
guidanceTarget?: Coordinate | null;
|
||||
followPosition?: boolean;
|
||||
onFollowPositionChange?: (follow: boolean) => void;
|
||||
anchorPoint?: Coordinate | null;
|
||||
anchorAlarmRadiusM?: number | null;
|
||||
anchorWatchActive?: boolean;
|
||||
@@ -71,8 +74,11 @@ export function MapView({
|
||||
waypoints = [],
|
||||
pickMode,
|
||||
route,
|
||||
navigationActive = false,
|
||||
guidanceActive = false,
|
||||
guidanceTarget = null,
|
||||
followPosition = false,
|
||||
onFollowPositionChange,
|
||||
anchorPoint = null,
|
||||
anchorAlarmRadiusM = null,
|
||||
anchorWatchActive = false,
|
||||
@@ -98,6 +104,7 @@ export function MapView({
|
||||
const accuracyMRef = useRef(accuracyM);
|
||||
const guidanceActiveRef = useRef(guidanceActive);
|
||||
const guidanceTargetRef = useRef(guidanceTarget);
|
||||
const onFollowPositionChangeRef = useRef(onFollowPositionChange);
|
||||
const anchorPointRef = useRef(anchorPoint);
|
||||
const anchorAlarmRadiusMRef = useRef(anchorAlarmRadiusM);
|
||||
const anchorWatchActiveRef = useRef(anchorWatchActive);
|
||||
@@ -114,6 +121,10 @@ export function MapView({
|
||||
applyMapFocusRequest(mapRef.current, focusRequest);
|
||||
}, [focusRequest]);
|
||||
|
||||
useEffect(() => {
|
||||
onFollowPositionChangeRef.current = onFollowPositionChange;
|
||||
}, [onFollowPositionChange]);
|
||||
|
||||
const clearContactFeatures = useCallback((map: MapLibreMap) => {
|
||||
const hadContactFeatures = contactFeaturesRef.current.length > 0;
|
||||
contactFeaturesRef.current = [];
|
||||
@@ -174,6 +185,9 @@ export function MapView({
|
||||
|
||||
map.addControl(new maplibregl.AttributionControl({ compact: true }), "bottom-right");
|
||||
map.addControl(new maplibregl.ScaleControl({ unit: "nautical" }), "bottom-left");
|
||||
map.on("dragstart", () => {
|
||||
onFollowPositionChangeRef.current?.(false);
|
||||
});
|
||||
mapRef.current = map;
|
||||
|
||||
map.on("load", () => {
|
||||
@@ -937,7 +951,15 @@ export function MapView({
|
||||
const source = map.getSource("user-position") as GeoJSONSource | undefined;
|
||||
source?.setData(userPositionFeatures(position, accuracyM));
|
||||
|
||||
if (!hasCenteredOnFirstFixRef.current) {
|
||||
if (followPosition) {
|
||||
hasCenteredOnFirstFixRef.current = true;
|
||||
map.easeTo({
|
||||
center: [position.lon, position.lat],
|
||||
zoom: Math.max(map.getZoom(), 13),
|
||||
duration: 500,
|
||||
essential: true
|
||||
});
|
||||
} else if (!hasCenteredOnFirstFixRef.current) {
|
||||
hasCenteredOnFirstFixRef.current = true;
|
||||
map.easeTo({
|
||||
center: [position.lon, position.lat],
|
||||
@@ -946,7 +968,7 @@ export function MapView({
|
||||
essential: true
|
||||
});
|
||||
}
|
||||
}, [accuracyM, position]);
|
||||
}, [accuracyM, followPosition, position]);
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current;
|
||||
@@ -1075,6 +1097,7 @@ export function MapView({
|
||||
data-pick-mode={pickMode ?? "none"}
|
||||
data-has-route={Boolean(route)}
|
||||
data-guidance-active={guidanceActive}
|
||||
data-follow-position={followPosition}
|
||||
>
|
||||
<div ref={containerRef} className="map-container" data-testid="map-container" />
|
||||
{pickMode && (
|
||||
@@ -1101,9 +1124,43 @@ export function MapView({
|
||||
<button
|
||||
className="map-tool locate-toggle"
|
||||
type="button"
|
||||
title="Position zentrieren"
|
||||
aria-label="Position zentrieren"
|
||||
onClick={() => mapRef.current?.flyTo({ center: [position.lon, position.lat], zoom: 13 })}
|
||||
title={
|
||||
navigationActive
|
||||
? followPosition
|
||||
? "GPS-Folge-Modus beenden"
|
||||
: "GPS-Folge-Modus aktivieren"
|
||||
: "Position zentrieren"
|
||||
}
|
||||
aria-label={
|
||||
navigationActive
|
||||
? followPosition
|
||||
? "GPS-Folge-Modus beenden"
|
||||
: "GPS-Folge-Modus aktivieren"
|
||||
: "Position zentrieren"
|
||||
}
|
||||
aria-pressed={navigationActive ? followPosition : undefined}
|
||||
data-active={navigationActive && followPosition}
|
||||
onClick={() => {
|
||||
if (navigationActive && onFollowPositionChange) {
|
||||
const next = !followPosition;
|
||||
onFollowPositionChange(next);
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (navigationActive) {
|
||||
mapRef.current?.flyTo({
|
||||
center: [position.lon, position.lat],
|
||||
zoom: Math.max(mapRef.current?.getZoom() ?? 13, 13),
|
||||
essential: true
|
||||
});
|
||||
} else {
|
||||
mapRef.current?.flyTo({
|
||||
center: [position.lon, position.lat],
|
||||
zoom: 13
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LocateFixed size={18} aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
@@ -455,6 +455,12 @@ button:disabled {
|
||||
right: 62px;
|
||||
}
|
||||
|
||||
.locate-toggle[data-active="true"] {
|
||||
background: #0f4c5c;
|
||||
color: #ffffff;
|
||||
border-color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
.anchor-toggle {
|
||||
z-index: 10;
|
||||
top: calc(env(safe-area-inset-top) + 116px);
|
||||
|
||||
@@ -788,6 +788,71 @@ describe("MapView marine feature information", () => {
|
||||
);
|
||||
expect(maplibreState.markerConstructions).toBe(0);
|
||||
});
|
||||
|
||||
it("keeps the GPS position centered while navigation follow mode is active", async () => {
|
||||
const onFollowPositionChange = vi.fn();
|
||||
const onMapReady = vi.fn();
|
||||
const onPickCoordinate = vi.fn();
|
||||
const view = render(
|
||||
<MapView
|
||||
config={config}
|
||||
position={{ lat: 52, lon: 7 }}
|
||||
accuracyM={5}
|
||||
startPoint={null}
|
||||
destination={null}
|
||||
pickMode={null}
|
||||
route={null}
|
||||
navigationActive
|
||||
followPosition={false}
|
||||
onFollowPositionChange={onFollowPositionChange}
|
||||
onPickCoordinate={onPickCoordinate}
|
||||
onMapReady={onMapReady}
|
||||
/>
|
||||
);
|
||||
await act(async () => maplibreState.current.emit("load"));
|
||||
|
||||
const followButton = screen.getByRole("button", {
|
||||
name: "GPS-Folge-Modus aktivieren"
|
||||
});
|
||||
expect(followButton).toHaveAttribute("aria-pressed", "false");
|
||||
fireEvent.click(followButton);
|
||||
expect(onFollowPositionChange).toHaveBeenCalledWith(true);
|
||||
expect(maplibreState.current.flyTo).toHaveBeenCalledWith({
|
||||
center: [7, 52],
|
||||
zoom: 13,
|
||||
essential: true
|
||||
});
|
||||
|
||||
view.rerender(
|
||||
<MapView
|
||||
config={config}
|
||||
position={{ lat: 52.01, lon: 7.02 }}
|
||||
accuracyM={5}
|
||||
startPoint={null}
|
||||
destination={null}
|
||||
pickMode={null}
|
||||
route={null}
|
||||
navigationActive
|
||||
followPosition
|
||||
onFollowPositionChange={onFollowPositionChange}
|
||||
onPickCoordinate={onPickCoordinate}
|
||||
onMapReady={onMapReady}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole("button", { name: "GPS-Folge-Modus beenden" })
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
expect(maplibreState.current.easeTo).toHaveBeenLastCalledWith({
|
||||
center: [7.02, 52.01],
|
||||
zoom: 13,
|
||||
duration: 500,
|
||||
essential: true
|
||||
});
|
||||
|
||||
await act(async () => maplibreState.current.emit("dragstart"));
|
||||
expect(onFollowPositionChange).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MapView anchor watch", () => {
|
||||
|
||||
Reference in New Issue
Block a user