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

Loading…
Cancel
Save