fix: Fix webp + delete ctrl z + filter by city
This commit is contained in:
@@ -595,10 +595,6 @@ interface MapServiceConfig {
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
interface HistoryState {
|
||||
state: string;
|
||||
}
|
||||
|
||||
type FeatureType = "station" | "route" | "sight";
|
||||
|
||||
class MapService {
|
||||
@@ -620,9 +616,6 @@ class MapService {
|
||||
private modifyInteraction: Modify;
|
||||
private selectInteraction: Select;
|
||||
private hoveredFeatureId: string | number | null;
|
||||
private history: HistoryState[];
|
||||
private historyIndex: number;
|
||||
private beforeActionState: string | null = null;
|
||||
private boundHandlePointerMove: (
|
||||
event: MapBrowserEvent<PointerEvent>
|
||||
) => void;
|
||||
@@ -674,8 +667,6 @@ class MapService {
|
||||
this.currentDrawingFeatureType = null;
|
||||
this.currentInteraction = null;
|
||||
this.hoveredFeatureId = null;
|
||||
this.history = [];
|
||||
this.historyIndex = -1;
|
||||
this.clusterStyleCache = {};
|
||||
|
||||
this.setLoading = setLoading;
|
||||
@@ -1037,21 +1028,10 @@ class MapService {
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
this.modifyInteraction.on("modifystart", () => {
|
||||
if (!this.beforeActionState) {
|
||||
this.beforeActionState = this.getCurrentStateAsGeoJSON();
|
||||
}
|
||||
});
|
||||
|
||||
this.modifyInteraction.on("modifyend", (event) => {
|
||||
if (this.beforeActionState) {
|
||||
this.addStateToHistory(this.beforeActionState);
|
||||
}
|
||||
event.features.getArray().forEach((feature) => {
|
||||
this.saveModifiedFeature(feature as Feature<Geometry>);
|
||||
});
|
||||
this.beforeActionState = null;
|
||||
});
|
||||
|
||||
if (this.map) {
|
||||
@@ -1100,11 +1080,6 @@ class MapService {
|
||||
return;
|
||||
}
|
||||
|
||||
const beforeState = this.getCurrentStateAsGeoJSON();
|
||||
if (beforeState) {
|
||||
this.addStateToHistory(beforeState);
|
||||
}
|
||||
|
||||
const newCoordinates = coordinates.filter(
|
||||
(_, index) => index !== closestIndex
|
||||
);
|
||||
@@ -1330,197 +1305,6 @@ class MapService {
|
||||
this.lineSource.addFeatures(lineFeatures);
|
||||
|
||||
this.updateFeaturesInReact();
|
||||
const initialState = this.getCurrentStateAsGeoJSON();
|
||||
if (initialState) {
|
||||
this.addStateToHistory(initialState);
|
||||
}
|
||||
}
|
||||
|
||||
private addStateToHistory(stateToSave: string): void {
|
||||
this.history = this.history.slice(0, this.historyIndex + 1);
|
||||
this.history.push({ state: stateToSave });
|
||||
this.historyIndex = this.history.length - 1;
|
||||
}
|
||||
|
||||
private getCurrentStateAsGeoJSON(): string | null {
|
||||
if (!this.map) return null;
|
||||
const geoJSONFormat = new GeoJSON();
|
||||
const allFeatures = [
|
||||
...this.pointSource.getFeatures(),
|
||||
...this.lineSource.getFeatures(),
|
||||
];
|
||||
return geoJSONFormat.writeFeatures(allFeatures, {
|
||||
dataProjection: "EPSG:4326",
|
||||
featureProjection: this.map.getView().getProjection().getCode(),
|
||||
});
|
||||
}
|
||||
|
||||
private applyHistoryState(geoJSONState: string) {
|
||||
if (!this.map) return;
|
||||
const projection = this.map.getView().getProjection();
|
||||
const geoJSONFormat = new GeoJSON({
|
||||
dataProjection: "EPSG:4326",
|
||||
featureProjection: projection.getCode(),
|
||||
});
|
||||
const features = geoJSONFormat.readFeatures(
|
||||
geoJSONState
|
||||
) as Feature<Geometry>[];
|
||||
|
||||
this.unselect();
|
||||
this.pointSource.clear();
|
||||
this.lineSource.clear();
|
||||
|
||||
const pointFeatures: Feature<Point>[] = [];
|
||||
const lineFeatures: Feature<LineString>[] = [];
|
||||
|
||||
features.forEach((feature) => {
|
||||
const featureType = feature.get("featureType");
|
||||
const isProxy = feature.get("isProxy");
|
||||
if (featureType === "route" && !isProxy) {
|
||||
lineFeatures.push(feature as Feature<LineString>);
|
||||
} else {
|
||||
pointFeatures.push(feature as Feature<Point>);
|
||||
}
|
||||
});
|
||||
|
||||
this.pointSource.addFeatures(pointFeatures);
|
||||
this.lineSource.addFeatures(lineFeatures);
|
||||
|
||||
this.updateFeaturesInReact();
|
||||
|
||||
const newStations: ApiStation[] = [];
|
||||
const newRoutes: ApiRoute[] = [];
|
||||
const newSights: ApiSight[] = [];
|
||||
|
||||
features.forEach((feature) => {
|
||||
const id = feature.getId();
|
||||
if (!id || feature.get("isProxy")) return;
|
||||
|
||||
const [featureType, numericIdStr] = String(id).split("-");
|
||||
const numericId = parseInt(numericIdStr, 10);
|
||||
if (isNaN(numericId)) return;
|
||||
const geometry = feature.getGeometry();
|
||||
if (!geometry) return;
|
||||
const properties = feature.getProperties();
|
||||
|
||||
if (featureType === "station") {
|
||||
const coords = (geometry as Point).getCoordinates();
|
||||
const [lon, lat] = toLonLat(coords, projection);
|
||||
newStations.push({
|
||||
id: numericId,
|
||||
name: properties.name,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
city_id: properties.city_id || 1, // Default city_id if not available
|
||||
});
|
||||
} else if (featureType === "sight") {
|
||||
const coords = (geometry as Point).getCoordinates();
|
||||
const [lon, lat] = toLonLat(coords, projection);
|
||||
newSights.push({
|
||||
id: numericId,
|
||||
name: properties.name,
|
||||
description: properties.description,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
city_id: properties.city_id || 1, // Default city_id if not available
|
||||
});
|
||||
} else if (featureType === "route") {
|
||||
const coords = (geometry as LineString).getCoordinates();
|
||||
const path = coords.map((c) => {
|
||||
const [lon, lat] = toLonLat(c, projection);
|
||||
return [lat, lon] as [number, number];
|
||||
});
|
||||
|
||||
const centerCoords = getCenter(geometry.getExtent());
|
||||
const [center_longitude, center_latitude] = toLonLat(
|
||||
centerCoords,
|
||||
projection
|
||||
);
|
||||
|
||||
newRoutes.push({
|
||||
id: numericId,
|
||||
route_number: properties.name,
|
||||
path: path,
|
||||
center_latitude,
|
||||
center_longitude,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
mapStore.stations = newStations;
|
||||
mapStore.routes = newRoutes.sort((a, b) =>
|
||||
a.route_number.localeCompare(b.route_number)
|
||||
);
|
||||
mapStore.sights = newSights;
|
||||
}
|
||||
|
||||
public undo(): void {
|
||||
if (this.historyIndex > 0) {
|
||||
this.historyIndex--;
|
||||
const stateToRestore = this.history[this.historyIndex].state;
|
||||
this.applyHistoryState(stateToRestore);
|
||||
|
||||
const features = [
|
||||
...this.pointSource.getFeatures().filter((f) => !f.get("isProxy")),
|
||||
...this.lineSource.getFeatures(),
|
||||
];
|
||||
const updatePromises = features.map((feature) => {
|
||||
const featureType = feature.get("featureType");
|
||||
const geoJSONFormat = new GeoJSON({
|
||||
dataProjection: "EPSG:4326",
|
||||
featureProjection: this.map?.getView().getProjection().getCode(),
|
||||
});
|
||||
const featureGeoJSON = geoJSONFormat.writeFeatureObject(feature);
|
||||
return mapStore.updateFeature(featureType, featureGeoJSON);
|
||||
});
|
||||
|
||||
Promise.all(updatePromises)
|
||||
.then(() => {})
|
||||
.catch((error) => {
|
||||
console.error("Failed to update backend after undo:", error);
|
||||
this.historyIndex++;
|
||||
const previousState = this.history[this.historyIndex].state;
|
||||
this.applyHistoryState(previousState);
|
||||
});
|
||||
} else {
|
||||
toast.info("Больше отменять нечего");
|
||||
}
|
||||
}
|
||||
|
||||
public redo(): void {
|
||||
if (this.historyIndex < this.history.length - 1) {
|
||||
this.historyIndex++;
|
||||
const stateToRestore = this.history[this.historyIndex].state;
|
||||
this.applyHistoryState(stateToRestore);
|
||||
|
||||
const features = [
|
||||
...this.pointSource.getFeatures().filter((f) => !f.get("isProxy")),
|
||||
...this.lineSource.getFeatures(),
|
||||
];
|
||||
const updatePromises = features.map((feature) => {
|
||||
const featureType = feature.get("featureType");
|
||||
const geoJSONFormat = new GeoJSON({
|
||||
dataProjection: "EPSG:4326",
|
||||
featureProjection: this.map?.getView().getProjection().getCode(),
|
||||
});
|
||||
const featureGeoJSON = geoJSONFormat.writeFeatureObject(feature);
|
||||
return mapStore.updateFeature(featureType, featureGeoJSON);
|
||||
});
|
||||
|
||||
Promise.all(updatePromises)
|
||||
.then(() => {
|
||||
toast.info("Действие повторено");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to update backend after redo:", error);
|
||||
toast.error("Не удалось обновить данные на сервере");
|
||||
this.historyIndex--;
|
||||
const previousState = this.history[this.historyIndex].state;
|
||||
this.applyHistoryState(previousState);
|
||||
});
|
||||
} else {
|
||||
toast.info("Больше повторять нечего");
|
||||
}
|
||||
}
|
||||
|
||||
private updateFeaturesInReact(): void {
|
||||
@@ -1543,16 +1327,6 @@ class MapService {
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent): void {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "z") {
|
||||
event.preventDefault();
|
||||
this.undo();
|
||||
return;
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "y") {
|
||||
event.preventDefault();
|
||||
this.redo();
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
this.unselect();
|
||||
}
|
||||
@@ -1619,36 +1393,20 @@ class MapService {
|
||||
style: styleForDrawing,
|
||||
});
|
||||
|
||||
this.currentInteraction.on("drawstart", () => {
|
||||
this.beforeActionState = this.getCurrentStateAsGeoJSON();
|
||||
});
|
||||
|
||||
this.currentInteraction.on("drawend", async (event: DrawEvent) => {
|
||||
if (this.beforeActionState) {
|
||||
this.addStateToHistory(this.beforeActionState);
|
||||
}
|
||||
this.beforeActionState = null;
|
||||
|
||||
const feature = event.feature as Feature<Geometry>;
|
||||
const fType = this.currentDrawingFeatureType;
|
||||
if (!fType) return;
|
||||
feature.set("featureType", fType);
|
||||
|
||||
let resourceName: string;
|
||||
const allFeatures = [
|
||||
...this.pointSource.getFeatures(),
|
||||
...this.lineSource.getFeatures(),
|
||||
];
|
||||
|
||||
switch (fType) {
|
||||
case "station":
|
||||
const existingStations = allFeatures.filter(
|
||||
(f) => f.get("featureType") === "station"
|
||||
);
|
||||
const stationNumbers = existingStations
|
||||
.map((f) => {
|
||||
const name = f.get("name") as string;
|
||||
const match = name?.match(/^Остановка (\d+)$/);
|
||||
// Используем полный список из mapStore, а не отфильтрованный
|
||||
const stationNumbers = mapStore.stations
|
||||
.map((station) => {
|
||||
const match = station.name?.match(/^Остановка (\d+)$/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
})
|
||||
.filter((num) => num > 0);
|
||||
@@ -1657,13 +1415,10 @@ class MapService {
|
||||
resourceName = `Остановка ${nextStationNumber}`;
|
||||
break;
|
||||
case "sight":
|
||||
const existingSights = allFeatures.filter(
|
||||
(f) => f.get("featureType") === "sight"
|
||||
);
|
||||
const sightNumbers = existingSights
|
||||
.map((f) => {
|
||||
const name = f.get("name") as string;
|
||||
const match = name?.match(/^Достопримечательность (\d+)$/);
|
||||
// Используем полный список из mapStore, а не отфильтрованный
|
||||
const sightNumbers = mapStore.sights
|
||||
.map((sight) => {
|
||||
const match = sight.name?.match(/^Достопримечательность (\d+)$/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
})
|
||||
.filter((num) => num > 0);
|
||||
@@ -1672,13 +1427,10 @@ class MapService {
|
||||
resourceName = `Достопримечательность ${nextSightNumber}`;
|
||||
break;
|
||||
case "route":
|
||||
const existingRoutes = allFeatures.filter(
|
||||
(f) => f.get("featureType") === "route" && !f.get("isProxy")
|
||||
);
|
||||
const routeNumbers = existingRoutes
|
||||
.map((f) => {
|
||||
const name = f.get("name") as string;
|
||||
const match = name?.match(/^Маршрут (\d+)$/);
|
||||
// Используем полный список из mapStore, а не отфильтрованный
|
||||
const routeNumbers = mapStore.routes
|
||||
.map((route) => {
|
||||
const match = route.route_number?.match(/^Маршрут (\d+)$/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
})
|
||||
.filter((num) => num > 0);
|
||||
@@ -1841,18 +1593,12 @@ class MapService {
|
||||
): void {
|
||||
if (featureId === undefined) return;
|
||||
|
||||
this.beforeActionState = this.getCurrentStateAsGeoJSON();
|
||||
|
||||
const numericId = parseInt(String(featureId).split("-")[1], 10);
|
||||
if (!recourse || isNaN(numericId)) return;
|
||||
|
||||
mapStore
|
||||
.deleteFeature(recourse, numericId)
|
||||
.then(() => {
|
||||
if (this.beforeActionState)
|
||||
this.addStateToHistory(this.beforeActionState);
|
||||
this.beforeActionState = null;
|
||||
|
||||
if (recourse === "route") {
|
||||
const lineFeature = this.lineSource.getFeatureById(featureId);
|
||||
if (lineFeature)
|
||||
@@ -1877,8 +1623,6 @@ class MapService {
|
||||
public deleteMultipleFeatures(featureIds: (string | number)[]): void {
|
||||
if (!featureIds || featureIds.length === 0) return;
|
||||
|
||||
this.beforeActionState = this.getCurrentStateAsGeoJSON();
|
||||
|
||||
const deletePromises = Array.from(featureIds).map((id) => {
|
||||
const recourse = String(id).split("-")[0];
|
||||
const numericId = parseInt(String(id).split("-")[1], 10);
|
||||
@@ -1895,10 +1639,6 @@ class MapService {
|
||||
| number
|
||||
)[];
|
||||
if (successfulDeletes.length > 0) {
|
||||
if (this.beforeActionState)
|
||||
this.addStateToHistory(this.beforeActionState);
|
||||
this.beforeActionState = null;
|
||||
|
||||
successfulDeletes.forEach((id) => {
|
||||
const recourse = String(id).split("-")[0];
|
||||
if (recourse === "route") {
|
||||
@@ -2029,9 +1769,6 @@ class MapService {
|
||||
// Метод для сброса кешей карты
|
||||
public clearCaches() {
|
||||
this.clusterStyleCache = {};
|
||||
this.history = [];
|
||||
this.historyIndex = -1;
|
||||
this.beforeActionState = null;
|
||||
this.hoveredFeatureId = null;
|
||||
this.selectedIds.clear();
|
||||
|
||||
@@ -2086,9 +1823,6 @@ class MapService {
|
||||
} catch (error) {
|
||||
console.error("Failed to update feature:", error);
|
||||
toast.error(`Не удалось обновить: ${error}`);
|
||||
if (this.beforeActionState) {
|
||||
this.applyHistoryState(this.beforeActionState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2156,10 +1890,6 @@ class MapService {
|
||||
if (this.pointSource.hasFeature(feature as Feature<Point>))
|
||||
this.pointSource.removeFeature(feature as Feature<Point>);
|
||||
}
|
||||
if (this.beforeActionState) {
|
||||
this.applyHistoryState(this.beforeActionState);
|
||||
}
|
||||
this.beforeActionState = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2994,18 +2724,6 @@ export const MapPage: React.FC = observer(() => {
|
||||
<span className="font-mono bg-gray-100 px-1 rounded">Esc</span>{" "}
|
||||
- Отменить выделение
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-mono bg-gray-100 px-1 rounded">
|
||||
Ctrl+Z
|
||||
</span>{" "}
|
||||
- Отменить действие
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-mono bg-gray-100 px-1 rounded">
|
||||
Ctrl+Y
|
||||
</span>{" "}
|
||||
- Повторить действие
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
onClick={() => setShowHelp(false)}
|
||||
|
||||
Reference in New Issue
Block a user