Add FieldConfig dropdown type + improve proxy field

issue/311
Kelvin Schoofs 3 years ago
parent bd25e7322d
commit c749fc99ca

@ -1,17 +1,16 @@
import * as React from 'react';
import { FieldConfig } from '../FieldTypes/config';
import { FieldDropdown } from '../FieldTypes/dropdown';
import { FieldNumber } from '../FieldTypes/number';
import { FieldString } from '../FieldTypes/string';
import { connect } from '../redux';
import { FileSystemConfig } from '../types/fileSystemConfig';
import type { FieldFactory, FSCChanged, FSCChangedMultiple } from './fields';
function proxy(config: FileSystemConfig, onChange: FSCChanged<'proxy'>): React.ReactElement {
function hostAndPort(config: FileSystemConfig, onChange: FSCChanged<'proxy'>): React.ReactElement {
const onChangeHost = (host: string) => onChange('proxy', { ...config.proxy!, host });
const onChangePort = (port: number) => onChange('proxy', { ...config.proxy!, port });
console.log('Current config:', config);
return <React.Fragment>
<p>{new Date().toString()}</p>
<FieldString label="Proxy Host" value={config.proxy!.host} onChange={onChangeHost}
description="Hostname or IP address of the proxy." />
<FieldNumber label="Proxy Port" value={config.proxy!.port} onChange={onChangePort}
@ -19,77 +18,56 @@ function proxy(config: FileSystemConfig, onChange: FSCChanged<'proxy'>): React.R
</React.Fragment>;
}
interface HopFieldProps {
config: FileSystemConfig;
configs: [name: string, label: string][];
onChange: FSCChanged<'hop'>;
}
const HopField = connect(({ config, configs, onChange }: HopFieldProps) => {
const callback = (newValue?: [string, string]) => onChange('hop', newValue?.[0]);
function hop(config: FileSystemConfig, onChange: FSCChanged<'hop'>): React.ReactElement {
const callback = (newValue?: FileSystemConfig) => onChange('hop', newValue?.name);
const description = 'Use another configuration as proxy, using a SSH tunnel through the targeted config to the actual remote system';
const displayName = (item: [string, string]) => item[1];
const value = config.hop ? [config.hop, configs.find(c => c[0] === config.hop)?.[1] || config.hop] as const : undefined;
return <FieldDropdown key="hop" label="Hop" {...{ value, values: configs, description, displayName } as const} onChange={callback} optional />;
})<Pick<HopFieldProps, 'configs'>>(state => {
const pairs = new Map<string, string>();
for (const { name, label } of state.data.configs) {
pairs.set(name, label || name);
}
return { configs: Array.from(pairs) };
});
return <FieldConfig key="hop" label="Hop" onChange={callback} value={config.hop} description={description} />;
}
const ProxyTypeToString = {
http: 'HTTP',
socks4: 'SOCKS 4',
socks5: 'SOCKS 5',
} as const;
const ProxyStringToType = {
'HTTP': 'http',
'SOCKS 4': 'socks4',
'SOCKS 5': 'socks5',
'SSH Hop': 'hop',
} as const;
type ProxyStrings = keyof typeof ProxyStringToType;
enum ProxyType { http, socks4, socks5, hop }
const ProxyTypeNames: Record<ProxyType, string> = {
[ProxyType.http]: 'HTTP',
[ProxyType.socks4]: 'SOCKS 4',
[ProxyType.socks5]: 'SOCKS 5',
[ProxyType.hop]: 'SSH Hop',
};
function merged(config: FileSystemConfig, onChange: FSCChanged, onChangeMultiple: FSCChangedMultiple): React.ReactElement | null {
function callback(newValue?: ProxyStrings) {
// Fields starting with _ don't get saved to file
// We use it here so we know when to display the hop stuff
function Merged(props: { config: FileSystemConfig, onChange: FSCChanged, onChangeMultiple: FSCChangedMultiple }): React.ReactElement | null {
const { config, onChange, onChangeMultiple } = props;
const [showHop, setShowHop] = React.useState(!!config.hop);
function callback(newValue?: ProxyType) {
if (!newValue) {
setShowHop(false);
return onChangeMultiple({
['_hop' as any]: undefined,
hop: undefined,
proxy: undefined,
});
}
const newType = ProxyStringToType[newValue];
if (newType === 'hop') {
return onChangeMultiple({
['_hop' as any]: true,
proxy: undefined,
});
if (newValue === ProxyType.hop) {
setShowHop(true);
return onChangeMultiple({ proxy: undefined });
}
setShowHop(false);
return onChangeMultiple({
['_hop' as any]: undefined,
hop: undefined,
proxy: {
host: '',
port: 22,
...config.proxy,
type: newType,
type: ProxyType[newValue] as any,
}
});
}
const description = 'The type of proxy to use when connecting to the remote system';
const values: ProxyStrings[] = ['SSH Hop', 'SOCKS 4', 'SOCKS 5', 'HTTP'];
const showHop = config.hop || (config as any)._hop;
const type = config.proxy && config.proxy.type;
const value = showHop ? 'SSH Hop' : (type && ProxyTypeToString[type]);
const values: ProxyType[] = [ProxyType.hop, ProxyType.socks4, ProxyType.socks5, ProxyType.http];
const type = config.proxy?.type;
const value = (config.hop || showHop) ? ProxyType.hop : (type && ProxyType[type]);
return <React.Fragment key="proxy">
<FieldDropdown key="proxy" label="Proxy" {...{ value, values, description } as const} onChange={callback} optional />
{showHop && <HopField config={config} onChange={onChange} />}
{config.proxy && proxy(config, onChange)}
<FieldDropdown key="proxy" label="Proxy" {...{ value, values, description } as const} displayName={i => ProxyTypeNames[i!]} onChange={callback} optional />
{(config.hop || showHop) && hop(config, onChange)}
{config.proxy && hostAndPort(config, onChange)}
</React.Fragment>;
}
export const PROXY_FIELD: FieldFactory = merged;
export const PROXY_FIELD: FieldFactory = (config, onChange, onChangeMultiple) =>
<Merged config={config} onChange={onChange} onChangeMultiple={onChangeMultiple} />;

@ -2,7 +2,7 @@ import * as React from 'react';
import { FieldGroup } from './group';
import './index.css';
interface Props<T> {
export interface Props<T> {
label: string;
description?: string;
value: T;

@ -0,0 +1,30 @@
import { connect } from '../redux';
import { FileSystemConfig, formatConfigLocation } from '../types/fileSystemConfig';
import type { Props as FieldBaseProps } from './base';
import { FieldDropdown, Props as FieldDropdownProps } from './dropdown';
type Props = Omit<FieldBaseProps<FileSystemConfig>, 'value'> & FieldDropdownProps<FileSystemConfig> & {
/**
* Defaults to `'full'`. Determines how `values` and the default `displayName`.
* In which way to display configs in the dropdown and how to handle duplicate:
* - `full`: Display `<label || name> - <location>`, no duplicate handling.
* - `names`: Display label/name. Keep first config in case of duplicate names.
*/
type?: 'full' | 'names';
/** Value can be a config name. First matching config will be picked, otherwise `undefined`. */
value?: FileSystemConfig | string;
};
const DISPLAY_NAME: Record<NonNullable<Props['type']>, (config: FileSystemConfig) => string> = {
full: config => `${config.label || config.name} - ${formatConfigLocation(config._location)}`,
names: config => config.label || config.name,
};
export const FieldConfig = connect(({ type = 'full', value, values, ...props }: Props) => {
if (type === 'names') {
const seen = new Set<string>();
values = values.filter(({ name }) => seen.has(name) ? false : seen.add(name));
}
if (typeof value === 'string') value = values.find(({ name }) => name === value);
return <FieldDropdown displayName={DISPLAY_NAME[type]} {...props} value={value} values={values} />;
})<Pick<Props, 'values'>>(state => ({ values: state.data.configs }));

@ -1,7 +1,7 @@
import * as React from 'react';
import { FieldBase } from './base';
interface Props<T> {
export interface Props<T> {
values: T[];
displayName?(item: T): string;
displayStyle?(item: T): React.CSSProperties;

Loading…
Cancel
Save