import { ConnectHistoryEntry, ConnectHistoryServerInfo, ConnectProperties, ConnectUiEvents, PropertyValidState } from "tc-shared/ui/modal/connect/Definitions"; import * as React from "react"; import {useContext, useState} from "react"; import {Registry} from "tc-shared/events"; import {InternalModal} from "tc-shared/ui/react-elements/internal-modal/Controller"; import {Translatable} from "tc-shared/ui/react-elements/i18n"; import {ControlledFlatInputField, ControlledSelect, FlatInputField} from "tc-shared/ui/react-elements/InputField"; import {joinClassList, useTr} from "tc-shared/ui/react-elements/Helper"; import {Button} from "tc-shared/ui/react-elements/Button"; import {kUnknownHistoryServerUniqueId} from "tc-shared/connectionlog/History"; import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons"; import {ClientIcon} from "svg-sprites/client-icons"; import * as i18n from "../../../i18n/country"; import {getIconManager} from "tc-shared/file/Icons"; import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon"; const EventContext = React.createContext>(undefined); const ConnectDefaultNewTabContext = React.createContext(false); const cssStyle = require("./Renderer.scss"); function useProperty(key: T, defaultValue: V) : [ConnectProperties[T] | V, (value: ConnectProperties[T]) => void] { const events = useContext(EventContext); const [ value, setValue ] = useState(() => { events.fire("query_property", { property: key }); return defaultValue; }); events.reactUse("notify_property", event => event.property === key && setValue(event.value as any)); return [value, setValue]; } function usePropertyValid(key: T, defaultValue: PropertyValidState[T]) : PropertyValidState[T] { const events = useContext(EventContext); const [ value, setValue ] = useState(() => { events.fire("query_property_valid", { property: key }); return defaultValue; }); events.reactUse("notify_property_valid", event => event.property === key && setValue(event.value as any)); return value; } const InputServerAddress = () => { const events = useContext(EventContext); const [address, setAddress] = useProperty("address", undefined); const valid = usePropertyValid("address", true); const newTab = useContext(ConnectDefaultNewTabContext); return ( Server address} labelType={"static"} invalid={valid ? undefined : Please enter a valid server address} onInput={value => { setAddress({ currentAddress: value, defaultAddress: address.defaultAddress }); events.fire("action_set_address", { address: value, validate: true, updateUi: false }); }} onBlur={() => { events.fire("action_set_address", { address: address?.currentAddress, validate: true, updateUi: true }); }} onEnter={() => { /* Setting the address just to ensure */ events.fire("action_set_address", { address: address?.currentAddress, validate: true, updateUi: true }); events.fire("action_connect", { newTab }); }} /> ) } const InputServerPassword = () => { const events = useContext(EventContext); const [password, setPassword] = useProperty("password", undefined); return ( Server password} labelType={password?.hashed ? "static" : "floating"} onInput={value => { setPassword({ password: value, hashed: false }); events.fire("action_set_password", { password: value, hashed: false, updateUi: false }); }} onBlur={() => { if(password) { events.fire("action_set_password", { password: password.password, hashed: password.hashed, updateUi: true }); } }} /> ) } const InputNickname = () => { const events = useContext(EventContext); const [nickname, setNickname] = useProperty("nickname", undefined); const valid = usePropertyValid("nickname", true); return ( Nickname} labelType={"static"} invalid={valid ? undefined : Nickname too short or too long} onInput={value => { setNickname({ currentNickname: value, defaultNickname: nickname.defaultNickname }); events.fire("action_set_nickname", { nickname: value, validate: true, updateUi: false }); }} onBlur={() => events.fire("action_set_nickname", { nickname: nickname?.currentNickname, validate: true, updateUi: true })} /> ); } const InputProfile = () => { const events = useContext(EventContext); const [profiles] = useProperty("profiles", undefined); const selectedProfile = profiles?.profiles.find(profile => profile.id === profiles?.selected); let invalidMarker; if(profiles) { if(!profiles.selected) { /* We have to select a profile. */ /* TODO: Only show if we've tried to press connect */ //invalidMarker = Please select a profile; } else if(!selectedProfile) { invalidMarker = Unknown select profile; } else if(!selectedProfile.valid) { invalidMarker = Selected profile has an invalid config } } return (
Connect profile} invalid={invalidMarker} invalidClassName={cssStyle.invalidFeedback} onChange={event => events.fire("action_select_profile", { id: event.target.value })} > {profiles?.profiles.map(profile => ( ))}
); } const ConnectContainer = () => (
); const ButtonToggleHistory = () => { const [state] = useProperty("historyShown", false); const events = useContext(EventContext); let body; if(state) { body = (
Hide connect history
); } else { body = (
Show connect history
); } return ( ); } const ButtonsConnect = () => { const connectNewTab = useContext(ConnectDefaultNewTabContext); const events = useContext(EventContext); let left; if(connectNewTab) { left = ( ); } else { left = ( ); } return (
{left}
); }; const ButtonContainer = () => (
); const CountryIcon = (props: { country: string }) => { return (
{i18n.country_name(props.country, useTr("Global"))}
) } const HistoryTableEntryConnectCount = React.memo((props: { entry: ConnectHistoryEntry }) => { const targetType = props.entry.uniqueServerId === kUnknownHistoryServerUniqueId ? "address" : "server-unique-id"; const target = targetType === "address" ? props.entry.targetAddress : props.entry.uniqueServerId; const events = useContext(EventContext); const [ amount, setAmount ] = useState(() => { events.fire("query_history_connections", { target, targetType }); return -1; }); events.reactUse("notify_history_connections", event => event.targetType === targetType && event.target === target && setAmount(event.value)); if(amount >= 0) { return {amount}; } else { return null; } }); const HistoryTableEntry = React.memo((props: { entry: ConnectHistoryEntry, selected: boolean }) => { const connectNewTab = useContext(ConnectDefaultNewTabContext); const events = useContext(EventContext); const [ info, setInfo ] = useState(() => { if(props.entry.uniqueServerId !== kUnknownHistoryServerUniqueId) { events.fire("query_history_entry", { serverUniqueId: props.entry.uniqueServerId }); } return undefined; }); events.reactUse("notify_history_entry", event => event.serverUniqueId === props.entry.uniqueServerId && setInfo(event.info)); const icon = getIconManager().resolveIcon(info ? info.icon.iconId : 0, info?.icon.serverUniqueId, info?.icon.handlerId); return (
{ if(event.isDefaultPrevented()) { return; } events.fire("action_select_history", { id: props.entry.id }); }} onDoubleClick={() => events.fire("action_connect", { newTab: connectNewTab })} >
{ event.preventDefault(); if(props.entry.uniqueServerId === kUnknownHistoryServerUniqueId) { events.fire("action_delete_history", { targetType: "address", target: props.entry.targetAddress }); } else { events.fire("action_delete_history", { targetType: "server-unique-id", target: props.entry.uniqueServerId }); } }}>
{info?.name}
{props.entry.targetAddress}
{info ? info.password ? tr("Yes") : tr("No") : ""}
{info ? : null}
{info && info.maxClients !== -1 ? `${info.clients}/${info.maxClients}` : ""}
); }); const HistoryTable = () => { const [history] = useProperty("history", undefined); let body; if(history) { if(history.history.length > 0) { body = history.history.map(entry => ); } else { body = ( ); } } else { return null; } return (
Name
Address
Password
Country
Clients
Connections
{body}
) } const HistoryContainer = () => { const [historyShown] = useProperty("historyShown", false); return (
) } export class ConnectModal extends InternalModal { private readonly events: Registry; private readonly connectNewTabByDefault: boolean; constructor(events: Registry, connectNewTabByDefault: boolean) { super(); this.events = events; this.connectNewTabByDefault = connectNewTabByDefault; } renderBody(): React.ReactElement { return (
); } title(): string | React.ReactElement { return Connect to a server; } color(): "none" | "blue" { return "blue"; } verticalAlignment(): "top" | "center" | "bottom" { return "top"; } }