mirror of
https://github.com/nocobase/nocobase.git
synced 2026-09-19 02:23:00 +08:00
Merge branch 'main' into next
This commit is contained in:
@@ -28,7 +28,7 @@ import { defaultImage, selectedImage } from '../../constants';
|
||||
import { useMapTranslation } from '../../locale';
|
||||
import { getSource } from '../../utils';
|
||||
import { MapBlockDrawer } from '../MapBlockDrawer';
|
||||
import { GoogleMapForwardedRefProps, GoogleMapsComponent, OverlayOptions } from './Map';
|
||||
import { GoogleMapForwardedRefProps, GoogleMapsComponent } from './Map';
|
||||
import { getIcon } from './utils';
|
||||
|
||||
const OVERLAY_KEY = 'google-maps-overlay-id';
|
||||
@@ -48,9 +48,43 @@ const pointClass = css`
|
||||
border: 1px solid #0000f5;
|
||||
`;
|
||||
|
||||
const isPositionInPolygon = (position: google.maps.LatLng, polygonPath: google.maps.LatLng[]) => {
|
||||
const x = position.lng();
|
||||
const y = position.lat();
|
||||
let inside = false;
|
||||
|
||||
for (let i = 0, j = polygonPath.length - 1; i < polygonPath.length; j = i++) {
|
||||
const xi = polygonPath[i].lng();
|
||||
const yi = polygonPath[i].lat();
|
||||
const xj = polygonPath[j].lng();
|
||||
const yj = polygonPath[j].lat();
|
||||
const intersects = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
||||
|
||||
if (intersects) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
};
|
||||
|
||||
const getMarkerLabel = (text: unknown): google.maps.MarkerLabel | undefined => {
|
||||
if (typeof text !== 'string' || !text) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
className: labelClass,
|
||||
fontFamily: 'inherit',
|
||||
fontSize: '13px',
|
||||
color: '#333',
|
||||
text,
|
||||
} as google.maps.MarkerLabel;
|
||||
};
|
||||
|
||||
export const GoogleMapsBlock = (props) => {
|
||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } =
|
||||
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort, service } =
|
||||
useProps(props);
|
||||
const { getPrimaryKey } = useCollection_deprecated();
|
||||
const primaryKey = getPrimaryKey();
|
||||
@@ -66,6 +100,9 @@ export const GoogleMapsBlock = (props) => {
|
||||
const selectingModeRef = useRef(selectingMode);
|
||||
const selectionOverlayRef = useRef<google.maps.Polygon | null>(null);
|
||||
const overlaysRef = useRef<google.maps.MVCObject[]>([]);
|
||||
const selectedRecordKeysRef = useRef<Set<string | number>>(new Set());
|
||||
const lastSelectionCompleteAtRef = useRef(0);
|
||||
const listLoadingRef = useRef(Boolean(service?.loading));
|
||||
selectingModeRef.current = selectingMode;
|
||||
const { fields } = useCollection();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
@@ -74,23 +111,35 @@ export const GoogleMapsBlock = (props) => {
|
||||
const { openPopup } = usePopupUtils();
|
||||
|
||||
const setOverlayOptions = (overlay: google.maps.MVCObject, state?: boolean) => {
|
||||
const selected = typeof state !== 'undefined' ? !state : overlay.get(OVERLAY_SELECtED);
|
||||
overlay.set(OVERLAY_SELECtED, !selected);
|
||||
(overlay as google.maps.Marker).setOptions({
|
||||
...(selected
|
||||
const selected = typeof state !== 'undefined' ? state : !overlay.get(OVERLAY_SELECtED);
|
||||
overlay.set(OVERLAY_SELECtED, selected);
|
||||
if (overlay instanceof google.maps.Marker) {
|
||||
overlay.setIcon(getIcon(selected ? selectedImage : defaultImage));
|
||||
overlay.setZIndex(selected ? 138 : null);
|
||||
return;
|
||||
}
|
||||
|
||||
(overlay as google.maps.Polygon).setOptions(
|
||||
selected
|
||||
? {
|
||||
icon: getIcon(defaultImage),
|
||||
strokeColor: '#4e9bff',
|
||||
fillColor: '#4e9bff',
|
||||
}
|
||||
: {
|
||||
icon: getIcon(selectedImage),
|
||||
strokeColor: '#F18b62',
|
||||
fillColor: '#F18b62',
|
||||
}),
|
||||
} as OverlayOptions);
|
||||
}
|
||||
: {
|
||||
strokeColor: '#4e9bff',
|
||||
fillColor: '#4e9bff',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const clearSelectionState = useMemoizedFn(() => {
|
||||
selectedRecordKeysRef.current.clear();
|
||||
overlaysRef.current.forEach((overlay) => {
|
||||
setOverlayOptions(overlay, false);
|
||||
});
|
||||
setSelectedRecordKeys([]);
|
||||
});
|
||||
|
||||
// selection
|
||||
useEffect(() => {
|
||||
if (selectingMode !== 'selection') {
|
||||
@@ -102,27 +151,19 @@ export const GoogleMapsBlock = (props) => {
|
||||
draggable: true,
|
||||
});
|
||||
}
|
||||
const listenerSet = new Set<() => void>();
|
||||
mapRef.current?.drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
|
||||
mapRef.current?.drawingManager.addListener('overlaycomplete', (event) => {
|
||||
mapRef.current?.drawingManager?.setDrawingMode('polygon');
|
||||
mapRef.current?.drawingManager?.addListener('overlaycomplete', (event) => {
|
||||
const polygon = event.overlay as google.maps.Polygon;
|
||||
mapRef.current?.drawingManager.setDrawingMode(null);
|
||||
mapRef.current?.drawingManager?.setDrawingMode(null);
|
||||
selectionOverlayRef.current = polygon;
|
||||
const path = polygon.getPath();
|
||||
['insert_at', 'remove_at', 'set_at'].forEach((key) => {
|
||||
listenerSet.add(path.addListener(key, () => {}).remove);
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
listenerSet.forEach((i) => {
|
||||
i();
|
||||
});
|
||||
if (!mapRef.current) return;
|
||||
selectionOverlayRef.current?.unbindAll();
|
||||
selectionOverlayRef.current?.setMap(null);
|
||||
selectionOverlayRef.current = null;
|
||||
mapRef.current?.drawingManager.setDrawingMode(null);
|
||||
mapRef.current?.drawingManager.unbindAll();
|
||||
mapRef.current?.drawingManager?.setDrawingMode(null);
|
||||
mapRef.current?.drawingManager?.unbindAll();
|
||||
};
|
||||
}, [selectingMode]);
|
||||
|
||||
@@ -139,21 +180,30 @@ export const GoogleMapsBlock = (props) => {
|
||||
}, [selectingMode]);
|
||||
|
||||
const onSelectingComplete = useMemoizedFn(() => {
|
||||
mapRef.current?.drawingManager?.completeDrawing();
|
||||
const overlay = selectionOverlayRef.current;
|
||||
if (!overlay) {
|
||||
return;
|
||||
}
|
||||
const overlays = overlaysRef.current;
|
||||
const poly = google.maps.geometry.poly;
|
||||
const selectionPath = overlay.getPath().getArray();
|
||||
if (selectionPath.length < 3) {
|
||||
return;
|
||||
}
|
||||
const selectedOverlays = overlays.filter((o) => {
|
||||
if (o === overlay || o.get(OVERLAY_KEY) === undefined) return;
|
||||
if (o instanceof google.maps.Marker) {
|
||||
return poly.containsLocation(o.getPosition()!, overlay!);
|
||||
const position = o.getPosition();
|
||||
return position ? isPositionInPolygon(position, selectionPath) : false;
|
||||
} else if (o instanceof google.maps.Circle) {
|
||||
return poly.containsLocation(o.getCenter()!, overlay!);
|
||||
const center = o.getCenter();
|
||||
return center ? isPositionInPolygon(center, selectionPath) : false;
|
||||
} else {
|
||||
return (o as google.maps.Polygon)
|
||||
.getPath()
|
||||
.getArray()
|
||||
.some((position) => {
|
||||
return poly.containsLocation(position, overlay!);
|
||||
return isPositionInPolygon(position, selectionPath);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -161,10 +211,15 @@ export const GoogleMapsBlock = (props) => {
|
||||
setOverlayOptions(o, true);
|
||||
return o.get(OVERLAY_KEY);
|
||||
});
|
||||
ids.forEach((id) => {
|
||||
selectedRecordKeysRef.current.add(id);
|
||||
});
|
||||
lastSelectionCompleteAtRef.current = Date.now();
|
||||
setSelectedRecordKeys((lastIds) => ids.concat(lastIds));
|
||||
overlay?.unbindAll();
|
||||
overlay?.setMap(null);
|
||||
mapRef.current?.drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
|
||||
overlay.unbindAll();
|
||||
overlay.setMap(null);
|
||||
selectionOverlayRef.current = null;
|
||||
mapRef.current?.drawingManager?.setDrawingMode('polygon');
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -179,6 +234,7 @@ export const GoogleMapsBlock = (props) => {
|
||||
.map((item) => {
|
||||
const data = getSource(item, fieldNames?.field, cf?.interface);
|
||||
const title = getLabelFormatValue(labelUiSchema, item[fieldNames.marker]);
|
||||
const markerLabel = getMarkerLabel(fieldNames?.marker ? compile(title) : undefined);
|
||||
if (!data?.length) return [];
|
||||
return data?.filter(Boolean).map((mapItem) => {
|
||||
if (!data) return;
|
||||
@@ -186,15 +242,12 @@ export const GoogleMapsBlock = (props) => {
|
||||
strokeColor: '#4e9bff',
|
||||
fillColor: '#4e9bff',
|
||||
cursor: 'pointer',
|
||||
label: {
|
||||
className: labelClass,
|
||||
fontFamily: 'inherit',
|
||||
fontSize: '13px',
|
||||
color: '#333',
|
||||
text: fieldNames?.marker ? compile(title) : undefined,
|
||||
} as google.maps.MarkerLabel,
|
||||
label: markerLabel,
|
||||
});
|
||||
overlay?.set(OVERLAY_KEY, item[primaryKey]);
|
||||
if (overlay) {
|
||||
setOverlayOptions(overlay, selectedRecordKeysRef.current.has(item[primaryKey]));
|
||||
}
|
||||
return overlay;
|
||||
});
|
||||
})
|
||||
@@ -205,6 +258,12 @@ export const GoogleMapsBlock = (props) => {
|
||||
|
||||
const events = overlays.map((o: google.maps.MVCObject) => {
|
||||
const onClick = (event) => {
|
||||
if (selectingModeRef.current === 'selection') {
|
||||
const markerPosition = o instanceof google.maps.Marker ? o.getPosition() : undefined;
|
||||
mapRef.current?.drawingManager?.handleOverlayClick(event, markerPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
const overlay = o as google.maps.Polygon;
|
||||
const id = overlay.get(OVERLAY_KEY);
|
||||
if (!id) return;
|
||||
@@ -240,7 +299,13 @@ export const GoogleMapsBlock = (props) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
const onDoubleClick = () => {
|
||||
if (selectingModeRef.current === 'selection') {
|
||||
mapRef.current?.drawingManager?.handleDoubleClick();
|
||||
}
|
||||
};
|
||||
o.addListener('click', onClick);
|
||||
o.addListener('dblclick', onDoubleClick);
|
||||
return () => o.unbindAll();
|
||||
});
|
||||
|
||||
@@ -299,9 +364,22 @@ export const GoogleMapsBlock = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setSelectedRecordKeys([]);
|
||||
if (Date.now() - lastSelectionCompleteAtRef.current < 100) {
|
||||
return;
|
||||
}
|
||||
clearSelectionState();
|
||||
});
|
||||
}, [dataSource]);
|
||||
}, [clearSelectionState, dataSource]);
|
||||
|
||||
useEffect(() => {
|
||||
const wasLoading = listLoadingRef.current;
|
||||
const isLoading = Boolean(service?.loading);
|
||||
listLoadingRef.current = isLoading;
|
||||
|
||||
if (wasLoading && !isLoading) {
|
||||
clearSelectionState();
|
||||
}
|
||||
}, [clearSelectionState, service?.loading]);
|
||||
|
||||
const mapRefCallback = (instance: GoogleMapForwardedRefProps) => {
|
||||
mapRef.current = instance;
|
||||
|
||||
@@ -15,6 +15,11 @@ import { useMemoizedFn } from 'ahooks';
|
||||
import { Alert, App, Button, Spin } from 'antd';
|
||||
import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import { defaultImage } from '../../constants';
|
||||
import {
|
||||
createGoogleMapsDrawingManager,
|
||||
GoogleMapsDrawingManager,
|
||||
GoogleMapsDrawingMode,
|
||||
} from '../../googleMapsDrawingManager';
|
||||
import { useMapConfiguration, useMapConfig } from '../../hooks';
|
||||
import { useMapTranslation } from '../../locale';
|
||||
import { MapEditorType } from '../../types';
|
||||
@@ -80,7 +85,7 @@ export interface GoogleMapForwardedRefProps {
|
||||
createDraw: (onlyCreate?: boolean, additionalOptions?: OverlayOptions) => any;
|
||||
map: google.maps.Map;
|
||||
overlay: google.maps.MVCObject;
|
||||
drawingManager: google.maps.drawing.DrawingManager;
|
||||
drawingManager: GoogleMapsDrawingManager;
|
||||
errMessage?: string;
|
||||
}
|
||||
|
||||
@@ -100,7 +105,7 @@ export const GoogleMapsComponent = React.forwardRef<GoogleMapForwardedRefProps,
|
||||
const { t } = useMapTranslation();
|
||||
const { getField } = useCollection_deprecated();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const drawingManagerRef = useRef<google.maps.drawing.DrawingManager>();
|
||||
const drawingManagerRef = useRef<GoogleMapsDrawingManager>();
|
||||
const map = useRef<google.maps.Map>();
|
||||
const overlayRef = useRef<google.maps.Marker | google.maps.Polygon | google.maps.Polyline | google.maps.Circle>();
|
||||
const [needUpdateFlag, forceUpdate] = useState([]);
|
||||
@@ -119,7 +124,7 @@ export const GoogleMapsComponent = React.forwardRef<GoogleMapForwardedRefProps,
|
||||
return collectionField?.interface;
|
||||
}, [props?.type, fieldSchema?.name]);
|
||||
|
||||
const drawingMode = useRef(getDrawingMode(type) as google.maps.drawing.OverlayType);
|
||||
const drawingMode = useRef(getDrawingMode(type) as GoogleMapsDrawingMode);
|
||||
|
||||
const [commonOptions] = useState<OverlayOptions>({
|
||||
strokeWeight: 5,
|
||||
@@ -240,9 +245,8 @@ export const GoogleMapsComponent = React.forwardRef<GoogleMapForwardedRefProps,
|
||||
...additionalOptions,
|
||||
map: map.current,
|
||||
};
|
||||
drawingManagerRef.current = new google.maps.drawing.DrawingManager({
|
||||
drawingManagerRef.current = createGoogleMapsDrawingManager({
|
||||
drawingMode: drawingMode.current,
|
||||
drawingControl: false,
|
||||
markerOptions: { ...currentOptions, icon: getIcon(defaultImage) },
|
||||
polygonOptions: currentOptions,
|
||||
polylineOptions: currentOptions,
|
||||
@@ -330,7 +334,12 @@ export const GoogleMapsComponent = React.forwardRef<GoogleMapForwardedRefProps,
|
||||
error(err, ...args);
|
||||
};
|
||||
|
||||
Promise.all([loader.importLibrary('drawing'), loader.importLibrary('core'), loader.importLibrary('geometry')])
|
||||
Promise.all([
|
||||
loader.importLibrary('maps'),
|
||||
loader.importLibrary('core'),
|
||||
loader.importLibrary('geometry'),
|
||||
loader.importLibrary('marker'),
|
||||
])
|
||||
.then(async (res) => {
|
||||
const center = await getCurrentPosition();
|
||||
const mapContainer = mapContainerRef.current;
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export type GoogleMapsDrawingMode = 'marker' | 'polyline' | 'polygon' | 'circle' | null;
|
||||
|
||||
export type GoogleMapsOverlay = google.maps.Marker | google.maps.Polyline | google.maps.Polygon | google.maps.Circle;
|
||||
|
||||
export interface GoogleMapsOverlayCompleteEvent {
|
||||
type: Exclude<GoogleMapsDrawingMode, null>;
|
||||
overlay: GoogleMapsOverlay;
|
||||
}
|
||||
|
||||
export interface GoogleMapsDrawingManager {
|
||||
setDrawingMode: (mode: GoogleMapsDrawingMode) => void;
|
||||
addListener: (
|
||||
eventName: 'overlaycomplete',
|
||||
listener: (event: GoogleMapsOverlayCompleteEvent) => void,
|
||||
) => google.maps.MapsEventListener;
|
||||
handleClick: (event: google.maps.MapMouseEvent) => void;
|
||||
handleOverlayClick: (event: google.maps.MapMouseEvent, fallbackPosition?: google.maps.LatLng) => void;
|
||||
handleDoubleClick: () => void;
|
||||
completeDrawing: () => void;
|
||||
unbindAll: () => void;
|
||||
}
|
||||
|
||||
interface GoogleMapsDrawingManagerOptions {
|
||||
drawingMode: GoogleMapsDrawingMode;
|
||||
markerOptions: google.maps.MarkerOptions;
|
||||
polygonOptions: google.maps.PolygonOptions;
|
||||
polylineOptions: google.maps.PolylineOptions;
|
||||
circleOptions: google.maps.CircleOptions;
|
||||
map: google.maps.Map;
|
||||
}
|
||||
|
||||
const createMapsEventListener = (remove: () => void): google.maps.MapsEventListener => {
|
||||
return { remove };
|
||||
};
|
||||
|
||||
export const createGoogleMapsDrawingManager = (options: GoogleMapsDrawingManagerOptions): GoogleMapsDrawingManager => {
|
||||
const overlayCompleteListeners = new Set<(event: GoogleMapsOverlayCompleteEvent) => void>();
|
||||
let mapListeners: google.maps.MapsEventListener[] = [];
|
||||
let drawingMode: GoogleMapsDrawingMode = null;
|
||||
let activeOverlay: GoogleMapsOverlay = null;
|
||||
let activePath: google.maps.MVCArray<google.maps.LatLng> = null;
|
||||
let circleCenter: google.maps.LatLng = null;
|
||||
let clickHandler: (event: google.maps.MapMouseEvent, fallbackPosition?: google.maps.LatLng) => void = null;
|
||||
let doubleClickHandler: () => void = null;
|
||||
let lastClickTime = 0;
|
||||
let lastClickPosition: google.maps.LatLng = null;
|
||||
|
||||
const clearMapListeners = () => {
|
||||
mapListeners.forEach((listener) => {
|
||||
listener.remove();
|
||||
});
|
||||
mapListeners = [];
|
||||
};
|
||||
|
||||
const clearActiveOverlay = () => {
|
||||
activeOverlay?.setMap(null);
|
||||
activeOverlay = null;
|
||||
activePath = null;
|
||||
circleCenter = null;
|
||||
lastClickPosition = null;
|
||||
lastClickTime = 0;
|
||||
};
|
||||
|
||||
const resetMapDrawingOptions = () => {
|
||||
options.map.setOptions({
|
||||
draggableCursor: undefined,
|
||||
disableDoubleClickZoom: false,
|
||||
});
|
||||
};
|
||||
|
||||
const stopDrawing = () => {
|
||||
clearMapListeners();
|
||||
clearActiveOverlay();
|
||||
clickHandler = null;
|
||||
doubleClickHandler = null;
|
||||
resetMapDrawingOptions();
|
||||
};
|
||||
|
||||
const emitOverlayComplete = (event: GoogleMapsOverlayCompleteEvent) => {
|
||||
overlayCompleteListeners.forEach((listener) => {
|
||||
listener(event);
|
||||
});
|
||||
};
|
||||
|
||||
const completeActiveOverlay = (type: Exclude<GoogleMapsDrawingMode, null>) => {
|
||||
if (!activeOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((type === 'polygon' || type === 'polyline') && activePath) {
|
||||
const path = activePath.getArray();
|
||||
const normalizedPath = path.filter((position, index) => {
|
||||
const previous = path[index - 1];
|
||||
return !previous || previous.lat() !== position.lat() || previous.lng() !== position.lng();
|
||||
});
|
||||
activePath.clear();
|
||||
normalizedPath.forEach((position) => {
|
||||
activePath.push(position);
|
||||
});
|
||||
}
|
||||
|
||||
let overlay = activeOverlay;
|
||||
if (type === 'polygon' && activePath) {
|
||||
activeOverlay.setMap(null);
|
||||
overlay = new google.maps.Polygon({
|
||||
...options.polygonOptions,
|
||||
paths: activePath.getArray(),
|
||||
map: options.map,
|
||||
});
|
||||
}
|
||||
|
||||
activeOverlay = null;
|
||||
activePath = null;
|
||||
circleCenter = null;
|
||||
clearMapListeners();
|
||||
resetMapDrawingOptions();
|
||||
drawingMode = null;
|
||||
emitOverlayComplete({ type, overlay });
|
||||
};
|
||||
|
||||
const getEventPosition = (event: google.maps.MapMouseEvent, fallbackPosition?: google.maps.LatLng) => {
|
||||
return event.latLng || fallbackPosition || null;
|
||||
};
|
||||
|
||||
const shouldSkipDuplicateClick = (position: google.maps.LatLng) => {
|
||||
const now = Date.now();
|
||||
const isDuplicate =
|
||||
lastClickPosition &&
|
||||
now - lastClickTime < 50 &&
|
||||
lastClickPosition.lat() === position.lat() &&
|
||||
lastClickPosition.lng() === position.lng();
|
||||
|
||||
lastClickTime = now;
|
||||
lastClickPosition = position;
|
||||
|
||||
return isDuplicate;
|
||||
};
|
||||
|
||||
const startMarkerDrawing = () => {
|
||||
clickHandler = (event: google.maps.MapMouseEvent, fallbackPosition?: google.maps.LatLng) => {
|
||||
const position = getEventPosition(event, fallbackPosition);
|
||||
if (!position || shouldSkipDuplicateClick(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const marker = new google.maps.Marker({
|
||||
...options.markerOptions,
|
||||
position,
|
||||
map: options.map,
|
||||
});
|
||||
emitOverlayComplete({ type: 'marker', overlay: marker });
|
||||
};
|
||||
mapListeners.push(options.map.addListener('click', clickHandler));
|
||||
};
|
||||
|
||||
const ensurePathOverlay = (type: 'polyline' | 'polygon') => {
|
||||
if (!activePath) {
|
||||
activePath = new google.maps.MVCArray<google.maps.LatLng>();
|
||||
}
|
||||
|
||||
if (!activeOverlay) {
|
||||
activeOverlay = new google.maps.Polyline({
|
||||
...(type === 'polyline' ? options.polylineOptions : options.polygonOptions),
|
||||
path: activePath,
|
||||
map: options.map,
|
||||
});
|
||||
}
|
||||
|
||||
return activePath;
|
||||
};
|
||||
|
||||
const startPathDrawing = (type: 'polyline' | 'polygon') => {
|
||||
options.map.setOptions({
|
||||
draggableCursor: 'crosshair',
|
||||
disableDoubleClickZoom: true,
|
||||
});
|
||||
|
||||
clickHandler = (event: google.maps.MapMouseEvent, fallbackPosition?: google.maps.LatLng) => {
|
||||
const position = getEventPosition(event, fallbackPosition);
|
||||
if (!position || shouldSkipDuplicateClick(position)) {
|
||||
return;
|
||||
}
|
||||
ensurePathOverlay(type).push(position);
|
||||
};
|
||||
mapListeners.push(options.map.addListener('click', clickHandler));
|
||||
|
||||
doubleClickHandler = () => {
|
||||
const minPoints = type === 'polygon' ? 3 : 2;
|
||||
if (!activePath || activePath.getLength() < minPoints) {
|
||||
return;
|
||||
}
|
||||
|
||||
completeActiveOverlay(type);
|
||||
};
|
||||
mapListeners.push(options.map.addListener('dblclick', doubleClickHandler));
|
||||
};
|
||||
|
||||
const updateCircleRadius = (position: google.maps.LatLng) => {
|
||||
if (!circleCenter || !(activeOverlay instanceof google.maps.Circle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeOverlay.setRadius(google.maps.geometry.spherical.computeDistanceBetween(circleCenter, position));
|
||||
};
|
||||
|
||||
const startCircleDrawing = () => {
|
||||
options.map.setOptions({
|
||||
draggableCursor: 'crosshair',
|
||||
});
|
||||
|
||||
clickHandler = (event: google.maps.MapMouseEvent, fallbackPosition?: google.maps.LatLng) => {
|
||||
const position = getEventPosition(event, fallbackPosition);
|
||||
if (!position || shouldSkipDuplicateClick(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!circleCenter) {
|
||||
circleCenter = position;
|
||||
activeOverlay = new google.maps.Circle({
|
||||
...options.circleOptions,
|
||||
center: circleCenter,
|
||||
radius: 0,
|
||||
map: options.map,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
updateCircleRadius(position);
|
||||
completeActiveOverlay('circle');
|
||||
};
|
||||
mapListeners.push(options.map.addListener('click', clickHandler));
|
||||
|
||||
mapListeners.push(
|
||||
options.map.addListener('mousemove', (event: google.maps.MapMouseEvent) => {
|
||||
const position = getEventPosition(event);
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateCircleRadius(position);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const manager: GoogleMapsDrawingManager = {
|
||||
setDrawingMode(mode) {
|
||||
stopDrawing();
|
||||
drawingMode = mode;
|
||||
|
||||
if (!drawingMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (drawingMode === 'marker') {
|
||||
startMarkerDrawing();
|
||||
} else if (drawingMode === 'polyline' || drawingMode === 'polygon') {
|
||||
startPathDrawing(drawingMode);
|
||||
} else if (drawingMode === 'circle') {
|
||||
startCircleDrawing();
|
||||
}
|
||||
},
|
||||
addListener(eventName, listener) {
|
||||
if (eventName !== 'overlaycomplete') {
|
||||
return createMapsEventListener(() => {});
|
||||
}
|
||||
|
||||
overlayCompleteListeners.add(listener);
|
||||
return createMapsEventListener(() => {
|
||||
overlayCompleteListeners.delete(listener);
|
||||
});
|
||||
},
|
||||
handleClick(event) {
|
||||
clickHandler?.(event);
|
||||
},
|
||||
handleOverlayClick(event, fallbackPosition) {
|
||||
clickHandler?.(event, fallbackPosition);
|
||||
},
|
||||
handleDoubleClick() {
|
||||
doubleClickHandler?.();
|
||||
},
|
||||
completeDrawing() {
|
||||
const minPoints = drawingMode === 'polygon' ? 3 : 2;
|
||||
if ((drawingMode === 'polygon' || drawingMode === 'polyline') && activePath?.getLength() >= minPoints) {
|
||||
completeActiveOverlay(drawingMode);
|
||||
} else if (drawingMode === 'circle' && activeOverlay) {
|
||||
completeActiveOverlay('circle');
|
||||
}
|
||||
},
|
||||
unbindAll() {
|
||||
stopDrawing();
|
||||
overlayCompleteListeners.clear();
|
||||
},
|
||||
};
|
||||
|
||||
manager.setDrawingMode(options.drawingMode);
|
||||
|
||||
return manager;
|
||||
};
|
||||
Reference in New Issue
Block a user