import React, { useEffect, useMemo, useState } from 'react'; import { api } from '../api.js'; import { autoServer, filterServers, groupServers, SERVER_RESULT_WINDOW, } from '../utils/serverPicker.js'; const FAVORITES_KEY = 'harbor-server-favorites'; const RECENT_KEY = 'harbor-server-recent'; const AUTO_KEY = 'harbor-server-auto'; const SIMPLE_SERVER_LIMIT = 5; function readList(key) { try { const value = JSON.parse(localStorage.getItem(key) || '[]'); return Array.isArray(value) ? value.map(String) : []; } catch { return []; } } function write(key, value) { try { localStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value)); } catch { // Preferences remain available for this session. } } function readAuto() { try { return localStorage.getItem(AUTO_KEY) === 'true'; } catch { return false; } } function serverHealthText(ping) { if (ping?.error) return 'Проверка недоступна'; if (ping?.ok) return `${ping.latency} мс`; return ping ? 'Недоступен' : null; } function ServerHealth({ ping, fallback }) { const health = fallback || serverHealthText(ping); if (!health && !ping?.checking) return null; return ; } function ServerCheckButton({ checking, disabled, onClick }) { return ; } function ServerRow({ server, selected, favorite, ping, disabled, index, onSelect, onFavorite }) { const health = ping?.checking ? 'Проверяем пинг' : serverHealthText(ping); return
{onFavorite && }
; } export function ServerPicker({ servers, selectedServerId, disabled, prompt, leaving, revealVersion, onSelect, }) { const [query, setQuery] = useState(''); const [advanced, setAdvanced] = useState(false); const [view, setView] = useState('all'); const [page, setPage] = useState(0); const [favorites, setFavorites] = useState(() => readList(FAVORITES_KEY)); const [recent, setRecent] = useState(() => readList(RECENT_KEY)); const [autoActive, setAutoActive] = useState(readAuto); const [collapsed, setCollapsed] = useState([]); const [pings, setPings] = useState({}); const [checking, setChecking] = useState(false); const serverKey = servers.map(({ id }) => id).join('|'); useEffect(() => { setPage(0); }, [query, view, serverKey]); const selected = servers.find(({ id }) => id === selectedServerId); const filtered = useMemo(() => { const found = filterServers(servers, query); if (view === 'favorites') return found.filter(({ id }) => favorites.includes(id)); if (view === 'recent') return recent.flatMap((id) => found.find((server) => server.id === id) || []); return found; }, [servers, query, view, favorites, recent]); const results = filtered.filter(({ id }) => id !== selectedServerId); const pageCount = Math.max(1, Math.ceil(results.length / SERVER_RESULT_WINDOW)); const visible = results.slice(page * SERVER_RESULT_WINDOW, (page + 1) * SERVER_RESULT_WINDOW); const grouped = servers.length >= 10; useEffect(() => { setPage((current) => Math.min(current, pageCount - 1)); }, [pageCount]); function toggleFavorite(id) { setFavorites((current) => { const next = current.includes(id) ? current.filter((item) => item !== id) : [id, ...current]; write(FAVORITES_KEY, next); return next; }); } function select(id, automatic = false) { setAutoActive(automatic); write(AUTO_KEY, String(automatic)); if (!automatic) { setRecent((current) => { const next = [id, ...current.filter((item) => item !== id)].slice(0, 5); write(RECENT_KEY, next); return next; }); } onSelect(id); } async function checkVisible() { const ids = [...new Set([selectedServerId, ...visible.map(({ id }) => id)].filter(Boolean))].slice(0, 30); if (!ids.length) return; const startedAt = performance.now(); setChecking(true); setPings((current) => ({ ...current, ...Object.fromEntries(ids.map((id) => [id, { ...current[id], checking: true }])), })); try { const data = await api.servers.ping(ids); setPings((current) => ({ ...current, ...Object.fromEntries((data.results || []).map((result) => [result.id, { ...result, checking: true }])), })); } catch { setPings((current) => ({ ...current, ...Object.fromEntries(ids.map((id) => [id, { error: true, checking: true, checkedAt: new Date().toISOString() }])), })); } finally { await new Promise((resolve) => setTimeout(resolve, Math.max(0, 700 - (performance.now() - startedAt)))); setPings((current) => ({ ...current, ...Object.fromEntries(ids.map((id) => [id, { ...current[id], checking: false }])), })); setChecking(false); } } if (servers.length === 1) { return
{prompt && Выберите сервер}
; } const renderRows = (items, offset = 0) => items.map((server, index) => ( )); const simpleServers = [ ...(selected ? [selected] : []), ...servers.filter(({ id }) => id !== selectedServerId), ].slice(0, SIMPLE_SERVER_LIMIT); return
{prompt && Выберите сервер}
{simpleServers.map((server, index) => )}
{servers.length > simpleServers.length &&

Ещё {servers.length - simpleServers.length} — доступны через поиск

}
setQuery(event.target.value)} />
{[ ['all', 'Все'], ['favorites', '★'], ['recent', 'Недавние'], ].map(([id, label]) => )}
{selected && }
{!visible.length &&

Серверы не найдены

} {grouped ? groupServers(visible).map(([group, items]) => { const isCollapsed = collapsed.includes(group); return
{!isCollapsed &&
{renderRows(items)}
}
; }) :
{renderRows(visible)}
} {pageCount > 1 && }
; }