import { ConnectHistoryEntry, ConnectUiEvents, ConnectUiVariables, } from "tc-shared/ui/modal/connect/Definitions"; import * as React from "react"; import {useContext} 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"; import {UiVariableConsumer} from "tc-shared/ui/utils/Variable"; import {createIpcUiVariableConsumer, IpcVariableDescriptor} from "tc-shared/ui/utils/IpcVariable"; import {AbstractModal} from "tc-shared/ui/react-elements/ModalDefinitions"; const EventContext = React.createContext>(undefined); const VariablesContext = React.createContext>(undefined); const ConnectDefaultNewTabContext = React.createContext(false); const cssStyle = require("./Renderer.scss"); const InputServerAddress = React.memo(() => { const events = useContext(EventContext); const newTab = useContext(ConnectDefaultNewTabContext); const variables = useContext(VariablesContext); const address = variables.useVariable("server_address"); const addressValid = variables.useReadOnly("server_address_valid", undefined, true) || address.localValue !== address.remoteValue; return ( Server address} labelType={"static"} invalid={addressValid ? undefined : Please enter a valid server address} editable={address.status === "loaded"} onInput={value => address.setValue({ currentAddress: value }, true)} onBlur={() => address.setValue({ currentAddress: address.localValue?.currentAddress })} onEnter={() => { /* Setting the address just to ensure */ address.setValue({ currentAddress: address.localValue?.currentAddress }); events.fire("action_connect", { newTab }); }} /> ) }); const InputServerPassword = () => { const variables = useContext(VariablesContext); const password = variables.useVariable("password"); return ( Server password} labelType={password.localValue?.hashed ? "static" : "floating"} onInput={value => password.setValue({ password: value, hashed: false }, true)} onBlur={() => password.setValue(password.localValue)} /> ) } const InputNickname = () => { const variables = useContext(VariablesContext); const nickname = variables.useVariable("nickname"); const validState = variables.useReadOnly("nickname_valid", undefined, true) || nickname.localValue !== nickname.remoteValue; return ( Nickname} labelType={"static"} invalid={validState ? undefined : Nickname too short or too long} onInput={value => nickname.setValue({ currentNickname: value }, true)} onBlur={() => nickname.setValue({ currentNickname: nickname.localValue?.currentNickname })} /> ); } const InputProfile = () => { const events = useContext(EventContext); const variables = useContext(VariablesContext); const profiles = variables.useVariable("profiles"); const selectedProfile = profiles.remoteValue?.profiles.find(profile => profile.id === profiles.remoteValue?.selected); let invalidMarker; if(profiles) { if(!profiles.remoteValue?.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 => profiles.setValue({ selected: event.target.value })} > { profiles.remoteValue?.profiles.map(profile => ( )) }
); } const ConnectContainer = () => (
{/* */}
); const ButtonToggleHistory = () => { const variables = useContext(VariablesContext); const historyShown = variables.useVariable("historyShown"); let body; if(historyShown.localValue) { 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 value = useContext(VariablesContext).useReadOnly("history_connections", { target, targetType }, -1); if(value >= 0) { return {value}; } else { return null; } }); const HistoryTableEntry = React.memo((props: { entry: ConnectHistoryEntry, selected: boolean }) => { const events = useContext(EventContext); const connectNewTab = useContext(ConnectDefaultNewTabContext); const variables = useContext(VariablesContext); const info = variables.useReadOnly("history_entry", { serverUniqueId: props.entry.uniqueServerId }, undefined); 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 = useContext(VariablesContext).useReadOnly("history", undefined, undefined); let body; if(history) { if(history.history.length > 0) { body = history.history.map(entry => ); } else { body = ( ); } } else { return null; } return (
) } const HistoryContainer = () => { const variables = useContext(VariablesContext); const historyShown = variables.useReadOnly("historyShown", undefined, false); return (
) } export class ConnectModal extends AbstractModal { private readonly events: Registry; private readonly variables: UiVariableConsumer; private readonly connectNewTabByDefault: boolean; constructor(events: Registry, variables: IpcVariableDescriptor, connectNewTabByDefault: boolean) { super(); this.variables = createIpcUiVariableConsumer(variables); this.events = events; this.connectNewTabByDefault = connectNewTabByDefault; } protected onDestroy() { super.onDestroy(); this.variables.destroy(); } renderBody(): React.ReactElement { return (
); } renderTitle(): string | React.ReactElement { return Connect to a server; } color(): "none" | "blue" { return "blue"; } verticalAlignment(): "top" | "center" | "bottom" { return "top"; } }