fix: Fix problems and bugs
This commit is contained in:
@ -75,6 +75,19 @@ import { makeAutoObservable } from "mobx";
|
||||
|
||||
import { stationsStore, routeStore, sightsStore } from "@shared";
|
||||
|
||||
// Функция для сброса кешей карты
|
||||
export const clearMapCaches = () => {
|
||||
// Сброс кешей маршрутов
|
||||
mapStore.routes = [];
|
||||
mapStore.stations = [];
|
||||
mapStore.sights = [];
|
||||
|
||||
// Сброс кешей MapService если он доступен
|
||||
if (typeof window !== "undefined" && (window as any).mapServiceInstance) {
|
||||
(window as any).mapServiceInstance.clearCaches();
|
||||
}
|
||||
};
|
||||
|
||||
interface ApiRoute {
|
||||
id: number;
|
||||
route_number: string;
|
||||
@ -328,6 +341,11 @@ class MapStore {
|
||||
|
||||
const mapStore = new MapStore();
|
||||
|
||||
// Делаем mapStore доступным глобально для сброса кешей
|
||||
if (typeof window !== "undefined") {
|
||||
(window as any).mapStore = mapStore;
|
||||
}
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
export const mapConfig = {
|
||||
center: [30.311, 59.94] as [number, number],
|
||||
@ -453,7 +471,7 @@ class MapService {
|
||||
public routeLayer: VectorLayer<VectorSource<Feature<LineString>>>; // Public for deselect
|
||||
private clusterSource: Cluster;
|
||||
private clusterStyleCache: { [key: number]: Style };
|
||||
private unclusteredRouteIds: Set<string | number> = new Set();
|
||||
|
||||
private tooltipElement: HTMLElement;
|
||||
private tooltipOverlay: Overlay | null;
|
||||
private mode: string | null;
|
||||
@ -488,8 +506,7 @@ class MapService {
|
||||
private sightIconStyle: Style;
|
||||
private selectedSightIconStyle: Style;
|
||||
private drawSightIconStyle: Style;
|
||||
private routeIconStyle: Style;
|
||||
private selectedRouteIconStyle: Style;
|
||||
|
||||
private universalHoverStylePoint: Style;
|
||||
private hoverSightIconStyle: Style;
|
||||
private universalHoverStyleLine: Style;
|
||||
@ -574,21 +591,6 @@ class MapService {
|
||||
}),
|
||||
});
|
||||
|
||||
this.routeIconStyle = new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 8,
|
||||
fill: new Fill({ color: "rgba(34, 197, 94, 0.8)" }), // Green
|
||||
stroke: new Stroke({ color: "#ffffff", width: 1.5 }),
|
||||
}),
|
||||
});
|
||||
this.selectedRouteIconStyle = new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 10,
|
||||
fill: new Fill({ color: "rgba(221, 107, 32, 0.9)" }), // Orange on select
|
||||
stroke: new Stroke({ color: "#ffffff", width: 2 }),
|
||||
}),
|
||||
});
|
||||
|
||||
this.sightIconStyle = new Style({
|
||||
image: new RegularShape({
|
||||
fill: new Fill({ color: "rgba(139, 92, 246, 0.8)" }),
|
||||
@ -659,10 +661,7 @@ class MapService {
|
||||
if (!feature) return this.defaultStyle;
|
||||
const fId = feature.getId();
|
||||
|
||||
if (fId === undefined || !this.unclusteredRouteIds.has(fId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Все маршруты всегда отображаются, так как они не кластеризуются
|
||||
const isSelected =
|
||||
this.selectInteraction?.getFeatures().getArray().includes(feature) ||
|
||||
(fId !== undefined && this.selectedIds.has(fId));
|
||||
@ -705,8 +704,6 @@ class MapService {
|
||||
const originalFeature = featuresInCluster[0];
|
||||
const fId = originalFeature.getId();
|
||||
const featureType = originalFeature.get("featureType");
|
||||
const isProxy = originalFeature.get("isProxy");
|
||||
if (isProxy) return new Style(); // Invisible empty style
|
||||
|
||||
const isSelected = fId !== undefined && this.selectedIds.has(fId);
|
||||
const isHovered = this.hoveredFeatureId === fId;
|
||||
@ -719,45 +716,20 @@ class MapService {
|
||||
|
||||
if (isSelected) {
|
||||
if (featureType === "sight") return this.selectedSightIconStyle;
|
||||
if (featureType === "route") return this.selectedRouteIconStyle;
|
||||
return this.selectedBusIconStyle;
|
||||
}
|
||||
|
||||
if (featureType === "sight") return this.sightIconStyle;
|
||||
if (featureType === "route") return this.routeIconStyle;
|
||||
return this.busIconStyle;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.clusterSource.on("change", () => {
|
||||
const newUnclusteredRouteIds = new Set<string | number>();
|
||||
this.clusterSource
|
||||
.getFeatures()
|
||||
.forEach((clusterFeature: Feature<any>) => {
|
||||
const originalFeatures = clusterFeature.get(
|
||||
"features"
|
||||
) as Feature<Point>[];
|
||||
if (originalFeatures && originalFeatures.length === 1) {
|
||||
const originalFeature = originalFeatures[0];
|
||||
if (originalFeature.get("featureType") === "route") {
|
||||
const featureId = originalFeature.getId();
|
||||
if (featureId !== undefined) {
|
||||
newUnclusteredRouteIds.add(featureId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
newUnclusteredRouteIds.size !== this.unclusteredRouteIds.size ||
|
||||
![...newUnclusteredRouteIds].every((id) =>
|
||||
this.unclusteredRouteIds.has(id)
|
||||
)
|
||||
) {
|
||||
this.unclusteredRouteIds = newUnclusteredRouteIds;
|
||||
this.routeLayer.changed();
|
||||
}
|
||||
// Поскольку маршруты больше не добавляются как точки,
|
||||
// нам не нужно отслеживать unclusteredRouteIds
|
||||
// Все маршруты всегда отображаются как линии
|
||||
this.routeLayer.changed();
|
||||
});
|
||||
|
||||
this.boundHandlePointerMove = this.handlePointerMove.bind(this);
|
||||
@ -1209,23 +1181,7 @@ class MapService {
|
||||
lineFeature.set("featureType", "route");
|
||||
lineFeatures.push(lineFeature);
|
||||
|
||||
if (route.center_longitude != null && route.center_latitude != null) {
|
||||
const centerPoint = new Point(
|
||||
transform(
|
||||
[route.center_longitude, route.center_latitude],
|
||||
"EPSG:4326",
|
||||
projection
|
||||
)
|
||||
);
|
||||
const proxyPointFeature = new Feature({
|
||||
geometry: centerPoint,
|
||||
name: route.route_number,
|
||||
isProxy: true,
|
||||
});
|
||||
proxyPointFeature.setId(routeId);
|
||||
proxyPointFeature.set("featureType", "route");
|
||||
pointFeatures.push(proxyPointFeature);
|
||||
}
|
||||
// Не создаем прокси-точки для маршрутов - они должны оставаться только линиями
|
||||
});
|
||||
|
||||
this.pointSource.addFeatures(pointFeatures);
|
||||
@ -1926,6 +1882,32 @@ class MapService {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
// Метод для сброса кешей карты
|
||||
public clearCaches() {
|
||||
this.clusterStyleCache = {};
|
||||
this.history = [];
|
||||
this.historyIndex = -1;
|
||||
this.beforeActionState = null;
|
||||
this.hoveredFeatureId = null;
|
||||
this.selectedIds.clear();
|
||||
|
||||
// Очищаем источники данных
|
||||
if (this.pointSource) {
|
||||
this.pointSource.clear();
|
||||
}
|
||||
if (this.lineSource) {
|
||||
this.lineSource.clear();
|
||||
}
|
||||
|
||||
// Обновляем слои
|
||||
if (this.clusterLayer) {
|
||||
this.clusterLayer.changed();
|
||||
}
|
||||
if (this.routeLayer) {
|
||||
this.routeLayer.changed();
|
||||
}
|
||||
}
|
||||
|
||||
public saveCurrentPosition(): void {
|
||||
if (!this.map) return;
|
||||
const center = this.map.getView().getCenter();
|
||||
@ -1941,20 +1923,6 @@ class MapService {
|
||||
const featureId = feature.getId();
|
||||
if (!featureType || featureId === undefined || !this.map) return;
|
||||
|
||||
if (
|
||||
featureType === "route" &&
|
||||
feature.getGeometry()?.getType() === "LineString"
|
||||
) {
|
||||
const proxyPoint = this.pointSource.getFeatureById(
|
||||
featureId
|
||||
) as Feature<Point>;
|
||||
if (proxyPoint) {
|
||||
const lineGeom = feature.getGeometry() as LineString;
|
||||
const newCenter = getCenter(lineGeom.getExtent());
|
||||
proxyPoint.getGeometry()?.setCoordinates(newCenter);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof featureId === "number" || !String(featureId).includes("-")) {
|
||||
console.warn(
|
||||
"Skipping save for feature with non-standard ID:",
|
||||
@ -2023,22 +1991,7 @@ class MapService {
|
||||
);
|
||||
feature.setGeometry(lineGeom);
|
||||
|
||||
// Create and add proxy point
|
||||
const centerPointGeom = new Point(
|
||||
transform(
|
||||
[routeData.center_longitude, routeData.center_latitude],
|
||||
"EPSG:4326",
|
||||
projection
|
||||
)
|
||||
);
|
||||
const proxyPointFeature = new Feature({
|
||||
geometry: centerPointGeom,
|
||||
name: displayName,
|
||||
isProxy: true,
|
||||
});
|
||||
proxyPointFeature.setId(newFeatureId);
|
||||
proxyPointFeature.set("featureType", "route");
|
||||
this.pointSource.addFeature(proxyPointFeature);
|
||||
// Не создаем прокси-точку для маршрута - только линия
|
||||
} else {
|
||||
// For points: update existing
|
||||
feature.setId(newFeatureId);
|
||||
@ -2603,6 +2556,12 @@ export const MapPage: React.FC = () => {
|
||||
setSelectedIds
|
||||
);
|
||||
setMapServiceInstance(service);
|
||||
|
||||
// Делаем mapServiceInstance доступным глобально для сброса кешей
|
||||
if (typeof window !== "undefined") {
|
||||
(window as any).mapServiceInstance = service;
|
||||
}
|
||||
|
||||
loadInitialData(service);
|
||||
} catch (e: any) {
|
||||
setError(
|
||||
@ -2615,6 +2574,11 @@ export const MapPage: React.FC = () => {
|
||||
return () => {
|
||||
service?.destroy();
|
||||
setMapServiceInstance(null);
|
||||
|
||||
// Удаляем глобальную ссылку
|
||||
if (typeof window !== "undefined") {
|
||||
delete (window as any).mapServiceInstance;
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
@ -225,7 +225,7 @@ export const LinkedItemsContents = <
|
||||
if (type === "edit") {
|
||||
setError(null);
|
||||
authInstance
|
||||
.get(`/${childResource}/`)
|
||||
.get(`/${childResource}`)
|
||||
.then((response) => {
|
||||
setAllItems(response?.data || []);
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Paper, TextField } from "@mui/material";
|
||||
import { Button, TextField } from "@mui/material";
|
||||
import { snapshotStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
||||
@ -20,50 +20,56 @@ export const SnapshotCreatePage = observer(() => {
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<div className="flex justify-between items-center">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold">Создание снапшота</h1>
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Название"
|
||||
required
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<div className="w-full h-[400px] flex justify-center items-center">
|
||||
<div className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<div className="flex justify-between items-center">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold">Создание снапшота</h1>
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<TextField
|
||||
className="w-full"
|
||||
label="Название"
|
||||
required
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await createSnapshot(name);
|
||||
setIsLoading(false);
|
||||
toast.success("Снапшот успешно создан");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Сохранить"
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await createSnapshot(name);
|
||||
setIsLoading(false);
|
||||
toast.success("Снапшот успешно создан");
|
||||
navigate(-1);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Ошибка при создании снапшота");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}}
|
||||
disabled={isLoading || !name.trim()}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Сохранить"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,3 +1,2 @@
|
||||
export * from "./SnapshotListPage";
|
||||
|
||||
export * from "./SnapshotCreatePage";
|
||||
|
@ -176,7 +176,7 @@ export const LinkedSightsContents = <
|
||||
if (type === "edit") {
|
||||
setError(null);
|
||||
authInstance
|
||||
.get(`/${childResource}/`)
|
||||
.get(`/${childResource}`)
|
||||
.then((response) => {
|
||||
setAllItems(response?.data || []);
|
||||
})
|
||||
|
Reference in New Issue
Block a user