From b904fb9f179b93c17eb7e9bcb0e360287203425b Mon Sep 17 00:00:00 2001 From: Kelvin Schoofs Date: Fri, 3 Dec 2021 18:18:06 +0100 Subject: [PATCH] Add FieldUmask and use for `newFileMode` (#214) --- CHANGELOG.md | 3 ++ webview/src/ConfigEditor/fields.tsx | 10 +++- webview/src/FieldTypes/umask.tsx | 79 +++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 webview/src/FieldTypes/umask.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 8733052..df8258a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Unreleased +### New features +- The settings UI now has a table-like field to modify `newFileMode` (#214) + ### Changes - The default `newFileMode` is now `0o664` instead defaulting to the underlying library's `0o666` (#214) - **This changes the permission for newly created files**, defaulting to `rw-rw-r--` instead of `rw-rw-rw-` diff --git a/webview/src/ConfigEditor/fields.tsx b/webview/src/ConfigEditor/fields.tsx index 3a57beb..4fe76de 100644 --- a/webview/src/ConfigEditor/fields.tsx +++ b/webview/src/ConfigEditor/fields.tsx @@ -5,6 +5,7 @@ import { FieldDropdownWithInput } from '../FieldTypes/dropdownwithinput'; import { FieldNumber } from '../FieldTypes/number'; import { FieldPath } from '../FieldTypes/path'; import { FieldString } from '../FieldTypes/string'; +import { FieldUmask } from '../FieldTypes/umask'; import { FileSystemConfig, invalidConfigName } from '../types/fileSystemConfig'; import FieldConfigGroup from './configGroupField'; import { PROXY_FIELD } from './proxyFields'; @@ -121,6 +122,13 @@ export function agentForward(config: FileSystemConfig, onChange: FSCChanged<'age return ; } +export function newFileMode(config: FileSystemConfig, onChange: FSCChanged<'newFileMode'>): React.ReactElement { + const callback = (newValue?: number) => onChange('newFileMode', Number.isInteger(newValue) ? `0o${newValue!.toString(8)}` : undefined); + const description = 'The filemode to assign to new files created using VS Code, not the terminal. Similar to umask. Defaults to `rw-rw-r--` (regardless of server config, whether you are root, ...)'; + const value = Number.isInteger(Number(config.newFileMode)) ? Number(config.newFileMode) : 0o664; + return ; +} + export function sftpCommand(config: FileSystemConfig, onChange: FSCChanged<'sftpCommand'>): React.ReactElement { const callback = (newValue?: string) => onChange('sftpCommand', newValue); const description = 'A command to run on the remote SSH session to start a SFTP session (defaults to sftp subsystem)'; @@ -157,5 +165,5 @@ export type FieldFactory = (config: FileSystemConfig, onChange: FSCChanged, onCh export const FIELDS: FieldFactory[] = [ name, merge, label, group, putty, host, port, root, agent, username, password, privateKeyPath, passphrase, - agentForward, sftpCommand, sftpSudo, terminalCommand, taskCommand, + newFileMode, agentForward, sftpCommand, sftpSudo, terminalCommand, taskCommand, PROXY_FIELD]; diff --git a/webview/src/FieldTypes/umask.tsx b/webview/src/FieldTypes/umask.tsx new file mode 100644 index 0000000..9131ed6 --- /dev/null +++ b/webview/src/FieldTypes/umask.tsx @@ -0,0 +1,79 @@ +import { FieldBase } from './base'; + +const PERM_MAP: Record = { + 0: '---', + 1: '--x', + 2: '-w-', + 3: '-wx', + 4: 'r--', + 5: 'r-x', + 6: 'rw-', + 7: 'rwx', +}; + +export class FieldUmask extends FieldBase { + protected getValueClassName() { + return super.getValueClassName() + ' checkbox-box checkbox-small'; + } + public renderInput() { + const value = this.getValue() || 0; + console.log(this.state.newValue, '=>', value); + const cell = (target: number, permission: number) => { + const shifted = permission * target; + const checked = (value & shifted) > 0; + return +
+
this.setPart(shifted, !checked)} /> +
+ ; + }; + const row = (target: number, name: string) => + {name} + {cell(target, 4)} + {cell(target, 2)} + {cell(target, 1)} + ; + return <> + + + + {row(0o100, 'Owner')} + {row(0o010, 'Group')} + {row(0o001, 'Other')} + +
ReadWriteExecute
+ + Resulting mask:{" "} + + {PERM_MAP[(value / 0o100) & 0o7]} + {PERM_MAP[(value / 0o010) & 0o7]} + {PERM_MAP[(value / 0o001) & 0o7]} + + + ; + } + public getError() { + const { newValue } = this.state; + const { validator, optional } = this.props; + if (newValue === undefined) { + if (optional) return null; + return 'No value given'; + } else if (Number.isNaN(Number(newValue))) { + return 'Not a number'; + } + return validator ? validator(newValue!) : null; + } + public getValue(): number | undefined { + const { newValue, oldValue } = this.state; + if (newValue === undefined) { + return this.props.optional ? newValue : Number(oldValue); + } + return typeof newValue === 'number' ? newValue : (Number(newValue) || undefined); + } + public setPart(part: number, checked: boolean): void { + this.setState(({ newValue }) => ({ + newValue: checked ? (newValue || 0) | part : (newValue || 0) & ~part + }), () => this.props.onChange(this.getValue())); + } +} \ No newline at end of file