feat: update center route-preview and align buttons

This commit is contained in:
2025-11-27 10:58:51 +03:00
parent d6772b1e3a
commit 5481d264e0
2 changed files with 130 additions and 8 deletions

View File

@@ -5,5 +5,5 @@ export const STATION_OUTLINE_WIDTH = 4;
export const SIGHT_SIZE = 40;
export const SCALE_FACTOR = 50;
export const BACKGROUND_COLOR = 0x111111;
export const BACKGROUND_COLOR = 0x000;
export const PATH_COLOR = 0xff4d4d;

View File

@@ -2,8 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { flushSync } from "react-dom";
import type { PointerEvent as ReactPointerEvent, CSSProperties } from "react";
import { observer } from "mobx-react-lite";
import { useMapData } from "../MapDataContext";
import { AlignLeft, AlignCenter, AlignRight } from "lucide-react";
import { useTransform } from "../TransformContext";
import { coordinatesToLocal, localToCoordinates } from "../utils";
import { BACKGROUND_COLOR, SCALE_FACTOR, UP_SCALE } from "../Constants";
@@ -12,6 +12,16 @@ import { SightData } from "../types";
const SIGHT_ICON_URL = "/sight_icon.svg";
const buttons = [
{ label: <AlignLeft size={16} />, value: 1, align: "left" },
{
label: <AlignCenter size={16} />,
value: 2,
align: "center",
},
{ label: <AlignRight size={16} />, value: 3, align: "right" },
];
type Vec2 = { x: number; y: number };
type Transform = {
@@ -412,6 +422,7 @@ export const WebGLRouteMapPrototype = observer(() => {
y: centerY - worldCenterY * clampedScale,
},
};
lastTransformRef.current = adjusted;
return adjusted;
}, []);
@@ -534,9 +545,12 @@ export const WebGLRouteMapPrototype = observer(() => {
return;
}
const roundedLat = Math.round(latitude * 1e6) / 1e6;
const roundedLon = Math.round(longitude * 1e6) / 1e6;
lastCenterRef.current = {
latitude: Math.round(latitude * 1e6) / 1e6,
longitude: Math.round(longitude * 1e6) / 1e6,
latitude: roundedLat,
longitude: roundedLon,
};
},
[rotationAngle]
@@ -634,8 +648,28 @@ export const WebGLRouteMapPrototype = observer(() => {
}, [cancelScheduledCenterCommit]);
const updateTransform = useCallback(
(next: Transform, options?: { immediate?: boolean }) => {
const adjusted = clampTransformScale(next);
(
next: Transform,
options?: { immediate?: boolean; skipClamp?: boolean }
) => {
console.log("🔄 updateTransform вызван", {
inputTransform: next,
options,
stackTrace: new Error().stack?.split("\n").slice(1, 4).join("\n"),
});
const adjusted = options?.skipClamp ? next : clampTransformScale(next);
if (adjusted !== next && !options?.skipClamp) {
console.log(
"🔄 updateTransform: transform изменен после clampTransformScale",
{
before: next,
after: adjusted,
}
);
}
transformRef.current = adjusted;
if (options?.immediate) {
flushSync(() => {
@@ -1122,6 +1156,10 @@ export const WebGLRouteMapPrototype = observer(() => {
let transform = transformRef.current;
if (!transform || !Number.isFinite(transform.scale)) {
if (canvasSize.width === 0 || canvasSize.height === 0) {
return;
}
transform = computeViewTransform(
fallbackVertices,
canvas.width,
@@ -1166,9 +1204,25 @@ export const WebGLRouteMapPrototype = observer(() => {
latitude: centerLat as number,
longitude: centerLon as number,
};
const clamped = clampTransformScale(transform);
if (clamped.scale !== transform.scale) {
const clampedScale = clamped.scale;
transform = {
scale: clampedScale,
translation: {
x: canvas.width / 2 - rotatedX * clampedScale,
y: canvas.height / 2 - rotatedY * clampedScale,
},
};
updateTransform(transform, { skipClamp: true });
} else {
updateTransform(clamped, { skipClamp: true });
}
} else {
transform = clampTransformScale(transform);
updateTransform(transform);
}
} else {
const clamped = clampTransformScale(transform);
if (clamped !== transform) {
@@ -1886,6 +1940,14 @@ export const WebGLRouteMapPrototype = observer(() => {
? { right: 0, transform: "none" }
: { left: "50%", transform: "translateX(-50%)" };
const secondaryLineHeight = 1.2;
const secondaryHeight = showSecondary
? secondaryFontSize * secondaryLineHeight
: 0;
const menuPaddingTop = showSecondary
? Math.max(0, secondaryHeight - secondaryMarginTop) + 3
: 3;
return (
<div
key={station.id}
@@ -1959,6 +2021,7 @@ export const WebGLRouteMapPrototype = observer(() => {
marginTop: -1 * secondaryMarginTop,
fontWeight: 400,
fontSize: secondaryFontSize,
lineHeight: secondaryLineHeight,
color: "#CBCBCB",
textShadow: "0 0 3px rgba(0,0,0,0.4)",
whiteSpace: "nowrap",
@@ -1972,6 +2035,65 @@ export const WebGLRouteMapPrototype = observer(() => {
</div>
</div>
</div>
{hoveredStationId === station.id && (
<div
style={{
position: "absolute",
top: "100%",
left: "50%",
transform: "translateX(-50%)",
paddingTop: menuPaddingTop,
pointerEvents: "auto",
zIndex: 10,
cursor: "default",
}}
onPointerDown={(e) => e.stopPropagation()}
>
<div
style={{
display: "flex",
gap: 4,
padding: 4,
borderRadius: 4,
backgroundColor: "white",
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
}}
>
{buttons.map((btn) => (
<div
key={btn.value}
onClick={(e) => {
e.stopPropagation();
setStationAlignments((prev) => {
const next = new Map(prev);
next.set(
station.id,
btn.align as StationAlignment
);
return next;
});
setStationAlign(station.id, btn.value);
}}
style={{
padding: "4px 8px",
fontSize: 12,
cursor: "pointer",
backgroundColor:
alignment === btn.align
? "#e0e0e0"
: "transparent",
borderRadius: 4,
color: "black",
fontWeight: 500,
userSelect: "none",
}}
>
{btn.label}
</div>
))}
</div>
</div>
)}
</div>
);
})}