From 6f32c6e671be924beb52eea723dc167bc9a1c28f Mon Sep 17 00:00:00 2001 From: itoshi Date: Wed, 12 Nov 2025 03:14:03 +0300 Subject: [PATCH] feat: Update `left sidebar` and `fetch queries` --- src/pages/Route/route-preview/LeftSidebar.tsx | 151 ++++++++---------- .../Route/route-preview/RightSidebar.tsx | 15 ++ .../WebGLRouteMapPrototype.tsx | 53 +++--- src/pages/Sight/LinkedStations.tsx | 106 +++++------- src/pages/Station/LinkedSights.tsx | 101 ++++-------- 5 files changed, 192 insertions(+), 234 deletions(-) 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 && ( + + )} + + При поддержке Правительства + +
+
+ +
+ + + +
+ { )} - - #ВсемПоПути - - - - {!open && ( #ВсемПоПути - )} +
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) => {