-
-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add FieldConfig dropdown type + improve proxy field
- Loading branch information
1 parent
bd25e73
commit c749fc9
Showing
4 changed files
with
63 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,95 +1,73 @@ | ||
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} | ||
description="Hostname or IP address of the proxy." /> | ||
</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} />; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 })); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters