From 1fda3a5f4ed772a0587c4041cf0e56151cdfcebf Mon Sep 17 00:00:00 2001 From: shats Date: Wed, 18 Feb 2026 14:05:20 +0700 Subject: [PATCH] feat(NetworkMapUnifi): enhance label placement logic and update country flag rendering with CDN support --- frontend/src/NetworkMapUnifi.jsx | 81 ++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/frontend/src/NetworkMapUnifi.jsx b/frontend/src/NetworkMapUnifi.jsx index 9c3c74a..235871f 100644 --- a/frontend/src/NetworkMapUnifi.jsx +++ b/frontend/src/NetworkMapUnifi.jsx @@ -1,6 +1,7 @@ import { useMemo, useCallback, useState, useRef, useEffect } from 'react'; import { IconRefresh, IconMaximize, IconMinimize } from '@tabler/icons-react'; -import { countryToFlag } from './utils/serverUtils.js'; + +const FLAG_CDN = 'https://flagcdn.com'; const UNIFI_BG = '#0f172a'; const UNIFI_CARD_BG = '#1e293b'; @@ -270,8 +271,11 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa const perpY = (ddx / len) * baseOffset; const cpx = mx + perpX; const cpy = my + perpY; + const perpLen = Math.hypot(perpX, perpY) || 1; + const ux = perpX / perpLen; + const uy = perpY / perpLen; - return { sx, sy, ex, ey, cpx, cpy }; + return { sx, sy, ex, ey, cpx, cpy, mx, my, perpX: ux, perpY: uy }; }, []); const pathD = useCallback((e) => { @@ -279,12 +283,14 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa return `M ${sx} ${sy} Q ${cpx} ${cpy} ${ex} ${ey}`; }, [computeCurvePoints]); - const labelPoint = useCallback((e) => { - const { sx, sy, ex, ey, cpx, cpy } = computeCurvePoints(e); - const t = 0.5; - const lx = (1 - t) * (1 - t) * sx + 2 * (1 - t) * t * cpx + t * t * ex; - const ly = (1 - t) * (1 - t) * sy + 2 * (1 - t) * t * cpy + t * t * ey; - return { x: lx, y: ly }; + /** Точка для подписи: середина ребра + смещение по перпендикуляру (чтобы подпись была у ребра, а не уезжала) */ + const labelPlacement = useCallback((e, level = 0) => { + const { mx, my, perpX, perpY } = computeCurvePoints(e); + const baseOffset = 18; + const stepOffset = 26; + const x = mx + perpX * (baseOffset + level * stepOffset); + const y = my + perpY * (baseOffset + level * stepOffset); + return { x, y }; }, [computeCurvePoints]); if (servers.length === 0) return null; @@ -342,24 +348,22 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa opacity={dimFactor} /> {(() => { - const base = labelPoint(e); - let x = base.x; - let y = base.y; const labels = [e.pingLabel, e.speedLabel].filter(Boolean); if (labels.length === 0) return null; - let level = 0; const totalH = labels.length * LABEL_H + (labels.length - 1) * LABEL_GAP; - while ( - level < 10 && - used.some( - (p) => - Math.abs(p.x - x) < LABEL_W && - Math.abs(p.y - (y + level * (totalH + 8))) < totalH + 16 - ) - ) { + const minDist = 70; + let level = 0; + let x; let y; + while (level < 10) { + const pos = labelPlacement(e, level); + x = pos.x; + y = pos.y; + const overlaps = used.some( + (p) => Math.hypot(p.x - x, p.y - y) < minDist + ); + if (!overlaps) break; level += 1; } - y = y + level * (totalH + 8); used.push({ x, y }); const pad = 8; @@ -418,20 +422,27 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa strokeWidth={1} /> - {/* Флаг страны в левом верхнем углу карточки */} - {s.country && ( - - {countryToFlag(s.country)} - - )} + {/* Флаг страны в левом верхнем углу карточки (картинка, т.к. эмодзи в SVG часто отображаются как буквы) */} + {s.country && (() => { + const cc = String(s.country).trim().toLowerCase(); + if (cc.length !== 2) return null; + const flagW = 22; + const flagH = 16; + return ( + + {s.country.toUpperCase()} + + ); + })()} {/* Иконка сервера в стиле UNIFI: прямоугольник с «портами» */}