diff --git a/src/pages/Route/route-preview/LeftSidebar.tsx b/src/pages/Route/route-preview/LeftSidebar.tsx
index d48d637..de2794d 100644
--- a/src/pages/Route/route-preview/LeftSidebar.tsx
+++ b/src/pages/Route/route-preview/LeftSidebar.tsx
@@ -61,6 +61,7 @@ export const LeftSidebar = observer(({ open, onToggle }: LeftSidebarProps) => {
width="100%"
spacing={4}
alignItems="stretch"
+ justifyContent="space-between"
sx={{
opacity: open ? 1 : 0,
transition: "opacity 0.25s ease",
@@ -68,71 +69,72 @@ export const LeftSidebar = observer(({ open, onToggle }: LeftSidebarProps) => {
display: open ? "flex" : "none",
}}
>
- }
- >
- Назад
-
-
-
-
+ }
>
- {carrierThumbnail && (
-
- )}
-
- При поддержке Правительства
-
-
-
+ Назад
+
-
-
-
-
+
+
+ {carrierThumbnail && (
+
+ )}
+
+ При поддержке Правительства
+
+
+
+
+
diff --git a/src/pages/Route/route-preview/RightSidebar.tsx b/src/pages/Route/route-preview/RightSidebar.tsx
index ceabec4..add9628 100644
--- a/src/pages/Route/route-preview/RightSidebar.tsx
+++ b/src/pages/Route/route-preview/RightSidebar.tsx
@@ -447,6 +447,21 @@ export function RightSidebar() {
>
Сохранить изменения
+
+
);
}
diff --git a/src/pages/Route/route-preview/webgl-prototype/WebGLRouteMapPrototype.tsx b/src/pages/Route/route-preview/webgl-prototype/WebGLRouteMapPrototype.tsx
index 18778b1..d16612d 100644
--- a/src/pages/Route/route-preview/webgl-prototype/WebGLRouteMapPrototype.tsx
+++ b/src/pages/Route/route-preview/webgl-prototype/WebGLRouteMapPrototype.tsx
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { flushSync } from "react-dom";
import type { PointerEvent as ReactPointerEvent } from "react";
import { observer } from "mobx-react-lite";
@@ -592,11 +593,18 @@ export const WebGLRouteMapPrototype = observer(() => {
}, [cancelScheduledCenterCommit]);
const updateTransform = useCallback(
- (next: Transform) => {
+ (next: Transform, options?: { immediate?: boolean }) => {
const adjusted = clampTransformScale(next);
transformRef.current = adjusted;
- setTransformState(adjusted);
- setSharedScale(adjusted.scale);
+ if (options?.immediate) {
+ flushSync(() => {
+ setTransformState(adjusted);
+ setSharedScale(adjusted.scale);
+ });
+ } else {
+ setTransformState(adjusted);
+ setSharedScale(adjusted.scale);
+ }
computeCenterCoordinates(adjusted);
},
[clampTransformScale, setSharedScale, computeCenterCoordinates]
@@ -1129,13 +1137,16 @@ export const WebGLRouteMapPrototype = observer(() => {
}
const { scale, translation } = transform;
- const pointOuterSizePx = clamp(scale * 13.3333, 6, 120);
+
+ const desiredRouteWidthCss = 13;
+ const desiredStationDiameterCss = 30;
+ const pointOuterSizePx = desiredStationDiameterCss * dpr;
const pointInnerSizePx = pointOuterSizePx * 0.8;
if (rotatedRouteVertices.length >= 4) {
gl.useProgram(lineProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, lineBuffer);
- const lineWidth = pointInnerSizePx / scale;
+ const lineWidth = (desiredRouteWidthCss * dpr) / scale;
const thickVertices = generateThickLineGeometry(
rotatedRouteVertices,
lineWidth
@@ -1494,7 +1505,7 @@ export const WebGLRouteMapPrototype = observer(() => {
y: transform.translation.y + dy,
},
};
- updateTransform(next);
+ updateTransform(next, { immediate: true });
drawSceneRef.current();
};
@@ -1570,13 +1581,16 @@ export const WebGLRouteMapPrototype = observer(() => {
scaleLimitsRef.current.max
);
- updateTransform({
- scale: clampedScale,
- translation: {
- x: midpoint.x - worldMidpoint.x * clampedScale,
- y: midpoint.y - worldMidpoint.y * clampedScale,
+ updateTransform(
+ {
+ scale: clampedScale,
+ translation: {
+ x: midpoint.x - worldMidpoint.x * clampedScale,
+ y: midpoint.y - worldMidpoint.y * clampedScale,
+ },
},
- });
+ { immediate: true }
+ );
drawSceneRef.current();
}
};
@@ -1627,13 +1641,16 @@ export const WebGLRouteMapPrototype = observer(() => {
y: (position.y - transform.translation.y) / transform.scale,
};
- updateTransform({
- scale: clampedScale,
- translation: {
- x: position.x - worldPoint.x * clampedScale,
- y: position.y - worldPoint.y * clampedScale,
+ updateTransform(
+ {
+ scale: clampedScale,
+ translation: {
+ x: position.x - worldPoint.x * clampedScale,
+ y: position.y - worldPoint.y * clampedScale,
+ },
},
- });
+ { immediate: true }
+ );
drawSceneRef.current();
scheduleCenterCommit();
};
diff --git a/src/pages/Sight/LinkedStations.tsx b/src/pages/Sight/LinkedStations.tsx
index bffbbe2..73b1afa 100644
--- a/src/pages/Sight/LinkedStations.tsx
+++ b/src/pages/Sight/LinkedStations.tsx
@@ -118,6 +118,10 @@ const LinkedStationsContentsInner = <
const parentResource = "sight";
const childResource = "station";
+ const buildPayload = (ids: number[]) => ({
+ [`${childResource}_ids`]: ids,
+ });
+
const availableItems = allItems
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
.filter((item) => {
@@ -159,9 +163,7 @@ const LinkedStationsContentsInner = <
const linkItem = () => {
if (selectedItemId !== null) {
setError(null);
- const requestData = {
- station_id: selectedItemId,
- };
+ const requestData = buildPayload([selectedItemId]);
setIsLinkingSingle(true);
authInstance
@@ -193,7 +195,7 @@ const LinkedStationsContentsInner = <
});
authInstance
.delete(`/${parentResource}/${parentId}/${childResource}`, {
- data: { [`${childResource}_id`]: itemId },
+ data: buildPayload([itemId]),
})
.then(() => {
setLinkedItems(linkedItems.filter((item) => item.id !== itemId));
@@ -228,46 +230,28 @@ const LinkedStationsContentsInner = <
setIsLinkingBulk(true);
const idsToLink = Array.from(selectedItems);
- const linkedIds: number[] = [];
- const failedIds: number[] = [];
- for (const id of idsToLink) {
- try {
- await authInstance.post(`/${parentResource}/${parentId}/${childResource}`, {
- station_id: id,
- });
- linkedIds.push(id);
- } catch (error) {
- console.error("Error linking station:", error);
- failedIds.push(id);
- }
- }
+ try {
+ await authInstance.post(
+ `/${parentResource}/${parentId}/${childResource}`,
+ buildPayload(idsToLink)
+ );
- if (linkedIds.length > 0) {
- const newItems = allItems.filter((item) => linkedIds.includes(item.id));
+ const newItems = allItems.filter((item) => idsToLink.includes(item.id));
setLinkedItems((prev) => {
const existingIds = new Set(prev.map((item) => item.id));
const additions = newItems.filter((item) => !existingIds.has(item.id));
return [...prev, ...additions];
});
+ setSelectedItems((prev) => {
+ const remaining = new Set(prev);
+ idsToLink.forEach((id) => remaining.delete(id));
+ return remaining;
+ });
onUpdate?.();
- }
-
- setSelectedItems((prev) => {
- if (linkedIds.length === 0) {
- return prev;
- }
- const remaining = new Set(prev);
- linkedIds.forEach((id) => remaining.delete(id));
- return failedIds.length > 0 ? remaining : new Set();
- });
-
- if (failedIds.length > 0) {
- setError(
- failedIds.length === idsToLink.length
- ? "Failed to link stations"
- : "Some stations failed to link"
- );
+ } catch (error) {
+ console.error("Error linking stations:", error);
+ setError("Failed to link stations");
}
setIsLinkingBulk(false);
@@ -303,39 +287,26 @@ const LinkedStationsContentsInner = <
return next;
});
- const detachedIds: number[] = [];
- const failedIds: number[] = [];
+ try {
+ await authInstance.delete(
+ `/${parentResource}/${parentId}/${childResource}`,
+ {
+ data: buildPayload(idsToDetach),
+ }
+ );
- for (const itemId of idsToDetach) {
- try {
- await authInstance.delete(`/${parentResource}/${parentId}/${childResource}`, {
- data: { [`${childResource}_id`]: itemId },
- });
- detachedIds.push(itemId);
- } catch (error) {
- console.error("Error deleting station:", error);
- failedIds.push(itemId);
- }
- }
-
- if (detachedIds.length > 0) {
setLinkedItems((prev) =>
- prev.filter((item) => !detachedIds.includes(item.id))
+ prev.filter((item) => !idsToDetach.includes(item.id))
);
setSelectedToDetach((prev) => {
const remaining = new Set(prev);
- detachedIds.forEach((id) => remaining.delete(id));
- return failedIds.length > 0 ? remaining : new Set();
+ idsToDetach.forEach((id) => remaining.delete(id));
+ return remaining;
});
onUpdate?.();
- }
-
- if (failedIds.length > 0) {
- setError(
- failedIds.length === idsToDetach.length
- ? "Failed to delete stations"
- : "Some stations failed to delete"
- );
+ } catch (error) {
+ console.error("Error deleting stations:", error);
+ setError("Failed to delete stations");
}
setDetachingIds((prev) => {
@@ -499,8 +470,9 @@ const LinkedStationsContentsInner = <
item.id === selectedItemId) ||
- null
+ availableItems?.find(
+ (item) => item.id === selectedItemId
+ ) || null
}
onChange={(_, newValue) =>
setSelectedItemId(newValue?.id || null)
@@ -508,7 +480,11 @@ const LinkedStationsContentsInner = <
options={availableItems}
getOptionLabel={(item) => String(item.name)}
renderInput={(params) => (
-
+
)}
isOptionEqualToValue={(option, value) =>
option.id === value?.id
diff --git a/src/pages/Station/LinkedSights.tsx b/src/pages/Station/LinkedSights.tsx
index 07e0e4b..b4c8d5e 100644
--- a/src/pages/Station/LinkedSights.tsx
+++ b/src/pages/Station/LinkedSights.tsx
@@ -118,6 +118,10 @@ const LinkedSightsContentsInner = <
const parentResource = "station";
const childResource = "sight";
+ const buildPayload = (ids: number[]) => ({
+ [`${childResource}_ids`]: ids,
+ });
+
const availableItems = allItems
.filter((item) => !linkedItems.some((linked) => linked.id === item.id))
.filter((item) => {
@@ -160,9 +164,7 @@ const LinkedSightsContentsInner = <
const linkItem = () => {
if (selectedItemId !== null) {
setError(null);
- const requestData = {
- sight_id: selectedItemId,
- };
+ const requestData = buildPayload([selectedItemId]);
setIsLinkingSingle(true);
authInstance
@@ -194,7 +196,7 @@ const LinkedSightsContentsInner = <
});
authInstance
.delete(`/${parentResource}/${parentId}/${childResource}`, {
- data: { [`${childResource}_id`]: itemId },
+ data: buildPayload([itemId]),
})
.then(() => {
setLinkedItems(linkedItems.filter((item) => item.id !== itemId));
@@ -229,49 +231,28 @@ const LinkedSightsContentsInner = <
setIsLinkingBulk(true);
const idsToLink = Array.from(selectedItems);
- const linkedIds: number[] = [];
- const failedIds: number[] = [];
- for (const id of idsToLink) {
- try {
- await authInstance.post(
- `/${parentResource}/${parentId}/${childResource}`,
- {
- sight_id: id,
- }
- );
- linkedIds.push(id);
- } catch (error) {
- console.error("Error linking sight:", error);
- failedIds.push(id);
- }
- }
+ try {
+ await authInstance.post(
+ `/${parentResource}/${parentId}/${childResource}`,
+ buildPayload(idsToLink)
+ );
- if (linkedIds.length > 0) {
- const newItems = allItems.filter((item) => linkedIds.includes(item.id));
+ const newItems = allItems.filter((item) => idsToLink.includes(item.id));
setLinkedItems((prev) => {
const existingIds = new Set(prev.map((item) => item.id));
const additions = newItems.filter((item) => !existingIds.has(item.id));
return [...prev, ...additions];
});
+ setSelectedItems((prev) => {
+ const remaining = new Set(prev);
+ idsToLink.forEach((id) => remaining.delete(id));
+ return remaining;
+ });
onUpdate?.();
- }
-
- setSelectedItems((prev) => {
- if (linkedIds.length === 0) {
- return prev;
- }
- const remaining = new Set(prev);
- linkedIds.forEach((id) => remaining.delete(id));
- return failedIds.length > 0 ? remaining : new Set();
- });
-
- if (failedIds.length > 0) {
- setError(
- failedIds.length === idsToLink.length
- ? "Failed to link sights"
- : "Some sights failed to link"
- );
+ } catch (error) {
+ console.error("Error linking sights:", error);
+ setError("Failed to link sights");
}
setIsLinkingBulk(false);
@@ -307,42 +288,26 @@ const LinkedSightsContentsInner = <
return next;
});
- const detachedIds: number[] = [];
- const failedIds: number[] = [];
+ try {
+ await authInstance.delete(
+ `/${parentResource}/${parentId}/${childResource}`,
+ {
+ data: buildPayload(idsToDetach),
+ }
+ );
- for (const itemId of idsToDetach) {
- try {
- await authInstance.delete(
- `/${parentResource}/${parentId}/${childResource}`,
- {
- data: { [`${childResource}_id`]: itemId },
- }
- );
- detachedIds.push(itemId);
- } catch (error) {
- console.error("Error deleting sight:", error);
- failedIds.push(itemId);
- }
- }
-
- if (detachedIds.length > 0) {
setLinkedItems((prev) =>
- prev.filter((item) => !detachedIds.includes(item.id))
+ prev.filter((item) => !idsToDetach.includes(item.id))
);
setSelectedToDetach((prev) => {
const remaining = new Set(prev);
- detachedIds.forEach((id) => remaining.delete(id));
- return failedIds.length > 0 ? remaining : new Set();
+ idsToDetach.forEach((id) => remaining.delete(id));
+ return remaining;
});
onUpdate?.();
- }
-
- if (failedIds.length > 0) {
- setError(
- failedIds.length === idsToDetach.length
- ? "Failed to delete sights"
- : "Some sights failed to delete"
- );
+ } catch (error) {
+ console.error("Error deleting sights:", error);
+ setError("Failed to delete sights");
}
setDetachingIds((prev) => {