From 325abe1d001e4a34a3626e3930cda50f833e7fb0 Mon Sep 17 00:00:00 2001 From: John Chadwick <86682572+johnwchadwick@users.noreply.github.com> Date: Wed, 8 Jun 2022 15:17:02 +0000 Subject: [PATCH] Improve typing in various UI components. (#4842) --- .../ui/components/base/debounced-input.tsx | 4 +- .../base/dropdown/dropdown-item.tsx | 2 +- .../ui/components/base/dropdown/dropdown.tsx | 7 +- .../components/codemirror/one-line-editor.tsx | 33 +++--- .../components/dropdowns/method-dropdown.tsx | 9 +- .../key-value-editor/key-value-editor.tsx | 41 +++---- .../ui/components/key-value-editor/row.tsx | 107 ++++++++++-------- 7 files changed, 109 insertions(+), 94 deletions(-) diff --git a/packages/insomnia/src/ui/components/base/debounced-input.tsx b/packages/insomnia/src/ui/components/base/debounced-input.tsx index 917fc1775..a3350d59c 100644 --- a/packages/insomnia/src/ui/components/base/debounced-input.tsx +++ b/packages/insomnia/src/ui/components/base/debounced-input.tsx @@ -6,8 +6,8 @@ import { debounce } from '../../../common/misc'; interface Props { onChange: (value: string) => void; - onFocus?: Function; - onBlur?: Function; + onFocus?: (e: React.FocusEvent) => void; + onBlur?: (e: React.FocusEvent) => void; textarea?: boolean; delay?: number; placeholder?: string; diff --git a/packages/insomnia/src/ui/components/base/dropdown/dropdown-item.tsx b/packages/insomnia/src/ui/components/base/dropdown/dropdown-item.tsx index 79428490b..ee238e559 100644 --- a/packages/insomnia/src/ui/components/base/dropdown/dropdown-item.tsx +++ b/packages/insomnia/src/ui/components/base/dropdown/dropdown-item.tsx @@ -19,7 +19,7 @@ interface Props { @autoBindMethodsForReact(AUTOBIND_CFG) export class DropdownItem extends PureComponent { - _handleClick(e) { + _handleClick(e: React.MouseEvent) { const { stayOpenAfterClick, onClick, disabled } = this.props; if (stayOpenAfterClick) { diff --git a/packages/insomnia/src/ui/components/base/dropdown/dropdown.tsx b/packages/insomnia/src/ui/components/base/dropdown/dropdown.tsx index 4b38b2828..9e30ca39b 100644 --- a/packages/insomnia/src/ui/components/base/dropdown/dropdown.tsx +++ b/packages/insomnia/src/ui/components/base/dropdown/dropdown.tsx @@ -60,7 +60,7 @@ export class Dropdown extends PureComponent { this._node = n; } - _handleCheckFilterSubmit(e) { + _handleCheckFilterSubmit(e: React.KeyboardEvent) { if (e.key === 'Enter') { // Listen for the Enter key and "click" on the active list item const selector = `li[data-filter-index="${this.state.filterActiveIndex}"] button`; @@ -232,7 +232,7 @@ export class Dropdown extends PureComponent { } } - _handleClick(e) { + _handleClick(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); this.toggle(); @@ -256,7 +256,8 @@ export class Dropdown extends PureComponent { } } - _getFlattenedChildren(children) { + // TODO: children should not be 'any'. + _getFlattenedChildren(children: any) { let newChildren: ReactNode[] = []; // Ensure children is an array children = Array.isArray(children) ? children : [children]; diff --git a/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx b/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx index eeabbd54c..3a8c12836 100644 --- a/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx +++ b/packages/insomnia/src/ui/components/codemirror/one-line-editor.tsx @@ -16,9 +16,9 @@ interface Props { id?: string; type?: string; mode?: string; - onBlur?: (e: FocusEvent) => void; - onKeyDown?: (e: KeyboardEvent, value?: any) => void; - onFocus?: (e: FocusEvent) => void; + onBlur?: (e: FocusEvent | React.FocusEvent) => void; + onKeyDown?: (e: KeyboardEvent | React.KeyboardEvent, value?: any) => void; + onFocus?: (e: FocusEvent | React.FocusEvent) => void; onChange?: CodeEditorOnChange; onPaste?: (e: ClipboardEvent) => void; getAutocompleteConstants?: () => string[] | PromiseLike; @@ -161,8 +161,9 @@ export class OneLineEditor extends PureComponent { this._convertToInputIfNotFocused(); } - _handleEditorFocus(e) { - const focusedFromTabEvent = !!e.sourceCapabilities; + _handleEditorFocus(e: FocusEvent) { + // TODO: unclear why this is missing in TypeScript DOM. + const focusedFromTabEvent = !!(e as any).sourceCapabilities; if (focusedFromTabEvent) { this._editor?.focusEnd(); @@ -179,7 +180,7 @@ export class OneLineEditor extends PureComponent { this.props.onFocus?.(e); } - _handleInputFocus(e) { + _handleInputFocus(e: React.FocusEvent) { // If we're focusing the whole thing, blur the input. This happens when // the user tabs to the field. const start = this._input?.getSelectionStart(); @@ -203,19 +204,19 @@ export class OneLineEditor extends PureComponent { this.props.onFocus?.(e); } - _handleInputChange(value) { + _handleInputChange(value: string) { this._convertToEditorPreserveFocus(); this.props.onChange?.(value); } - _handleInputKeyDown(event) { + _handleInputKeyDown(event: React.KeyboardEvent) { if (this.props.onKeyDown) { - this.props.onKeyDown(event, event.target.value); + this.props.onKeyDown(event, event.currentTarget.value); } } - _handleInputBlur(e: FocusEvent) { + _handleInputBlur(e: React.FocusEvent) { // Set focused state this._input?.removeAttribute('data-focused'); @@ -245,10 +246,11 @@ export class OneLineEditor extends PureComponent { } // @TODO Refactor this event handler. The way we search for a parent form node is not stable. - _handleKeyDown(event) { + _handleKeyDown(event: KeyboardEvent) { // submit form if needed if (event.keyCode === 13) { - let node = event.target; + // TODO: This can be NULL, or not an HTMLElement. + let node = event.target as HTMLElement; for (let i = 0; i < 20 && node; i++) { if (node.tagName === 'FORM') { @@ -258,7 +260,8 @@ export class OneLineEditor extends PureComponent { break; } - node = node.parentNode; + // TODO: This can be NULL. + node = node.parentNode as HTMLElement; } } @@ -310,7 +313,7 @@ export class OneLineEditor extends PureComponent { return; } - if (this._mayContainNunjucks(this.getValue())) { + if (this._mayContainNunjucks(this.getValue() || '')) { return; } @@ -327,7 +330,7 @@ export class OneLineEditor extends PureComponent { this._input = n; } - _mayContainNunjucks(text) { + _mayContainNunjucks(text: string) { // Not sure, but sometimes this isn't a string if (typeof text !== 'string') { return false; diff --git a/packages/insomnia/src/ui/components/dropdowns/method-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/method-dropdown.tsx index b6acdeb28..dcd0e20fb 100644 --- a/packages/insomnia/src/ui/components/dropdowns/method-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/method-dropdown.tsx @@ -12,7 +12,7 @@ const LOCALSTORAGE_KEY = 'insomnia.httpMethods'; const GRPC_LABEL = 'gRPC'; interface Props { - onChange: Function; + onChange: (method: string) => void; method: string; right?: boolean; showGrpc?: boolean; @@ -28,12 +28,11 @@ export class MethodDropdown extends PureComponent { } _handleSetCustomMethod() { - let recentMethods; + let recentMethods: string[]; try { const v = window.localStorage.getItem(LOCALSTORAGE_KEY); - // @ts-expect-error -- TSCONVERSION don't try parse if no item found - recentMethods = JSON.parse(v) || []; + recentMethods = v ? JSON.parse(v) || [] : []; } catch (err) { recentMethods = []; } @@ -74,7 +73,7 @@ export class MethodDropdown extends PureComponent { }); } - _handleChange(method) { + _handleChange(method: string) { this.props.onChange(method); } diff --git a/packages/insomnia/src/ui/components/key-value-editor/key-value-editor.tsx b/packages/insomnia/src/ui/components/key-value-editor/key-value-editor.tsx index 3d6962e15..9e13a0021 100644 --- a/packages/insomnia/src/ui/components/key-value-editor/key-value-editor.tsx +++ b/packages/insomnia/src/ui/components/key-value-editor/key-value-editor.tsx @@ -10,7 +10,7 @@ import { DropdownButton } from '../base/dropdown/dropdown-button'; import { DropdownItem } from '../base/dropdown/dropdown-item'; import { Lazy } from '../base/lazy'; import { PromptButton } from '../base/prompt-button'; -import { Row } from './row'; +import { AutocompleteHandler, Pair, Row } from './row'; const NAME = 'name'; const VALUE = 'value'; @@ -25,8 +25,8 @@ const RIGHT = 39; interface Props { onChange: Function; pairs: any[]; - handleGetAutocompleteNameConstants?: Function; - handleGetAutocompleteValueConstants?: Function; + handleGetAutocompleteNameConstants?: AutocompleteHandler; + handleGetAutocompleteValueConstants?: AutocompleteHandler; allowFile?: boolean; allowMultiline?: boolean; sortable?: boolean; @@ -83,10 +83,10 @@ export class KeyValueEditor extends PureComponent { } } - _handlePairChange(pair) { + _handlePairChange(pair: Pair) { const i = this._getPairIndex(pair); - const pairs = [ + const pairs: Pair[] = [ ...this.state.pairs.slice(0, i), Object.assign({}, pair), ...this.state.pairs.slice(i + 1), @@ -99,7 +99,7 @@ export class KeyValueEditor extends PureComponent { this._onChange([]); } - _handleMove(pairToMove, pairToTarget, targetOffset) { + _handleMove(pairToMove: Pair, pairToTarget: Pair, targetOffset: 1 | -1) { if (pairToMove.id === pairToTarget.id) { // Nothing to do return; @@ -122,7 +122,7 @@ export class KeyValueEditor extends PureComponent { this._onChange(pairs); } - _handlePairDelete(pair) { + _handlePairDelete(pair: Pair) { const i = this.state.pairs.findIndex(p => p.id === pair.id); this._deletePair(i, true); @@ -140,28 +140,31 @@ export class KeyValueEditor extends PureComponent { this._focusedField = null; } - _handleFocusName(pair) { + _handleFocusName(pair: Pair) { this._setFocusedPair(pair); this._focusedField = NAME; - this._rows[pair.id].focusNameEnd(); + // TODO: this may be a bug; pair.id is not an index into _rows + this._rows[pair.id as any].focusNameEnd(); } - _handleFocusValue(pair) { + _handleFocusValue(pair: Pair) { this._setFocusedPair(pair); this._focusedField = VALUE; - this._rows[pair.id].focusValueEnd(); + // TODO: this may be a bug; pair.id is not an index into _rows + this._rows[pair.id as any].focusValueEnd(); } - _handleFocusDescription(pair) { + _handleFocusDescription(pair: Pair) { this._setFocusedPair(pair); this._focusedField = DESCRIPTION; - this._rows[pair.id].focusDescriptionEnd(); + // TODO: this may be a bug; pair.id is not an index into _rows + this._rows[pair.id as any].focusDescriptionEnd(); } _handleAddFromName() { @@ -183,7 +186,7 @@ export class KeyValueEditor extends PureComponent { this._addPair(); } - _handleKeyDown(_pair, e, value) { + _handleKeyDown(_pair: Pair, e: KeyboardEvent, value?: any) { if (e.metaKey || e.ctrlKey) { return; } @@ -209,7 +212,7 @@ export class KeyValueEditor extends PureComponent { } } - _onChange(pairs) { + _onChange(pairs: Pair[]) { this.setState( { pairs, @@ -235,7 +238,7 @@ export class KeyValueEditor extends PureComponent { } position = position === undefined ? numPairs : position; - const pair = { + const pair: Pair = { id: '', name: '', value: '', @@ -260,7 +263,7 @@ export class KeyValueEditor extends PureComponent { this.props.onCreate?.(); } - _deletePair(position, breakFocus = false) { + _deletePair(position: number, breakFocus = false) { if (this.props.disableDelete) { return; } @@ -385,7 +388,7 @@ export class KeyValueEditor extends PureComponent { } } - _getPairIndex(pair) { + _getPairIndex(pair: Pair) { if (pair) { return this.state.pairs.findIndex(p => p.id === pair.id); } else { @@ -401,7 +404,7 @@ export class KeyValueEditor extends PureComponent { return this.state.pairs.find(p => p.id === this._focusedPairId) || null; } - _setFocusedPair(pair) { + _setFocusedPair(pair: Pair) { if (pair) { this._focusedPairId = pair.id; } else { diff --git a/packages/insomnia/src/ui/components/key-value-editor/row.tsx b/packages/insomnia/src/ui/components/key-value-editor/row.tsx index 01f3e6056..c4b55d765 100644 --- a/packages/insomnia/src/ui/components/key-value-editor/row.tsx +++ b/packages/insomnia/src/ui/components/key-value-editor/row.tsx @@ -2,7 +2,7 @@ import { autoBindMethodsForReact } from 'class-autobind-decorator'; import classnames from 'classnames'; import React, { forwardRef, ForwardRefRenderFunction, PureComponent } from 'react'; -import { ConnectDragPreview, ConnectDragSource, ConnectDropTarget, DragSource, DropTarget } from 'react-dnd'; +import { ConnectDragPreview, ConnectDragSource, ConnectDropTarget, DragSource, DropTarget, DropTargetMonitor } from 'react-dnd'; import ReactDOM from 'react-dom'; import { AUTOBIND_CFG } from '../../../common/constants'; @@ -18,32 +18,39 @@ import { OneLineEditor } from '../codemirror/one-line-editor'; import { CodePromptModal } from '../modals/code-prompt-modal'; import { showModal } from '../modals/index'; +export interface Pair { + id: string; + name: string; + value: string; + description: string; + fileName?: string; + type?: string; + disabled?: boolean; + multiline?: boolean | string; +} + +export type AutocompleteHandler = (pair: Pair) => string[] | PromiseLike; + +type DragDirection = 0 | 1 | -1; + interface Props { - onChange: Function; - onDelete: Function; - onFocusName: Function; - onFocusValue: Function; - onFocusDescription: Function; + onChange: (pair: Pair) => void; + onDelete: (pair: Pair) => void; + onFocusName: (pair: Pair, e: FocusEvent) => void; + onFocusValue: (pair: Pair, e: FocusEvent) => void; + onFocusDescription: (pair: Pair, e: FocusEvent) => void; displayDescription: boolean; index: number; - pair: { - id: string; - name: string; - value: string; - description: string; - fileName: string; - type: string; - disabled: boolean; - }; + pair: Pair; readOnly?: boolean; - onMove?: Function; - onKeyDown?: Function; - onBlurName?: Function; - onBlurValue?: Function; - onBlurDescription?: Function; + onMove?: (pairToMove: Pair, pairToTarget: Pair, targetOffset: 1 | -1) => void; + onKeyDown?: (pair: Pair, e: KeyboardEvent, value?: any) => void; + onBlurName?: (pair: Pair, e: FocusEvent) => void; + onBlurValue?: (pair: Pair, e: FocusEvent) => void; + onBlurDescription?: (pair: Pair, e: FocusEvent) => void; enableNunjucks?: boolean; - handleGetAutocompleteNameConstants?: Function; - handleGetAutocompleteValueConstants?: Function; + handleGetAutocompleteNameConstants?: AutocompleteHandler; + handleGetAutocompleteValueConstants?: AutocompleteHandler; namePlaceholder?: string; valuePlaceholder?: string; descriptionPlaceholder?: string; @@ -66,7 +73,7 @@ interface Props { } interface State { - dragDirection: number; + dragDirection: DragDirection; } @autoBindMethodsForReact(AUTOBIND_CFG) @@ -94,7 +101,7 @@ class KeyValueEditorRowInternal extends PureComponent { this._descriptionInput?.focusEnd(); } - setDragDirection(dragDirection) { + setDragDirection(dragDirection: DragDirection) { if (dragDirection !== this.state.dragDirection) { this.setState({ dragDirection, @@ -106,23 +113,23 @@ class KeyValueEditorRowInternal extends PureComponent { this._descriptionInput = n; } - _sendChange(patch) { + _sendChange(patch: Partial) { const pair = Object.assign({}, this.props.pair, patch); this.props.onChange?.(pair); } - _handleNameChange(name) { + _handleNameChange(name: string) { this._sendChange({ name, }); } - _handleValuePaste(e) { + _handleValuePaste(e: ClipboardEvent) { if (!this.props.allowMultiline) { return; } - const value = e.clipboardData.getData('text/plain'); + const value = e.clipboardData?.getData('text/plain'); if (value?.includes('\n')) { e.preventDefault(); @@ -147,25 +154,25 @@ class KeyValueEditorRowInternal extends PureComponent { } } - _handleValueChange(value) { + _handleValueChange(value: string) { this._sendChange({ value, }); } - _handleFileNameChange(fileName) { + _handleFileNameChange(fileName: string) { this._sendChange({ fileName, }); } - _handleDescriptionChange(description) { + _handleDescriptionChange(description: string) { this._sendChange({ description, }); } - _handleTypeChange(def) { + _handleTypeChange(def: Partial) { // Remove newlines if converting to text // WARNING: props should never be overwritten! let value = this.props.pair.value || ''; @@ -181,31 +188,31 @@ class KeyValueEditorRowInternal extends PureComponent { }); } - _handleDisableChange(disabled) { + _handleDisableChange(disabled: boolean) { this._sendChange({ disabled, }); } - _handleFocusName(e) { + _handleFocusName(e: FocusEvent) { this.props.onFocusName(this.props.pair, e); } - _handleFocusValue(e) { + _handleFocusValue(e: FocusEvent) { this.props.onFocusValue(this.props.pair, e); } - _handleFocusDescription(e) { + _handleFocusDescription(e: FocusEvent) { this.props.onFocusDescription(this.props.pair, e); } - _handleBlurName(e) { + _handleBlurName(e: FocusEvent) { if (this.props.onBlurName) { this.props.onBlurName(this.props.pair, e); } } - _handleBlurValue(e) { + _handleBlurValue(e: FocusEvent) { if (this.props.onBlurName) { this.props.onBlurValue?.(this.props.pair, e); } @@ -223,7 +230,7 @@ class KeyValueEditorRowInternal extends PureComponent { } } - _handleKeyDown(e, value) { + _handleKeyDown(e: KeyboardEvent, value?: any) { if (this.props.onKeyDown) { this.props.onKeyDown(this.props.pair, e, value); } @@ -235,6 +242,8 @@ class KeyValueEditorRowInternal extends PureComponent { if (handleGetAutocompleteNameConstants) { return handleGetAutocompleteNameConstants(this.props.pair); } + + return []; } _handleAutocompleteValues() { @@ -243,6 +252,8 @@ class KeyValueEditorRowInternal extends PureComponent { if (handleGetAutocompleteValueConstants) { return handleGetAutocompleteValueConstants(this.props.pair); } + + return []; } _handleEditMultiline() { @@ -253,9 +264,8 @@ class KeyValueEditorRowInternal extends PureComponent { defaultValue: pair.value, onChange: this._handleValueChange, enableRender: enableNunjucks, - // @ts-expect-error -- TSCONVERSION mode: pair.multiline || 'text/plain', - onModeChange: mode => { + onModeChange: (mode: string) => { this._handleTypeChange( Object.assign({}, pair, { multiline: mode, @@ -316,7 +326,6 @@ class KeyValueEditorRowInternal extends PureComponent { onChange={this._handleFileNameChange} /> ); - // @ts-expect-error -- TSCONVERSION } else if (pair.type === 'text' && pair.multiline) { const bytes = Buffer.from(pair.value, 'utf8').length; return ( @@ -543,27 +552,27 @@ const dragSource = { }, }; -function isAbove(monitor, component) { +function isAbove(monitor: DropTargetMonitor, component: any) { const hoveredNode = ReactDOM.findDOMNode(component); // @ts-expect-error -- TSCONVERSION const hoveredTop = hoveredNode.getBoundingClientRect().top; // @ts-expect-error -- TSCONVERSION const height = hoveredNode.clientHeight; - const draggedTop = monitor.getSourceClientOffset().y; + const draggedTop = monitor.getSourceClientOffset()?.y; // NOTE: Not quite sure why it's height / 3 (seems to work) - return hoveredTop > draggedTop - height / 3; + return draggedTop !== undefined ? hoveredTop > draggedTop - height / 3 : false; } const dragTarget = { - drop(props, monitor, component) { + drop(props: Props, monitor: DropTargetMonitor, component: any) { if (isAbove(monitor, component)) { - props.onMove(monitor.getItem().pair, props.pair, 1); + props.onMove?.(monitor.getItem().pair, props.pair, 1); } else { - props.onMove(monitor.getItem().pair, props.pair, -1); + props.onMove?.(monitor.getItem().pair, props.pair, -1); } }, - hover(_props, monitor, component) { + hover(_props: Props, monitor: DropTargetMonitor, component: any) { if (isAbove(monitor, component)) { component.setDragDirection(1); } else {