feat: update center route-preview and align buttons
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
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>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user