From d415441af80f140e3497d8914ff9d5da7ce8dec2 Mon Sep 17 00:00:00 2001 From: itoshi Date: Mon, 16 Jun 2025 12:26:19 +0300 Subject: [PATCH] fix: Add `route-station` link area --- src/pages/MapPage/index.tsx | 187 ++++---- src/pages/Route/LinekedStations.tsx | 425 ++++++++++++++++++ src/pages/Route/RouteCreatePage/index.tsx | 3 +- src/pages/Route/RouteEditPage/index.tsx | 46 +- .../Route/route-preview/RightSidebar.tsx | 12 +- src/shared/store/CreateSightStore/index.tsx | 1 + src/shared/store/EditSightStore/index.tsx | 2 + src/shared/store/RouteStore/index.ts | 13 +- 8 files changed, 570 insertions(+), 119 deletions(-) create mode 100644 src/pages/Route/LinekedStations.tsx diff --git a/src/pages/MapPage/index.tsx b/src/pages/MapPage/index.tsx index 51e813b..de06029 100644 --- a/src/pages/MapPage/index.tsx +++ b/src/pages/MapPage/index.tsx @@ -172,7 +172,7 @@ class MapStore { } else if (featureType === "route") { data = { route_number: properties.name, - path: geometry.coordinates, + path: geometry.coordinates.map((coord: any) => [coord[1], coord[0]]), // Swap coordinates }; } else if (featureType === "sight") { data = { @@ -210,8 +210,8 @@ class MapStore { { ...oldData, path: data.path, - center_latitude: data.path[0][1], - center_longitude: data.path[0][0], + center_latitude: data.path[0][0], // First coordinate is latitude + center_longitude: data.path[0][1], // Second coordinate is longitude } ); } @@ -485,65 +485,38 @@ class MapService { const isHovered = this.hoveredFeatureId === fId; const isLassoSelected = fId !== undefined && this.selectedIds.has(fId); - if (geometryType === "Point") { - const defaultPointStyle = - featureType === "sight" ? this.sightIconStyle : this.busIconStyle; - const selectedPointStyle = - featureType === "sight" - ? this.selectedSightIconStyle - : this.selectedBusIconStyle; - - if (isEditSelected) { - return selectedPointStyle; + if (isHovered) { + if (geometryType === "Point") { + return featureType === "sight" + ? this.hoverSightIconStyle + : this.universalHoverStylePoint; } - if (isHovered) { - // Only apply hover styles if not in edit mode - if (this.mode !== "edit") { - return featureType === "sight" - ? this.hoverSightIconStyle - : this.universalHoverStylePoint; - } - return defaultPointStyle; - } - - if (isLassoSelected) { - let imageStyle; - if (featureType === "sight") { - imageStyle = new RegularShape({ - fill: new Fill({ color: "#14b8a6" }), - stroke: new Stroke({ color: "#fff", width: 2 }), - points: 5, - radius: 12, - radius2: 6, - angle: 0, - }); - } else { - imageStyle = new CircleStyle({ - radius: 10, - fill: new Fill({ color: "#14b8a6" }), - stroke: new Stroke({ color: "#fff", width: 2 }), - }); - } - return new Style({ image: imageStyle, zIndex: Infinity }); - } - - return defaultPointStyle; - } else if (geometryType === "LineString") { - if (isEditSelected) { - return this.selectedStyle; - } - if (isHovered) { - return this.universalHoverStyleLine; - } - if (isLassoSelected) { - return new Style({ - stroke: new Stroke({ color: "#14b8a6", width: 6 }), - zIndex: Infinity, - }); - } - return this.defaultStyle; + return this.universalHoverStyleLine; } + if (isLassoSelected) { + if (geometryType === "Point") { + return featureType === "sight" + ? this.selectedSightIconStyle + : this.selectedBusIconStyle; + } + return this.selectedStyle; + } + + if (isEditSelected) { + if (geometryType === "Point") { + return featureType === "sight" + ? this.selectedSightIconStyle + : this.selectedBusIconStyle; + } + return this.selectedStyle; + } + + if (geometryType === "Point") { + return featureType === "sight" + ? this.sightIconStyle + : this.busIconStyle; + } return this.defaultStyle; }, }); @@ -763,7 +736,9 @@ class MapService { if (!route.path || route.path.length === 0) return; const coordinates = route.path .filter((c) => c[0] != null && c[1] != null) - .map((c) => transform(c, "EPSG:4326", projection)); + .map((c: [number, number]) => + transform([c[1], c[0]], "EPSG:4326", projection) + ); // Swap coordinates if (coordinates.length === 0) return; const line = new LineString(coordinates); const feature = new Feature({ geometry: line, name: route.route_number }); @@ -866,6 +841,11 @@ class MapService { this.redo(); return; } + if ((event.ctrlKey || event.metaKey) && event.key === "r") { + event.preventDefault(); + this.unselect(); + return; + } if (event.key === "Escape") { this.unselect(); } @@ -1090,29 +1070,29 @@ class MapService { ); if (!featureAtPixel) { - if (ctrlKey) { - // При ctrl + клик вне сущности сбрасываем выбор - this.setSelectedIds(new Set()); - } + if (ctrlKey) this.unselect(); return; } const featureId = featureAtPixel.getId(); if (featureId === undefined) return; + const newSet = new Set(this.selectedIds); + if (ctrlKey) { - // При ctrl + клик на сущность добавляем/удаляем её из выбора - const newSet = new Set(this.selectedIds); + // Toggle selection for the clicked feature if (newSet.has(featureId)) { newSet.delete(featureId); } else { newSet.add(featureId); } - this.setSelectedIds(newSet); } else { - // При обычном клике на сущность выбираем только её - this.setSelectedIds(new Set([featureId])); + // Single selection + newSet.clear(); + newSet.add(featureId); } + + this.setSelectedIds(newSet); } public selectFeature(featureId: string | number | undefined): void { @@ -1127,14 +1107,6 @@ class MapService { return; } - if (this.mode === "edit") { - this.selectInteraction.getFeatures().clear(); - this.selectInteraction.getFeatures().push(feature); - // @ts-ignore - const selectEvent = new SelectEvent("select", [feature], []); - this.selectInteraction.dispatchEvent(selectEvent); - } - this.setSelectedIds(new Set([featureId])); const view = this.map.getView(); @@ -1286,7 +1258,30 @@ class MapService { public setSelectedIds(ids: Set) { this.selectedIds = new Set(ids); if (this.onSelectionChange) this.onSelectionChange(this.selectedIds); - this.vectorLayer.changed(); + + // Update selectInteraction to match selectedIds + if (this.selectInteraction) { + this.selectInteraction.getFeatures().clear(); + ids.forEach((id) => { + const feature = this.vectorSource.getFeatureById(id); + if (feature) { + this.selectInteraction.getFeatures().push(feature); + } + }); + } + + // Update modifyInteraction + this.modifyInteraction.setActive(ids.size > 0); + + // Update feature selection in sidebar + if (ids.size === 1) { + const feature = this.vectorSource.getFeatureById(Array.from(ids)[0]); + if (feature) { + this.onFeatureSelect(feature); + } + } else { + this.onFeatureSelect(null); + } } public getSelectedIds() { @@ -1501,8 +1496,10 @@ const MapSightbar: React.FC = ({ }, [mapFeatures, searchQuery]); const handleFeatureClick = useCallback( - // @ts-ignore - (id) => mapService?.selectFeature(id), + (id: string | number | undefined) => { + if (!id || !mapService) return; + mapService.selectFeature(id); + }, [mapService] ); @@ -1521,7 +1518,7 @@ const MapSightbar: React.FC = ({ const handleCheckboxChange = useCallback( (id: string | number | undefined) => { - if (id === undefined) return; + if (!id || !mapService) return; const newSet = new Set(selectedIds); if (newSet.has(id)) { newSet.delete(id); @@ -1529,11 +1526,9 @@ const MapSightbar: React.FC = ({ newSet.add(id); } setSelectedIds(newSet); - if (mapService) { - mapService.setSelectedIds(newSet); - } + mapService.setSelectedIds(newSet); }, - [selectedIds, setSelectedIds, mapService] + [mapService, selectedIds, setSelectedIds] ); const handleBulkDelete = useCallback(() => { @@ -1630,7 +1625,7 @@ const MapSightbar: React.FC = ({ handleCheckboxChange(sId)} onClick={(e) => e.stopPropagation()} aria-label={`Выбрать ${sName}`} @@ -1719,7 +1714,7 @@ const MapSightbar: React.FC = ({ handleCheckboxChange(lId)} onClick={(e) => e.stopPropagation()} aria-label={`Выбрать ${lName}`} @@ -1808,7 +1803,7 @@ const MapSightbar: React.FC = ({ handleCheckboxChange(sId)} onClick={(e) => e.stopPropagation()} aria-label={`Выбрать ${sName}`} @@ -2008,11 +2003,13 @@ export const MapPage: React.FC = () => { ); const handleMapClick = useCallback( - (event: MapBrowserEvent) => { - if (!mapServiceInstance) return; - mapServiceInstance.handleMapClick(event, event.originalEvent.ctrlKey); + (event: any) => { + if (!mapServiceInstance || isLassoActive) return; + const ctrlKey = + event.originalEvent.ctrlKey || event.originalEvent.metaKey; + mapServiceInstance.handleMapClick(event, ctrlKey); }, - [mapServiceInstance] + [mapServiceInstance, isLassoActive] ); useEffect(() => { @@ -2211,6 +2208,12 @@ export const MapPage: React.FC = () => { {" "} - Повторить действие +
  • + + Ctrl+R + {" "} + - Отменить выделение +
  • + + )} + + ); +}; diff --git a/src/pages/Route/RouteCreatePage/index.tsx b/src/pages/Route/RouteCreatePage/index.tsx index 627795a..e17a76c 100644 --- a/src/pages/Route/RouteCreatePage/index.tsx +++ b/src/pages/Route/RouteCreatePage/index.tsx @@ -184,9 +184,8 @@ export const RouteCreatePage = observer(() => { onChange={(e) => setRouteNumber(e.target.value)} /> { diff --git a/src/pages/Route/RouteEditPage/index.tsx b/src/pages/Route/RouteEditPage/index.tsx index 3d3a1bb..dfcc3c2 100644 --- a/src/pages/Route/RouteEditPage/index.tsx +++ b/src/pages/Route/RouteEditPage/index.tsx @@ -19,7 +19,8 @@ import { carrierStore } from "../../../shared/store/CarrierStore"; import { articlesStore } from "../../../shared/store/ArticlesStore"; import { routeStore } from "../../../shared/store/RouteStore"; import { toast } from "react-toastify"; -import { languageStore } from "@shared"; +import { languageStore, stationsStore } from "@shared"; +import { LinkedItems } from "../LinekedStations"; export const RouteEditPage = observer(() => { const navigate = useNavigate(); @@ -34,6 +35,7 @@ export const RouteEditPage = observer(() => { const response = await routeStore.getRoute(Number(id)); routeStore.setEditRouteData(response); carrierStore.getCarriers(language); + stationsStore.getStations(); articlesStore.getArticleList(); }; fetchData(); @@ -150,8 +152,7 @@ export const RouteEditPage = observer(() => { } /> { routeStore.setEditRouteData({ - scale_min: Number(e.target.value), + scale_min: + e.target.value === "" ? null : parseFloat(e.target.value), }) } /> routeStore.setEditRouteData({ - scale_max: Number(e.target.value), + scale_max: + e.target.value === "" ? null : parseFloat(e.target.value), }) } /> routeStore.setEditRouteData({ - rotate: Number(e.target.value), + rotate: + e.target.value === "" ? null : parseFloat(e.target.value), }) } /> routeStore.setEditRouteData({ - center_latitude: Number(e.target.value), + center_latitude: e.target.value, }) } /> routeStore.setEditRouteData({ - center_longitude: Number(e.target.value), + center_longitude: e.target.value, }) } /> + + { + routeStore.getRoute(Number(id)); + }} + /> +