From 796d1c245b100463ac059f3497c5b7dfcf2bc6d6 Mon Sep 17 00:00:00 2001 From: Kelvin Schoofs Date: Sat, 25 Mar 2023 22:59:05 +0100 Subject: [PATCH] Add FieldList field type with subtype FieldConfigList --- CHANGELOG.md | 1 + webview/src/FieldTypes/base.tsx | 8 ++-- webview/src/FieldTypes/index.css | 34 +++++++++++++++++ webview/src/FieldTypes/list.tsx | 64 ++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 webview/src/FieldTypes/list.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fbc9f0..091b86a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - Caching of Yarn dependencies is now handled by `actions/setup-node` - Migrated from `actions/create-release` and `actions/upload-release-asset` to `softprops/action-gh-release` - Fix Webpack only listening on IPv6 instead of all interfaces + update VS Code default styles +- Added the `FieldList` and derivate `FieldConfigList` field types to the Settings UI ## v1.25.0 (2022-06-01) diff --git a/webview/src/FieldTypes/base.tsx b/webview/src/FieldTypes/base.tsx index c179fd2..8c3d315 100644 --- a/webview/src/FieldTypes/base.tsx +++ b/webview/src/FieldTypes/base.tsx @@ -3,7 +3,7 @@ import { FieldGroup } from './group'; import './index.css'; export interface Props { - label: string; + label?: string; description?: string; value: T; optional?: boolean; @@ -59,8 +59,8 @@ export abstract class FieldBase extends } return newValue!; } - public getLabel() { - return this.props.label; + public getLabel(): string { + return this.props.label || ''; } protected getClassName(): string { return 'Field'; } protected getValueClassName(): string { return 'value'; } @@ -69,7 +69,7 @@ export abstract class FieldBase extends const { description, label, optional, preface, postface } = this.props; return
{group => (group?.register(this), [])} -
{label}
{optional &&
Optional
} + {label && <>
{label}
{optional &&
Optional
}} {description &&
{description}
} {preface} {error &&
{error}
} diff --git a/webview/src/FieldTypes/index.css b/webview/src/FieldTypes/index.css index 0bf4bfe..f6648a8 100644 --- a/webview/src/FieldTypes/index.css +++ b/webview/src/FieldTypes/index.css @@ -117,6 +117,40 @@ div.FieldDropdownWithInput p.arrow { cursor: pointer; } +div.FieldList > div.adder { + display: flex; +} +div.FieldList > div.adder > div.Field { + padding: 0; + flex-grow: 1; +} +div.FieldList button { + height: 1em; + padding: 5px; + margin: 9px 0 0 5px; + box-sizing: content-box; + border-radius: 0; +} +div.FieldList > li { + background: var(--vscode-settings-dropdownBackground); + color: var(--vscode-settings-dropdownForeground); + border: 1px solid var(--vscode-settings-dropdownBorder); + position: relative; + display: flex; + height: 1em; + padding: 5px; + margin: 5px 0; +} +div.FieldList > li > p { + margin: auto; + flex-grow: 1; +} +div.FieldList > li > button { + margin: -4px; + padding: 3px; + height: 100%; +} + div.FieldPath { position: relative; background: none; diff --git a/webview/src/FieldTypes/list.tsx b/webview/src/FieldTypes/list.tsx new file mode 100644 index 0000000..b8e91d1 --- /dev/null +++ b/webview/src/FieldTypes/list.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { connect } from '../redux'; +import { FieldBase } from './base'; +import { FieldDropdown } from './dropdown'; +import { FieldDropdownWithInput } from './dropdownwithinput'; +import { FieldString } from './string'; + +// Maybe in the future we can make this generic, but we'd have to make FieldDropdown etc also generic first +type T = string; + +// TODO: Allow reordering of items + +export interface Props { + options?: T[]; + freeText?: boolean; + //displayName?(item: T): string; + displayStyle?(item: T): React.CSSProperties; +} +interface State { + open: boolean; + inputText: string; +} +export class FieldList extends FieldBase, State> { + public getInitialSubState(props: Props): State { + return { open: false, inputText: '' }; + } + protected renderNewInputField(): React.ReactNode { + const { inputText } = this.state; + const { freeText, options, displayStyle } = this.props; + const FD = options?.length ? (freeText ? FieldDropdownWithInput : FieldDropdown) : (freeText ? FieldString : undefined); + return FD && this.setState({ inputText: t || '' })} />; + } + public renderInput(): React.ReactNode { + const { newValue } = this.state; + const { displayStyle } = this.props; + const newInput = this.renderNewInputField(); + return
+ {newInput &&
{newInput}
} + {newValue?.map((item, index) =>
  • +

    {item}

    + +
  • )} +
    ; + } + protected onAdd = () => { + const inputText = this.state.inputText?.trim(); + if (!inputText) return; + this.setState({ + inputText: '', + newValue: [...this.state.newValue || [], inputText] + }, () => this.props.onChange(this.state.newValue)); + }; + protected onRemove = (index: number) => { + const newValue = [...this.state.newValue || []]; + newValue.splice(index, 1); + this.setState({ newValue }, () => this.props.onChange(newValue)); + }; +} + +export type FieldConfigListState = { options: string[] }; +export const FieldConfigList = connect(FieldList)( + state => ({ options: state.data.configs.map(c => c.name).sort() }), +);