67 lines
2.4 KiB
TypeScript
67 lines
2.4 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import type { EventGpsCoordinate } from "./event-location-coordinate";
|
|
|
|
export type EventLocationAddressResolver = (coordinate: EventGpsCoordinate, signal: AbortSignal) => Promise<string>;
|
|
|
|
export type EventLocationAddressLookup = {
|
|
pending: boolean;
|
|
error: string | null;
|
|
changeLocation: (value: string) => void;
|
|
retry: () => void;
|
|
};
|
|
|
|
export function useEventLocationAddress({ active = true, coordinate, resolveAddress, onChange }: {
|
|
active?: boolean;
|
|
coordinate: EventGpsCoordinate | null;
|
|
resolveAddress: EventLocationAddressResolver;
|
|
onChange: (value: string) => void;
|
|
}): EventLocationAddressLookup {
|
|
const [pending, setPending] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [retryGeneration, setRetryGeneration] = useState(0);
|
|
const requestRef = useRef<AbortController | null>(null);
|
|
const longitude = coordinate?.[0];
|
|
const latitude = coordinate?.[1];
|
|
|
|
useEffect(() => {
|
|
setError(null);
|
|
if (!active || longitude === undefined || latitude === undefined) {
|
|
setPending(false);
|
|
return;
|
|
}
|
|
const controller = new AbortController();
|
|
requestRef.current = controller;
|
|
setPending(true);
|
|
onChange("");
|
|
// Coalesce keyboard adjustments. GPS stays immediate while only the final
|
|
// point is reverse-geocoded; a former point's address must never be reused.
|
|
const timer = window.setTimeout(async () => {
|
|
if (controller.signal.aborted) return;
|
|
try {
|
|
const address = await resolveAddress([longitude, latitude], controller.signal);
|
|
if (!controller.signal.aborted) onChange(address);
|
|
} catch (failure) {
|
|
if (!controller.signal.aborted) {
|
|
setError(failure instanceof Error ? failure.message : "地址查询失败,请重试或手动填写");
|
|
}
|
|
} finally {
|
|
if (!controller.signal.aborted) setPending(false);
|
|
}
|
|
}, 250);
|
|
return () => {
|
|
window.clearTimeout(timer);
|
|
controller.abort();
|
|
if (requestRef.current === controller) requestRef.current = null;
|
|
};
|
|
}, [active, longitude, latitude, resolveAddress, onChange, retryGeneration]);
|
|
|
|
const changeLocation = useCallback((value: string) => {
|
|
requestRef.current?.abort();
|
|
setPending(false);
|
|
setError(null);
|
|
onChange(value);
|
|
}, [onChange]);
|
|
|
|
return { pending, error, changeLocation, retry: () => setRetryGeneration((current) => current + 1) };
|
|
}
|