mirror of https://github.com/usememos/memos
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import { create } from "@bufbuild/protobuf";
|
|
import { LatLng } from "leaflet";
|
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
import { Location, LocationSchema } from "@/types/proto/api/v1/memo_service_pb";
|
|
import { LocationState } from "../types/insert-menu";
|
|
|
|
export const useLocation = (initialLocation?: Location) => {
|
|
const [locationInitialized, setLocationInitialized] = useState(false);
|
|
const locationInitializedRef = useRef(locationInitialized);
|
|
locationInitializedRef.current = locationInitialized;
|
|
|
|
const [state, setState] = useState<LocationState>({
|
|
placeholder: initialLocation?.placeholder || "",
|
|
position: initialLocation ? new LatLng(initialLocation.latitude, initialLocation.longitude) : undefined,
|
|
latInput: initialLocation ? String(initialLocation.latitude) : "",
|
|
lngInput: initialLocation ? String(initialLocation.longitude) : "",
|
|
});
|
|
|
|
// Ref to latest state so getLocation can be stable without closing over state.
|
|
const stateRef = useRef(state);
|
|
stateRef.current = state;
|
|
|
|
const updatePosition = useCallback((position?: LatLng) => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
position,
|
|
latInput: position ? String(position.lat) : "",
|
|
lngInput: position ? String(position.lng) : "",
|
|
}));
|
|
}, []);
|
|
|
|
// Stable — reads locationInitialized via ref to avoid recreating on every change.
|
|
const handlePositionChange = useCallback(
|
|
(position: LatLng) => {
|
|
if (!locationInitializedRef.current) setLocationInitialized(true);
|
|
updatePosition(position);
|
|
},
|
|
[updatePosition],
|
|
);
|
|
|
|
// Stable — merges coordinate update into a single functional setState, avoiding closure over state.position.
|
|
const updateCoordinate = useCallback((type: "lat" | "lng", value: string) => {
|
|
const num = parseFloat(value);
|
|
const isValid = type === "lat" ? !isNaN(num) && num >= -90 && num <= 90 : !isNaN(num) && num >= -180 && num <= 180;
|
|
setState((prev) => {
|
|
const next = { ...prev, [type === "lat" ? "latInput" : "lngInput"]: value };
|
|
if (isValid && prev.position) {
|
|
const newPos = type === "lat" ? new LatLng(num, prev.position.lng) : new LatLng(prev.position.lat, num);
|
|
return { ...next, position: newPos, latInput: String(newPos.lat), lngInput: String(newPos.lng) };
|
|
}
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
// Stable reference — uses functional setState, no closure deps.
|
|
const setPlaceholder = useCallback((placeholder: string) => {
|
|
setState((prev) => ({ ...prev, placeholder }));
|
|
}, []);
|
|
|
|
const reset = useCallback(() => {
|
|
setState({
|
|
placeholder: "",
|
|
position: undefined,
|
|
latInput: "",
|
|
lngInput: "",
|
|
});
|
|
setLocationInitialized(false);
|
|
}, []);
|
|
|
|
// Stable — reads latest state via ref, no closure over state.
|
|
const getLocation = useCallback((): Location | undefined => {
|
|
const { position, placeholder } = stateRef.current;
|
|
if (!position || !placeholder.trim()) {
|
|
return undefined;
|
|
}
|
|
return create(LocationSchema, {
|
|
latitude: position.lat,
|
|
longitude: position.lng,
|
|
placeholder,
|
|
});
|
|
}, []);
|
|
|
|
return useMemo(
|
|
() => ({ state, locationInitialized, handlePositionChange, updateCoordinate, setPlaceholder, reset, getLocation }),
|
|
[state, locationInitialized, handlePositionChange, updateCoordinate, setPlaceholder, reset, getLocation],
|
|
);
|
|
};
|