snap radius to 2.5 meters and added indicator for snap
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useMapEvents, Polyline, Marker } from 'react-leaflet';
|
import { useMapEvents, Polyline, Marker, Circle } from 'react-leaflet';
|
||||||
import { useDrawingStore } from '../../stores/drawingStore';
|
import { useDrawingStore } from '../../stores/drawingStore';
|
||||||
import { useUIStore } from '../../stores/uiStore';
|
import { useUIStore } from '../../stores/uiStore';
|
||||||
import { mapItemService } from '../../services/mapItemService';
|
import { mapItemService } from '../../services/mapItemService';
|
||||||
@@ -18,6 +18,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
const [allItems, setAllItems] = useState<any[]>([]);
|
const [allItems, setAllItems] = useState<any[]>([]);
|
||||||
const [startDeviceId, setStartDeviceId] = useState<string | null>(null);
|
const [startDeviceId, setStartDeviceId] = useState<string | null>(null);
|
||||||
const [endDeviceId, setEndDeviceId] = useState<string | null>(null);
|
const [endDeviceId, setEndDeviceId] = useState<string | null>(null);
|
||||||
|
const [nearbyDevice, setNearbyDevice] = useState<any | null>(null);
|
||||||
|
const [nearbyFullDevice, setNearbyFullDevice] = useState<any | null>(null);
|
||||||
|
|
||||||
const isCableTool = ['fiber', 'cat6', 'cat6_poe'].includes(activeTool);
|
const isCableTool = ['fiber', 'cat6', 'cat6_poe'].includes(activeTool);
|
||||||
const isDeviceTool = ['switch', 'indoor_ap', 'outdoor_ap', 'other_device', 'info'].includes(activeTool);
|
const isDeviceTool = ['switch', 'indoor_ap', 'outdoor_ap', 'other_device', 'info'].includes(activeTool);
|
||||||
@@ -37,7 +39,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
}, [mapId]);
|
}, [mapId]);
|
||||||
|
|
||||||
// Find nearby device for snapping (exclude info markers)
|
// Find nearby device for snapping (exclude info markers)
|
||||||
const findNearbyDevice = (lat: number, lng: number, radiusMeters = 1): any | null => {
|
const findNearbyDevice = (lat: number, lng: number, radiusMeters = 2.5): any | null => {
|
||||||
const devices = allItems.filter(item =>
|
const devices = allItems.filter(item =>
|
||||||
['switch', 'indoor_ap', 'outdoor_ap', 'other_device'].includes(item.type) &&
|
['switch', 'indoor_ap', 'outdoor_ap', 'other_device'].includes(item.type) &&
|
||||||
item.geometry.type === 'Point'
|
item.geometry.type === 'Point'
|
||||||
@@ -78,6 +80,10 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
let { lat, lng } = e.latlng;
|
let { lat, lng } = e.latlng;
|
||||||
let clickedDevice = null;
|
let clickedDevice = null;
|
||||||
|
|
||||||
|
// Clear snap indicator on click
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
|
|
||||||
// Check for nearby device when drawing cables
|
// Check for nearby device when drawing cables
|
||||||
if (isCableTool && map) {
|
if (isCableTool && map) {
|
||||||
clickedDevice = findNearbyDevice(lat, lng);
|
clickedDevice = findNearbyDevice(lat, lng);
|
||||||
@@ -151,7 +157,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
// Wireless mesh - connect AP to AP only
|
// Wireless mesh - connect AP to AP only
|
||||||
if (isWirelessTool) {
|
if (isWirelessTool) {
|
||||||
// Must click on an AP
|
// Must click on an AP
|
||||||
const ap = findNearbyDevice(lat, lng, 1);
|
const ap = findNearbyDevice(lat, lng, 2.5);
|
||||||
if (!ap || !['indoor_ap', 'outdoor_ap'].includes(ap.type)) {
|
if (!ap || !['indoor_ap', 'outdoor_ap'].includes(ap.type)) {
|
||||||
showToast('Wireless mesh can only connect between Access Points. Please click on an AP.', 'error');
|
showToast('Wireless mesh can only connect between Access Points. Please click on an AP.', 'error');
|
||||||
return;
|
return;
|
||||||
@@ -191,6 +197,40 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
if (isDrawing && (isCableTool || isWirelessTool)) {
|
if (isDrawing && (isCableTool || isWirelessTool)) {
|
||||||
setCursorPosition([e.latlng.lat, e.latlng.lng]);
|
setCursorPosition([e.latlng.lat, e.latlng.lng]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for nearby device to show snap indicator
|
||||||
|
if ((isCableTool || isWirelessTool) && map) {
|
||||||
|
const nearby = findNearbyDevice(e.latlng.lat, e.latlng.lng);
|
||||||
|
|
||||||
|
// For wireless mesh, only show indicator for APs
|
||||||
|
if (isWirelessTool) {
|
||||||
|
if (nearby && ['indoor_ap', 'outdoor_ap'].includes(nearby.type)) {
|
||||||
|
setNearbyDevice(nearby);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
|
} else {
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
|
}
|
||||||
|
} else if (isCableTool) {
|
||||||
|
// For cables, check port availability
|
||||||
|
if (nearby) {
|
||||||
|
if (hasAvailablePorts(nearby)) {
|
||||||
|
setNearbyDevice(nearby);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
|
} else {
|
||||||
|
// Device has full ports - show red indicator
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(nearby);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
contextmenu: async (e) => {
|
contextmenu: async (e) => {
|
||||||
@@ -214,6 +254,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
if (e.key === 'Escape' && isDrawing) {
|
if (e.key === 'Escape' && isDrawing) {
|
||||||
resetDrawing();
|
resetDrawing();
|
||||||
setCursorPosition(null);
|
setCursorPosition(null);
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -221,6 +263,12 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [isDrawing, resetDrawing]);
|
}, [isDrawing, resetDrawing]);
|
||||||
|
|
||||||
|
// Clear nearby device indicator when tool changes
|
||||||
|
useEffect(() => {
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
|
}, [activeTool]);
|
||||||
|
|
||||||
const finishCable = async () => {
|
const finishCable = async () => {
|
||||||
if (drawingPoints.length < 2) return;
|
if (drawingPoints.length < 2) return;
|
||||||
|
|
||||||
@@ -244,6 +292,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
setCursorPosition(null);
|
setCursorPosition(null);
|
||||||
setStartDeviceId(null);
|
setStartDeviceId(null);
|
||||||
setEndDeviceId(null);
|
setEndDeviceId(null);
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
// Reload items
|
// Reload items
|
||||||
const items = await mapItemService.getMapItems(mapId);
|
const items = await mapItemService.getMapItems(mapId);
|
||||||
setAllItems(items);
|
setAllItems(items);
|
||||||
@@ -283,6 +333,8 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
setCursorPosition(null);
|
setCursorPosition(null);
|
||||||
setStartDeviceId(null);
|
setStartDeviceId(null);
|
||||||
setEndDeviceId(null);
|
setEndDeviceId(null);
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
// Reload items
|
// Reload items
|
||||||
const items = await mapItemService.getMapItems(mapId);
|
const items = await mapItemService.getMapItems(mapId);
|
||||||
setAllItems(items);
|
setAllItems(items);
|
||||||
@@ -293,50 +345,81 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
setCursorPosition(null);
|
setCursorPosition(null);
|
||||||
setStartDeviceId(null);
|
setStartDeviceId(null);
|
||||||
setEndDeviceId(null);
|
setEndDeviceId(null);
|
||||||
|
setNearbyDevice(null);
|
||||||
|
setNearbyFullDevice(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render drawing preview
|
// Render drawing preview and snap indicator
|
||||||
if (isDrawing && drawingPoints.length > 0) {
|
const color = isCableTool
|
||||||
const color = isCableTool
|
? CABLE_COLORS[activeTool as CableType]
|
||||||
? CABLE_COLORS[activeTool as CableType]
|
: isWirelessTool
|
||||||
: isWirelessTool
|
? '#10B981'
|
||||||
? '#10B981'
|
: '#6B7280';
|
||||||
: '#6B7280';
|
|
||||||
|
|
||||||
const dashArray = isWirelessTool ? '10, 10' : undefined;
|
const dashArray = isWirelessTool ? '10, 10' : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Main line connecting all points */}
|
{/* Green snap indicator - show when hovering near a device with available ports */}
|
||||||
{drawingPoints.length > 1 && (
|
{nearbyDevice && nearbyDevice.geometry.type === 'Point' && (
|
||||||
<Polyline
|
<Circle
|
||||||
positions={drawingPoints}
|
center={[nearbyDevice.geometry.coordinates[1], nearbyDevice.geometry.coordinates[0]]}
|
||||||
color={color}
|
radius={3}
|
||||||
weight={3}
|
pathOptions={{
|
||||||
dashArray={dashArray}
|
color: '#10B981',
|
||||||
opacity={0.8}
|
fillColor: '#10B981',
|
||||||
/>
|
fillOpacity: 0.2,
|
||||||
)}
|
weight: 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Preview line from last point to cursor */}
|
{/* Red snap indicator - show when hovering near a device with full ports */}
|
||||||
{cursorPosition && drawingPoints.length > 0 && (
|
{nearbyFullDevice && nearbyFullDevice.geometry.type === 'Point' && (
|
||||||
<Polyline
|
<Circle
|
||||||
positions={[drawingPoints[drawingPoints.length - 1], cursorPosition]}
|
center={[nearbyFullDevice.geometry.coordinates[1], nearbyFullDevice.geometry.coordinates[0]]}
|
||||||
color={color}
|
radius={3}
|
||||||
weight={3}
|
pathOptions={{
|
||||||
dashArray="5, 5"
|
color: '#EF4444',
|
||||||
opacity={0.5}
|
fillColor: '#EF4444',
|
||||||
/>
|
fillOpacity: 0.2,
|
||||||
)}
|
weight: 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Markers at each point */}
|
{/* Drawing preview - only show when actively drawing */}
|
||||||
{drawingPoints.map((point, idx) => (
|
{isDrawing && drawingPoints.length > 0 && (
|
||||||
<Marker key={idx} position={point} />
|
<>
|
||||||
))}
|
{/* Main line connecting all points */}
|
||||||
</>
|
{drawingPoints.length > 1 && (
|
||||||
);
|
<Polyline
|
||||||
}
|
positions={drawingPoints}
|
||||||
|
color={color}
|
||||||
|
weight={3}
|
||||||
|
dashArray={dashArray}
|
||||||
|
opacity={0.8}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
return null;
|
{/* Preview line from last point to cursor */}
|
||||||
|
{cursorPosition && drawingPoints.length > 0 && (
|
||||||
|
<Polyline
|
||||||
|
positions={[drawingPoints[drawingPoints.length - 1], cursorPosition]}
|
||||||
|
color={color}
|
||||||
|
weight={3}
|
||||||
|
dashArray="5, 5"
|
||||||
|
opacity={0.5}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Markers at each point */}
|
||||||
|
{drawingPoints.map((point, idx) => (
|
||||||
|
<Marker key={idx} position={point} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user