mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 22:14:08 +01:00
218 lines
6.7 KiB
TypeScript
218 lines
6.7 KiB
TypeScript
import { Dispatch, MutableRefObject, useEffect } from "react";
|
|
|
|
import {
|
|
Action,
|
|
DrawResult,
|
|
DrawingActionKind,
|
|
Overlay,
|
|
Snapshot,
|
|
State,
|
|
isCircle,
|
|
isMarker,
|
|
isPolygon,
|
|
isPolyline,
|
|
isRectangle,
|
|
} from "@/types";
|
|
|
|
export default function reducer(state: State, action: Action) {
|
|
switch (action.type) {
|
|
// This action is called whenever anything changes on any overlay.
|
|
// We then take a snapshot of the relevant values of each overlay and
|
|
// save them as the new "now". The old "now" is added to the "past" stack
|
|
case DrawingActionKind.UPDATE_OVERLAYS: {
|
|
const overlays = state.now.map((overlay: Overlay) => {
|
|
const snapshot: Snapshot = {};
|
|
const { geometry } = overlay;
|
|
|
|
if (isCircle(geometry)) {
|
|
snapshot.center = geometry.getCenter()?.toJSON();
|
|
snapshot.radius = geometry.getRadius();
|
|
} else if (isMarker(geometry)) {
|
|
snapshot.position = geometry.getPosition()?.toJSON();
|
|
} else if (isPolygon(geometry) || isPolyline(geometry)) {
|
|
snapshot.path = geometry.getPath()?.getArray();
|
|
} else if (isRectangle(geometry)) {
|
|
snapshot.bounds = geometry.getBounds()?.toJSON();
|
|
}
|
|
|
|
return {
|
|
...overlay,
|
|
snapshot,
|
|
};
|
|
});
|
|
|
|
return {
|
|
now: [...overlays],
|
|
past: [...state.past, state.now],
|
|
future: [],
|
|
};
|
|
}
|
|
|
|
// This action is called when a new overlay is added to the map.
|
|
// We then take a snapshot of the relevant values of the new overlay and
|
|
// add it to the "now" state. The old "now" is added to the "past" stack
|
|
case DrawingActionKind.SET_OVERLAY: {
|
|
const { overlay } = action.payload;
|
|
|
|
const snapshot: Snapshot = {};
|
|
|
|
if (isCircle(overlay)) {
|
|
snapshot.center = overlay.getCenter()?.toJSON();
|
|
snapshot.radius = overlay.getRadius();
|
|
} else if (isMarker(overlay)) {
|
|
snapshot.position = overlay.getPosition()?.toJSON();
|
|
} else if (isPolygon(overlay) || isPolyline(overlay)) {
|
|
snapshot.path = overlay.getPath()?.getArray();
|
|
} else if (isRectangle(overlay)) {
|
|
snapshot.bounds = overlay.getBounds()?.toJSON();
|
|
}
|
|
|
|
return {
|
|
past: [...state.past, state.now],
|
|
now: [
|
|
...state.now,
|
|
{
|
|
type: action.payload.type,
|
|
geometry: action.payload.overlay,
|
|
snapshot,
|
|
},
|
|
],
|
|
future: [],
|
|
};
|
|
}
|
|
|
|
// This action is called when the undo button is clicked.
|
|
// Get the top item from the "past" stack and set it as the new "now".
|
|
// Add the old "now" to the "future" stack to enable redo functionality
|
|
case DrawingActionKind.UNDO: {
|
|
const last = state.past.slice(-1)[0];
|
|
|
|
if (!last) return state;
|
|
|
|
return {
|
|
past: [...state.past].slice(0, -1),
|
|
now: last,
|
|
future: state.now ? [...state.future, state.now] : state.future,
|
|
};
|
|
}
|
|
|
|
// This action is called when the redo button is clicked.
|
|
// Get the top item from the "future" stack and set it as the new "now".
|
|
// Add the old "now" to the "past" stack to enable undo functionality
|
|
case DrawingActionKind.REDO: {
|
|
const next = state.future.slice(-1)[0];
|
|
|
|
if (!next) return state;
|
|
|
|
return {
|
|
past: state.now ? [...state.past, state.now] : state.past,
|
|
now: next,
|
|
future: [...state.future].slice(0, -1),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle drawing manager events
|
|
export function useDrawingManagerEvents(
|
|
drawingManager: google.maps.drawing.DrawingManager | null,
|
|
overlaysShouldUpdateRef: MutableRefObject<boolean>,
|
|
dispatch: Dispatch<Action>
|
|
) {
|
|
useEffect(() => {
|
|
if (!drawingManager) return;
|
|
|
|
const eventListeners: Array<google.maps.MapsEventListener> = [];
|
|
|
|
const addUpdateListener = (eventName: string, drawResult: DrawResult) => {
|
|
const updateListener = google.maps.event.addListener(drawResult.overlay, eventName, () => {
|
|
if (eventName === "dragstart") {
|
|
overlaysShouldUpdateRef.current = false;
|
|
}
|
|
|
|
if (eventName === "dragend") {
|
|
overlaysShouldUpdateRef.current = true;
|
|
}
|
|
|
|
if (overlaysShouldUpdateRef.current) {
|
|
dispatch({ type: DrawingActionKind.UPDATE_OVERLAYS });
|
|
}
|
|
});
|
|
|
|
eventListeners.push(updateListener);
|
|
};
|
|
|
|
const overlayCompleteListener = google.maps.event.addListener(
|
|
drawingManager,
|
|
"overlaycomplete",
|
|
(drawResult: DrawResult) => {
|
|
switch (drawResult.type) {
|
|
case google.maps.drawing.OverlayType.CIRCLE:
|
|
["center_changed", "radius_changed"].forEach((eventName) => addUpdateListener(eventName, drawResult));
|
|
break;
|
|
|
|
case google.maps.drawing.OverlayType.MARKER:
|
|
["dragend"].forEach((eventName) => addUpdateListener(eventName, drawResult));
|
|
|
|
break;
|
|
|
|
case google.maps.drawing.OverlayType.POLYGON:
|
|
case google.maps.drawing.OverlayType.POLYLINE:
|
|
["mouseup"].forEach((eventName) => addUpdateListener(eventName, drawResult));
|
|
|
|
case google.maps.drawing.OverlayType.RECTANGLE:
|
|
["bounds_changed", "dragstart", "dragend"].forEach((eventName) => addUpdateListener(eventName, drawResult));
|
|
|
|
break;
|
|
}
|
|
|
|
dispatch({ type: DrawingActionKind.SET_OVERLAY, payload: drawResult });
|
|
}
|
|
);
|
|
|
|
eventListeners.push(overlayCompleteListener);
|
|
|
|
return () => {
|
|
eventListeners.forEach((listener) => google.maps.event.removeListener(listener));
|
|
};
|
|
}, [dispatch, drawingManager, overlaysShouldUpdateRef]);
|
|
}
|
|
|
|
// Update overlays with the current "snapshot" when the "now" state changes
|
|
export function useOverlaySnapshots(
|
|
map: google.maps.Map | null,
|
|
state: State,
|
|
overlaysShouldUpdateRef: MutableRefObject<boolean>
|
|
) {
|
|
useEffect(() => {
|
|
if (!map || !state.now) return;
|
|
|
|
for (const overlay of state.now) {
|
|
overlaysShouldUpdateRef.current = false;
|
|
|
|
overlay.geometry.setMap(map);
|
|
|
|
const { radius, center, position, path, bounds } = overlay.snapshot;
|
|
|
|
if (isCircle(overlay.geometry)) {
|
|
overlay.geometry.setRadius(radius ?? 0);
|
|
overlay.geometry.setCenter(center ?? null);
|
|
} else if (isMarker(overlay.geometry)) {
|
|
overlay.geometry.setPosition(position);
|
|
} else if (isPolygon(overlay.geometry) || isPolyline(overlay.geometry)) {
|
|
overlay.geometry.setPath(path ?? []);
|
|
} else if (isRectangle(overlay.geometry)) {
|
|
overlay.geometry.setBounds(bounds ?? null);
|
|
}
|
|
|
|
overlaysShouldUpdateRef.current = true;
|
|
}
|
|
|
|
return () => {
|
|
for (const overlay of state.now) {
|
|
overlay.geometry.setMap(null);
|
|
}
|
|
};
|
|
}, [map, overlaysShouldUpdateRef, state.now]);
|
|
}
|