feat: update map anchor for station and desciprtion search

This commit is contained in:
2025-11-27 20:17:23 +03:00
parent 5481d264e0
commit c5c5f835bc
2 changed files with 45 additions and 14 deletions

View File

@@ -364,8 +364,20 @@ const computeViewTransform = (
return { scale, translation }; return { scale, translation };
}; };
const getAnchorFromOffset = (
offsetX: number,
offsetY: number
): { x: number; y: number } => {
const length = Math.hypot(offsetX, offsetY);
const nx = offsetX / length;
const ny = offsetY / length;
return { x: (1 - nx) / 2, y: (1 - ny) / 2 };
};
const backgroundColor = toColor(BACKGROUND_COLOR); const backgroundColor = toColor(BACKGROUND_COLOR);
// Override route & station color to ED1C24
const pathColor = toColor(0xed1c24); const pathColor = toColor(0xed1c24);
export const WebGLRouteMapPrototype = observer(() => { export const WebGLRouteMapPrototype = observer(() => {
@@ -395,6 +407,7 @@ export const WebGLRouteMapPrototype = observer(() => {
const transformRef = useRef<Transform | null>(null); const transformRef = useRef<Transform | null>(null);
const lastTransformRef = useRef<Transform | null>(null); const lastTransformRef = useRef<Transform | null>(null);
const [transformState, setTransformState] = useState<Transform | null>(null); const [transformState, setTransformState] = useState<Transform | null>(null);
const clampTransformScale = useCallback((transform: Transform): Transform => { const clampTransformScale = useCallback((transform: Transform): Transform => {
const { min, max } = scaleLimitsRef.current; const { min, max } = scaleLimitsRef.current;
const clampedScale = clamp(transform.scale, min, max); const clampedScale = clamp(transform.scale, min, max);
@@ -1904,6 +1917,11 @@ export const WebGLRouteMapPrototype = observer(() => {
const labelY = const labelY =
(rotatedY + offsetY) * camera.scale + camera.translation.y; (rotatedY + offsetY) * camera.scale + camera.translation.y;
const anchor = getAnchorFromOffset(offsetX, offsetY);
const transformCss = `translate(${-anchor.x * 100}%, ${
-anchor.y * 100
}%)`;
const dpr = Math.max(1, window.devicePixelRatio || 1); const dpr = Math.max(1, window.devicePixelRatio || 1);
const cssX = labelX / dpr; const cssX = labelX / dpr;
const cssY = labelY / dpr; const cssY = labelY / dpr;
@@ -1919,8 +1937,10 @@ export const WebGLRouteMapPrototype = observer(() => {
const fontSizePercent = const fontSizePercent =
routeData?.font_size ?? originalRouteData?.font_size ?? 100; routeData?.font_size ?? originalRouteData?.font_size ?? 100;
const fontScale = fontSizePercent / 100; const fontScale = fontSizePercent / 100;
const primaryFontSize = 16 * fontScale; const primaryFontSize = 16 * fontScale;
const secondaryFontSize = 13 * fontScale; const secondaryFontSize = 13 * fontScale;
const secondaryMarginTop = 5 * fontScale; const secondaryMarginTop = 5 * fontScale;
const backendAlign = station.align; const backendAlign = station.align;
@@ -1972,7 +1992,7 @@ export const WebGLRouteMapPrototype = observer(() => {
position: "absolute", position: "absolute",
left: cssX, left: cssX,
top: cssY, top: cssY,
transform: "translate(0, -50%)", transform: transformCss,
color: "#fff", color: "#fff",
fontFamily: "Roboto, sans-serif", fontFamily: "Roboto, sans-serif",
textAlign: "left", textAlign: "left",

View File

@@ -135,7 +135,10 @@ const LinkedStationsContentsInner = <
const filteredAvailableItems = availableItems.filter((item) => { const filteredAvailableItems = availableItems.filter((item) => {
if (!searchQuery.trim()) return true; if (!searchQuery.trim()) return true;
return String(item.name).toLowerCase().includes(searchQuery.toLowerCase()); const query = searchQuery.toLowerCase();
const name = String(item.name || "").toLowerCase();
const description = String(item.description || "").toLowerCase();
return name.includes(query) || description.includes(query);
}); });
useEffect(() => { useEffect(() => {
@@ -483,6 +486,7 @@ const LinkedStationsContentsInner = <
<TextField <TextField
{...params} {...params}
label="Выберите остановку" label="Выберите остановку"
placeholder="Введите название или описание остановки..."
fullWidth fullWidth
/> />
)} )}
@@ -490,16 +494,15 @@ const LinkedStationsContentsInner = <
option.id === value?.id option.id === value?.id
} }
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
const searchWords = inputValue if (!inputValue.trim()) return options;
.toLowerCase() const query = inputValue.toLowerCase();
.split(" ")
.filter(Boolean);
return options.filter((option) => { return options.filter((option) => {
const optionWords = String(option.name) const name = String(option.name || "").toLowerCase();
.toLowerCase() const description = String(
.split(" "); option.description || ""
return searchWords.every((searchWord) => ).toLowerCase();
optionWords.some((word) => word.startsWith(searchWord)) return (
name.includes(query) || description.includes(query)
); );
}); });
}} }}
@@ -534,7 +537,7 @@ const LinkedStationsContentsInner = <
label="Поиск остановок" label="Поиск остановок"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Введите название остановки..." placeholder="Введите название или описание остановки..."
size="small" size="small"
/> />
@@ -550,11 +553,19 @@ const LinkedStationsContentsInner = <
size="small" size="small"
/> />
} }
label={String(item.name)} label={
<div className="flex justify-between items-center w-full gap-10">
<p>{String(item.name)}</p>
<p className="text-xs text-gray-500 max-w-[300px] truncate text-ellipsis">
{String(item.description)}
</p>
</div>
}
sx={{ sx={{
margin: 0, margin: 0,
"& .MuiFormControlLabel-label": { "& .MuiFormControlLabel-label": {
fontSize: "0.9rem", fontSize: "0.9rem",
width: "100%",
}, },
}} }}
/> />