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';
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 ServerRow({ server, selected, favorite, ping, disabled, index, onSelect, onFavorite }) {
const health = ping?.checking
? 'Проверяем TCP…'
: ping?.error ? 'Проверка недоступна'
: ping?.ok ? `TCP ${ping.latency} мс` : ping ? 'TCP недоступен' : 'Не проверен';
return
{onFavorite && }
;
}
export function ServerPicker({
servers,
selectedServerId,
disabled,
prompt,
leaving,
revealVersion,
onSelect,
}) {
const [query, setQuery] = useState('');
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;
setChecking(true);
setPings((current) => ({
...current,
...Object.fromEntries(ids.map((id) => [id, { checking: true }])),
}));
try {
const data = await api.servers.ping(ids);
setPings((current) => ({
...current,
...Object.fromEntries((data.results || []).map((result) => [result.id, result])),
}));
} catch {
setPings((current) => ({
...current,
...Object.fromEntries(ids.map((id) => [id, { error: true, checkedAt: new Date().toISOString() }])),
}));
} finally {
setChecking(false);
}
}
if (servers.length === 1) {
return
{prompt && Выберите сервер}
;
}
const renderRows = (items, offset = 0) => items.map((server, index) => (
));
return
{prompt && Выберите сервер}
{selected && !autoActive && }
{!visible.length &&
Серверы не найдены
}
{grouped ? groupServers(visible).map(([group, items]) => {
const isCollapsed = collapsed.includes(group);
return
{!isCollapsed && {renderRows(items)}
}
;
}) :
{renderRows(visible)}
}
{pageCount > 1 &&
}
;
}