Replace right-click behavior with context menu on waypoints
Right-clicking a waypoint now shows a context menu with: - "Mark as overnight stop" / "Remove overnight stop" (middle waypoints only) - "Delete" (all waypoints) Previously first/last waypoints were deleted on right-click and middle waypoints toggled overnight, with no visual indication of which action would happen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7d0918259d
commit
f4ebf75a42
2 changed files with 75 additions and 6 deletions
|
|
@ -18,6 +18,7 @@ import { NoGoAreaLayer } from "./NoGoAreaLayer";
|
|||
import { ColoredRoute, findSegmentForPoint, type ColorMode } from "./ColoredRoute";
|
||||
import { RouteInteraction } from "./RouteInteraction";
|
||||
import { PoiPanel, PoiMarkers } from "./PoiPanel";
|
||||
import { WaypointContextMenu } from "./WaypointContextMenu";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
/** Distance from a point to a line segment in degrees (approximate) */
|
||||
|
|
@ -368,6 +369,7 @@ export function PlannerMap({ yjs, onRouteRequest, highlightPosition, highlighted
|
|||
useYjsPoiSync(yjs, poiState);
|
||||
const [enabledOverlays, setEnabledOverlays] = useState<string[]>([]);
|
||||
const [selectedBaseLayer, setSelectedBaseLayer] = useState<string>(baseLayers[0]!.name);
|
||||
const [contextMenu, setContextMenu] = useState<{ x: number; y: number; index: number } | null>(null);
|
||||
const [draggingOver, setDraggingOver] = useState(false);
|
||||
const dragCounterRef = useRef(0);
|
||||
const [routeCoordinates, setRouteCoordinates] = useState<[number, number, number][] | null>(null);
|
||||
|
|
@ -687,12 +689,8 @@ export function PlannerMap({ yjs, onRouteRequest, highlightPosition, highlighted
|
|||
},
|
||||
contextmenu: (e) => {
|
||||
L.DomEvent.preventDefault(e as unknown as Event);
|
||||
// Middle waypoints: toggle overnight. First/last: delete.
|
||||
if (i > 0 && i < waypoints.length - 1) {
|
||||
setOvernight(yjs, i, !wp.overnight);
|
||||
} else {
|
||||
deleteWaypoint(i);
|
||||
}
|
||||
const orig = e.originalEvent as MouseEvent;
|
||||
setContextMenu({ x: orig.clientX, y: orig.clientY, index: i });
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
@ -742,6 +740,17 @@ export function PlannerMap({ yjs, onRouteRequest, highlightPosition, highlighted
|
|||
/>
|
||||
)}
|
||||
</MapContainer>
|
||||
{contextMenu && (
|
||||
<WaypointContextMenu
|
||||
position={{ x: contextMenu.x, y: contextMenu.y }}
|
||||
isFirst={contextMenu.index === 0}
|
||||
isLast={contextMenu.index === waypoints.length - 1}
|
||||
isOvernight={waypoints[contextMenu.index]?.overnight ?? false}
|
||||
onDelete={() => deleteWaypoint(contextMenu.index)}
|
||||
onToggleOvernight={() => setOvernight(yjs, contextMenu.index, !waypoints[contextMenu.index]?.overnight)}
|
||||
onClose={() => setContextMenu(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
60
apps/planner/app/components/WaypointContextMenu.tsx
Normal file
60
apps/planner/app/components/WaypointContextMenu.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface WaypointContextMenuProps {
|
||||
position: { x: number; y: number };
|
||||
isFirst: boolean;
|
||||
isLast: boolean;
|
||||
isOvernight: boolean;
|
||||
onDelete: () => void;
|
||||
onToggleOvernight: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function WaypointContextMenu({
|
||||
position,
|
||||
isFirst,
|
||||
isLast,
|
||||
isOvernight,
|
||||
onDelete,
|
||||
onToggleOvernight,
|
||||
onClose,
|
||||
}: WaypointContextMenuProps) {
|
||||
const { t } = useTranslation("planner");
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) onClose();
|
||||
};
|
||||
document.addEventListener("mousedown", handler);
|
||||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, [onClose]);
|
||||
|
||||
const canToggleOvernight = !isFirst && !isLast;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="fixed z-[2000] min-w-40 rounded-md border border-gray-200 bg-white py-1 shadow-lg"
|
||||
style={{ left: position.x, top: position.y }}
|
||||
>
|
||||
{canToggleOvernight && (
|
||||
<button
|
||||
onClick={() => { onToggleOvernight(); onClose(); }}
|
||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-gray-700 hover:bg-gray-100"
|
||||
>
|
||||
<span>☾</span>
|
||||
{isOvernight ? t("multiDay.removeOvernight") : t("multiDay.markOvernight")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => { onDelete(); onClose(); }}
|
||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-red-600 hover:bg-red-50"
|
||||
>
|
||||
<span>×</span>
|
||||
{t("common:delete", "Delete")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue