import React, { useEffect } from 'react' import { appQueryParams } from '../appParams' import { fetchServerStatus, isServerValid } from '../api/mcStatusApi' import { parseServerAddress } from '../parseServerAddress' import Screen from './Screen' import Input, { INPUT_LABEL_WIDTH, InputWithLabel } from './Input' import Button from './Button' import SelectGameVersion from './SelectGameVersion' import { usePassesScaledDimensions } from './UIProvider' export interface BaseServerInfo { ip: string name?: string versionOverride?: string proxyOverride?: string usernameOverride?: string /** Username or always use new if true */ authenticatedAccountOverride?: string | true } interface Props { onBack: () => void onConfirm: (info: BaseServerInfo) => void title?: string initialData?: BaseServerInfo parseQs?: boolean onQsConnect?: (server: BaseServerInfo) => void placeholders?: Pick accounts?: string[] authenticatedAccounts?: number versions?: string[] } export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, placeholders, accounts, versions }: Props) => { const isSmallHeight = !usePassesScaledDimensions(null, 350) const qsParamName = parseQs ? appQueryParams.name : undefined const qsParamIp = parseQs ? appQueryParams.ip : undefined const qsParamVersion = parseQs ? appQueryParams.version : undefined const qsParamProxy = parseQs ? appQueryParams.proxy : undefined const qsParamUsername = parseQs ? appQueryParams.username : undefined const qsParamLockConnect = parseQs ? appQueryParams.lockConnect : undefined const parsedQsIp = parseServerAddress(qsParamIp) const parsedInitialIp = parseServerAddress(initialData?.ip) const [serverName, setServerName] = React.useState(initialData?.name ?? qsParamName ?? '') const [serverIp, setServerIp] = React.useState(parsedQsIp.serverIpFull || parsedInitialIp.serverIpFull || '') const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? /* legacy */ initialData?.['version'] ?? qsParamVersion ?? '') const [proxyOverride, setProxyOverride] = React.useState(initialData?.proxyOverride ?? qsParamProxy ?? '') const [usernameOverride, setUsernameOverride] = React.useState(initialData?.usernameOverride ?? qsParamUsername ?? '') const lockConnect = qsParamLockConnect === 'true' const smallWidth = !usePassesScaledDimensions(400) const initialAccount = initialData?.authenticatedAccountOverride const [accountIndex, setAccountIndex] = React.useState(initialAccount === true ? -2 : initialAccount ? (accounts?.includes(initialAccount) ? accounts.indexOf(initialAccount) : -2) : -1) const freshAccount = accountIndex === -2 const noAccountSelected = accountIndex === -1 const authenticatedAccountOverride = noAccountSelected ? undefined : freshAccount ? true : accounts?.[accountIndex] let ipFinal = serverIp ipFinal = ipFinal.replace(/:$/, '') const commonUseOptions: BaseServerInfo = { name: serverName, ip: ipFinal, versionOverride: versionOverride || undefined, proxyOverride: proxyOverride || undefined, usernameOverride: usernameOverride || undefined, authenticatedAccountOverride, } const [fetchedServerInfoIp, setFetchedServerInfoIp] = React.useState(undefined) const [serverOnline, setServerOnline] = React.useState(null as boolean | null) const [onlinePlayersList, setOnlinePlayersList] = React.useState([]) useEffect(() => { const controller = new AbortController() const checkServer = async () => { if (!qsParamIp || !isServerValid(qsParamIp)) return try { const status = await fetchServerStatus(qsParamIp) if (!status) return setServerOnline(status.raw.online) setOnlinePlayersList(status.raw.players?.list.map(p => p.name_raw) ?? []) setFetchedServerInfoIp(qsParamIp) } catch (err) { console.error('Failed to fetch server status:', err) } } void checkServer() return () => controller.abort() }, [qsParamIp]) const validateUsername = (username: string) => { if (!username) return undefined if (onlinePlayersList.includes(username)) { return { border: 'red solid 1px' } } const MINECRAFT_USERNAME_REGEX = /^\w{3,16}$/ if (!MINECRAFT_USERNAME_REGEX.test(username)) { return { border: 'red solid 1px' } } return undefined } const validateServerIp = () => { if (!serverIp) return undefined if (serverOnline) { return { border: 'lightgreen solid 1px' } } else { return { border: 'red solid 1px' } } } const displayConnectButton = qsParamIp const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg'] // pick random example const example = serverExamples[Math.floor(Math.random() * serverExamples.length)] return
{ e.preventDefault() onConfirm(commonUseOptions) }} >
{ setServerIp(value) setServerOnline(false) }} validateInput={serverOnline === null || fetchedServerInfoIp !== serverIp ? undefined : validateServerIp} placeholder={example} /> {!lockConnect && <>
setServerName(value)} placeholder='Defaults to IP' />
} {isSmallHeight ?
:
Overrides:
}
{ return { value: v, label: v } }) ?? []} onChange={(value) => { setVersionOverride(value) }} placeholder="Optional, but recommended to specify" disabled={lockConnect} />
setProxyOverride(value)} placeholder={serverIp.startsWith('ws://') || serverIp.startsWith('wss://') ? 'Not needed for websocket servers' : placeholders?.proxyOverride} /> setUsernameOverride(value)} placeholder={placeholders?.usernameOverride} validateInput={!serverOnline || fetchedServerInfoIp !== serverIp ? undefined : validateUsername} /> {!lockConnect && <> { onBack() }}> Cancel {displayConnectButton ? translate('Save') : {translate('Save')}} } {displayConnectButton && (
{ onQsConnect?.(commonUseOptions) }} > {translate('Connect')}
)}
} const ButtonWrapper = ({ ...props }: React.ComponentProps) => { props.style ??= {} props.style.width = INPUT_LABEL_WIDTH return