diff --git a/package-lock.json b/package-lock.json index 24fa69199..40f20ada1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,12 +70,14 @@ "@types/codemirror": "^5.60.15", "@types/color": "^3.0.6", "@types/cors": "^2.8.17", + "@types/file-saver": "^2.0.7", "@types/lodash.capitalize": "^4.2.9", "@types/lodash.isequal": "^4.5.8", "@types/lodash.throttle": "^4.1.9", "@types/react": "^16.14.52", "@types/react-aria-modal": "^4.0.9", "@types/react-autocomplete": "^1.8.9", + "@types/react-collapse": "^5.0.4", "@types/react-color": "^3.0.10", "@types/react-dom": "^16.9.24", "@types/react-file-reader-input": "^2.0.4", @@ -4711,6 +4713,12 @@ "@types/send": "*" } }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true + }, "node_modules/@types/find-cache-dir": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", @@ -4899,6 +4907,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-collapse": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/react-collapse/-/react-collapse-5.0.4.tgz", + "integrity": "sha512-tM5cVB6skGLneNYnRK2E3R56VOHguSeJQHslGPTIMC58ytL3oelT8L/l1onkwHGn5vSEs2BEq2Olzrur+YdliA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-color": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.10.tgz", diff --git a/package.json b/package.json index fd9e96242..eea99ee4b 100644 --- a/package.json +++ b/package.json @@ -99,12 +99,14 @@ "@types/codemirror": "^5.60.15", "@types/color": "^3.0.6", "@types/cors": "^2.8.17", + "@types/file-saver": "^2.0.7", "@types/lodash.capitalize": "^4.2.9", "@types/lodash.isequal": "^4.5.8", "@types/lodash.throttle": "^4.1.9", "@types/react": "^16.14.52", "@types/react-aria-modal": "^4.0.9", "@types/react-autocomplete": "^1.8.9", + "@types/react-collapse": "^5.0.4", "@types/react-color": "^3.0.10", "@types/react-dom": "^16.9.24", "@types/react-file-reader-input": "^2.0.4", diff --git a/src/components/AppLayout.jsx b/src/components/AppLayout.tsx similarity index 73% rename from src/components/AppLayout.jsx rename to src/components/AppLayout.tsx index 99b669ea5..90392ab92 100644 --- a/src/components/AppLayout.jsx +++ b/src/components/AppLayout.tsx @@ -2,16 +2,16 @@ import React from 'react' import PropTypes from 'prop-types' import ScrollContainer from './ScrollContainer' -class AppLayout extends React.Component { - static propTypes = { - toolbar: PropTypes.element.isRequired, - layerList: PropTypes.element.isRequired, - layerEditor: PropTypes.element, - map: PropTypes.element.isRequired, - bottom: PropTypes.element, - modals: PropTypes.node, - } +type AppLayoutProps = { + toolbar: React.ReactElement + layerList: React.ReactElement + layerEditor?: React.ReactElement + map: React.ReactElement + bottom?: React.ReactElement + modals?: React.ReactNode +}; +class AppLayout extends React.Component { static childContextTypes = { reactIconBase: PropTypes.object } diff --git a/src/components/AppMessagePanel.jsx b/src/components/AppMessagePanel.tsx similarity index 62% rename from src/components/AppMessagePanel.jsx rename to src/components/AppMessagePanel.tsx index 3f69343c2..90948000e 100644 --- a/src/components/AppMessagePanel.jsx +++ b/src/components/AppMessagePanel.tsx @@ -1,29 +1,28 @@ import React from 'react' -import PropTypes from 'prop-types' import {formatLayerId} from '../util/format'; +import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'; -export default class AppMessagePanel extends React.Component { - static propTypes = { - errors: PropTypes.array, - infos: PropTypes.array, - mapStyle: PropTypes.object, - onLayerSelect: PropTypes.func, - currentLayer: PropTypes.object, - selectedLayerIndex: PropTypes.number, - } +type AppMessagePanelProps = { + errors?: unknown[] + infos?: unknown[] + mapStyle?: StyleSpecification + onLayerSelect?(...args: unknown[]): unknown + currentLayer?: object + selectedLayerIndex?: number +}; +export default class AppMessagePanel extends React.Component { static defaultProps = { onLayerSelect: () => {}, } render() { const {selectedLayerIndex} = this.props; - const errors = this.props.errors.map((error, idx) => { + const errors = this.props.errors?.map((error: any, idx) => { let content; if (error.parsed && error.parsed.type === "layer") { const {parsed} = error; - const {mapStyle, currentLayer} = this.props; - const layerId = mapStyle.layers[parsed.data.index].id; + const layerId = this.props.mapStyle?.layers[parsed.data.index].id; content = ( <> Layer {formatLayerId(layerId)}: {parsed.data.message} @@ -32,7 +31,7 @@ export default class AppMessagePanel extends React.Component {  —  @@ -49,7 +48,7 @@ export default class AppMessagePanel extends React.Component {

}) - const infos = this.props.infos.map((m, i) => { + const infos = this.props.infos?.map((m, i) => { return

{m}

}) diff --git a/src/components/AppToolbar.jsx b/src/components/AppToolbar.tsx similarity index 77% rename from src/components/AppToolbar.jsx rename to src/components/AppToolbar.tsx index 13d62caef..97e4ac793 100644 --- a/src/components/AppToolbar.jsx +++ b/src/components/AppToolbar.tsx @@ -1,5 +1,4 @@ import React from 'react' -import PropTypes from 'prop-types' import classnames from 'classnames' import {detect} from 'detect-browser'; @@ -9,27 +8,28 @@ import pkgJson from '../../package.json' // This is required because of , there isn't another way to detect support that I'm aware of. const browser = detect(); -const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser.name) > -1; +const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser!.name) > -1; -class IconText extends React.Component { - static propTypes = { - children: PropTypes.node, - } +type IconTextProps = { + children?: React.ReactNode +}; + +class IconText extends React.Component { render() { return {this.props.children} } } -class ToolbarLink extends React.Component { - static propTypes = { - className: PropTypes.string, - children: PropTypes.node, - href: PropTypes.string, - onToggleModal: PropTypes.func, - } +type ToolbarLinkProps = { + className?: string + children?: React.ReactNode + href?: string + onToggleModal?(...args: unknown[]): unknown +}; +class ToolbarLink extends React.Component { render() { return { render() { return { render() { return
{ render() { return @@ -246,7 +246,7 @@ export default class AppToolbar extends React.Component { className="maputnik-select" data-wd-key="maputnik-select" onChange={(e) => this.handleSelection(e.target.value)} - value={currentView.id} + value={currentView?.id} > {views.filter(v => v.group === "general").map((item) => { return ( diff --git a/src/components/Collapse.jsx b/src/components/Collapse.tsx similarity index 65% rename from src/components/Collapse.jsx rename to src/components/Collapse.tsx index df854c347..945f143d6 100644 --- a/src/components/Collapse.jsx +++ b/src/components/Collapse.tsx @@ -1,15 +1,15 @@ import React from 'react' -import PropTypes from 'prop-types' import { Collapse as ReactCollapse } from 'react-collapse' -import {reducedMotionEnabled} from '../../libs/accessibility' +import {reducedMotionEnabled} from '../libs/accessibility' -export default class Collapse extends React.Component { - static propTypes = { - isActive: PropTypes.bool.isRequired, - children: PropTypes.element.isRequired - } +type CollapseProps = { + isActive: boolean + children: React.ReactElement +}; + +export default class Collapse extends React.Component { static defaultProps = { isActive: true } diff --git a/src/components/Collapser.jsx b/src/components/Collapser.tsx similarity index 61% rename from src/components/Collapser.jsx rename to src/components/Collapser.tsx index b2b413bbd..bca8e529c 100644 --- a/src/components/Collapser.jsx +++ b/src/components/Collapser.tsx @@ -1,13 +1,12 @@ import React from 'react' -import PropTypes from 'prop-types' import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md' -export default class Collapser extends React.Component { - static propTypes = { - isCollapsed: PropTypes.bool.isRequired, - style: PropTypes.object, - } +type CollapserProps = { + isCollapsed: boolean + style?: object +}; +export default class Collapser extends React.Component { render() { const iconStyle = { width: 20, diff --git a/src/components/FieldAutocomplete.jsx b/src/components/FieldAutocomplete.jsx deleted file mode 100644 index da63105e4..000000000 --- a/src/components/FieldAutocomplete.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import Block from './Block' -import InputAutocomplete from './InputAutocomplete' - - -export default class FieldAutocomplete extends React.Component { - static propTypes = { - ...InputAutocomplete.propTypes, - } - - render() { - const {props} = this; - - return - - - } -} - diff --git a/src/components/FieldAutocomplete.tsx b/src/components/FieldAutocomplete.tsx new file mode 100644 index 000000000..133ce6075 --- /dev/null +++ b/src/components/FieldAutocomplete.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import Block from './Block' +import InputAutocomplete, { InputAutocompleteProps } from './InputAutocomplete' + + +type FieldAutocompleteProps = InputAutocompleteProps & { + label?: string; +}; + + +export default class FieldAutocomplete extends React.Component { + render() { + return + + + } +} + diff --git a/src/components/FieldCheckbox.jsx b/src/components/FieldCheckbox.jsx deleted file mode 100644 index 7cdc08b83..000000000 --- a/src/components/FieldCheckbox.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import Block from './Block' -import InputCheckbox from './InputCheckbox' - - -export default class FieldCheckbox extends React.Component { - static propTypes = { - ...InputCheckbox.propTypes, - } - - render() { - const {props} = this; - - return - - - } -} - diff --git a/src/components/FieldCheckbox.tsx b/src/components/FieldCheckbox.tsx new file mode 100644 index 000000000..28041ac45 --- /dev/null +++ b/src/components/FieldCheckbox.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import Block from './Block' +import InputCheckbox, {InputCheckboxProps} from './InputCheckbox' + + +type FieldCheckboxProps = InputCheckboxProps & { + label?: string; +}; + + +export default class FieldCheckbox extends React.Component { + render() { + return + + + } +} + diff --git a/src/components/FieldComment.jsx b/src/components/FieldComment.tsx similarity index 72% rename from src/components/FieldComment.jsx rename to src/components/FieldComment.tsx index 9c6125ec9..a455d0965 100644 --- a/src/components/FieldComment.jsx +++ b/src/components/FieldComment.tsx @@ -1,15 +1,14 @@ import React from 'react' -import PropTypes from 'prop-types' import Block from './Block' import InputString from './InputString' -export default class FieldComment extends React.Component { - static propTypes = { - value: PropTypes.string, - onChange: PropTypes.func.isRequired, - } +type FieldCommentProps = { + value?: string + onChange(...args: unknown[]): unknown +}; +export default class FieldComment extends React.Component { render() { const fieldSpec = { doc: "Comments for the current layer. This is non-standard and not in the spec." diff --git a/src/components/FieldDynamicArray.jsx b/src/components/FieldDynamicArray.jsx deleted file mode 100644 index 47bbbaf5e..000000000 --- a/src/components/FieldDynamicArray.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import Block from './Block' -import InputDynamicArray from './InputDynamicArray' -import Fieldset from './Fieldset' - -export default class FieldDynamicArray extends React.Component { - static propTypes = { - ...InputDynamicArray.propTypes, - name: PropTypes.string, - } - - render() { - const {props} = this; - - return
- -
- } -} - diff --git a/src/components/FieldDynamicArray.tsx b/src/components/FieldDynamicArray.tsx new file mode 100644 index 000000000..fc3666b67 --- /dev/null +++ b/src/components/FieldDynamicArray.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import InputDynamicArray, {FieldDynamicArrayProps as InputDynamicArrayProps} from './InputDynamicArray' +import Fieldset from './Fieldset' + +type FieldDynamicArrayProps = InputDynamicArrayProps & { + name?: string +}; + +export default class FieldDynamicArray extends React.Component { + render() { + return
+ +
+ } +} + diff --git a/src/components/FieldFunction.jsx b/src/components/FieldFunction.tsx similarity index 88% rename from src/components/FieldFunction.jsx rename to src/components/FieldFunction.tsx index 28c23d6cb..fea91f74c 100644 --- a/src/components/FieldFunction.jsx +++ b/src/components/FieldFunction.tsx @@ -1,19 +1,18 @@ import React from 'react' -import PropTypes from 'prop-types' import SpecProperty from './_SpecProperty' -import DataProperty from './_DataProperty' +import DataProperty, { Stop } from './_DataProperty' import ZoomProperty from './_ZoomProperty' import ExpressionProperty from './_ExpressionProperty' import {function as styleFunction} from '@maplibre/maplibre-gl-style-spec'; import {findDefaultFromSpec} from '../util/spec-helper'; -function isLiteralExpression (value) { +function isLiteralExpression(value: any) { return (Array.isArray(value) && value.length === 2 && value[0] === "literal"); } -function isGetExpression (value) { +function isGetExpression(value: any) { return ( Array.isArray(value) && value.length === 2 && @@ -21,14 +20,14 @@ function isGetExpression (value) { ); } -function isZoomField(value) { +function isZoomField(value: any) { return ( typeof(value) === 'object' && value.stops && typeof(value.property) === 'undefined' && Array.isArray(value.stops) && value.stops.length > 1 && - value.stops.every(stop => { + value.stops.every((stop: Stop) => { return ( Array.isArray(stop) && stop.length === 2 @@ -37,7 +36,7 @@ function isZoomField(value) { ); } -function isIdentityProperty (value) { +function isIdentityProperty(value: any) { return ( typeof(value) === 'object' && value.type === "identity" && @@ -45,14 +44,14 @@ function isIdentityProperty (value) { ); } -function isDataStopProperty (value) { +function isDataStopProperty(value: any) { return ( typeof(value) === 'object' && value.stops && typeof(value.property) !== 'undefined' && value.stops.length > 1 && Array.isArray(value.stops) && - value.stops.every(stop => { + value.stops.every((stop: Stop) => { return ( Array.isArray(stop) && stop.length === 2 && @@ -62,26 +61,26 @@ function isDataStopProperty (value) { ); } -function isDataField(value) { +function isDataField(value: any) { return ( isIdentityProperty(value) || isDataStopProperty(value) ); } -function isPrimative (value) { +function isPrimative(value: any): value is string | boolean | number { const valid = ["string", "boolean", "number"]; return valid.includes(typeof(value)); } -function isArrayOfPrimatives (values) { +function isArrayOfPrimatives(values: any): values is Array { if (Array.isArray(values)) { return values.every(isPrimative); } return false; } -function getDataType (value, fieldSpec={}) { +function getDataType(value: any, fieldSpec={} as any) { if (value === undefined) { return "value"; } @@ -103,35 +102,33 @@ function getDataType (value, fieldSpec={}) { } +type FieldFunctionProps = { + onChange(fieldName: string, value: any): unknown + fieldName: string + fieldType: string + fieldSpec: any + errors?: unknown[] + value?: any +}; + +type FieldFunctionState = { + dataType: string + isEditing: boolean +} + /** Supports displaying spec field for zoom function objects * https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property */ -export default class FieldFunction extends React.Component { - static propTypes = { - onChange: PropTypes.func.isRequired, - fieldName: PropTypes.string.isRequired, - fieldType: PropTypes.string.isRequired, - fieldSpec: PropTypes.object.isRequired, - errors: PropTypes.object, - - value: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.string, - PropTypes.number, - PropTypes.bool, - PropTypes.array - ]), - } - - constructor (props) { - super(); +export default class FieldFunction extends React.Component { + constructor (props: FieldFunctionProps) { + super(props); this.state = { dataType: getDataType(props.value, props.fieldSpec), isEditing: false, } } - static getDerivedStateFromProps(props, state) { + static getDerivedStateFromProps(props: FieldFunctionProps, state: FieldFunctionState) { // Because otherwise when editing values we end up accidentally changing field type. if (state.isEditing) { return {}; @@ -144,7 +141,7 @@ export default class FieldFunction extends React.Component { } } - getFieldFunctionType(fieldSpec) { + getFieldFunctionType(fieldSpec: any) { if (fieldSpec.expression.interpolated) { return "exponential" } @@ -183,7 +180,7 @@ export default class FieldFunction extends React.Component { }); } - deleteStop = (stopIdx) => { + deleteStop = (stopIdx: number) => { const stops = this.props.value.stops.slice(0) stops.splice(stopIdx, 1) @@ -207,7 +204,7 @@ export default class FieldFunction extends React.Component { if (value.stops) { zoomFunc = { base: value.base, - stops: value.stops.map(stop => { + stops: value.stops.map((stop: Stop) => { return [stop[0].zoom, stop[1] || findDefaultFromSpec(this.props.fieldSpec)]; }) } @@ -292,7 +289,7 @@ export default class FieldFunction extends React.Component { property: "", type: functionType, base: value.base, - stops: value.stops.map(stop => { + stops: value.stops.map((stop: Stop) => { return [{zoom: stop[0], value: stopValue}, stop[1] || findDefaultFromSpec(this.props.fieldSpec)]; }) } diff --git a/src/components/FieldMultiInput.tsx b/src/components/FieldMultiInput.tsx index 994480d9e..f9a0aade2 100644 --- a/src/components/FieldMultiInput.tsx +++ b/src/components/FieldMultiInput.tsx @@ -10,10 +10,8 @@ type FieldMultiInputProps = InputMultiInputProps & { export default class FieldMultiInput extends React.Component { render() { - const {props} = this; - - return
- + return
+
} } diff --git a/src/components/FieldSource.jsx b/src/components/FieldSource.tsx similarity index 61% rename from src/components/FieldSource.jsx rename to src/components/FieldSource.tsx index 68400f5e7..c20e9eaef 100644 --- a/src/components/FieldSource.jsx +++ b/src/components/FieldSource.tsx @@ -1,19 +1,18 @@ import React from 'react' -import PropTypes from 'prop-types' import {latest} from '@maplibre/maplibre-gl-style-spec' import Block from './Block' import InputAutocomplete from './InputAutocomplete' -export default class FieldSource extends React.Component { - static propTypes = { - value: PropTypes.string, - wdKey: PropTypes.string, - onChange: PropTypes.func, - sourceIds: PropTypes.array, - error: PropTypes.object, - } +type FieldSourceProps = { + value?: string + wdKey?: string + onChange?(...args: unknown[]): unknown + sourceIds?: unknown[] + error?: unknown[] +}; +export default class FieldSource extends React.Component { static defaultProps = { onChange: () => {}, sourceIds: [], @@ -29,7 +28,7 @@ export default class FieldSource extends React.Component { [src, src])} + options={this.props.sourceIds?.map(src => [src, src])} /> } diff --git a/src/components/FieldSourceLayer.jsx b/src/components/FieldSourceLayer.tsx similarity index 65% rename from src/components/FieldSourceLayer.jsx rename to src/components/FieldSourceLayer.tsx index 044a673a1..6bc056c83 100644 --- a/src/components/FieldSourceLayer.jsx +++ b/src/components/FieldSourceLayer.tsx @@ -1,18 +1,17 @@ import React from 'react' -import PropTypes from 'prop-types' import {latest} from '@maplibre/maplibre-gl-style-spec' import Block from './Block' import InputAutocomplete from './InputAutocomplete' -export default class FieldSourceLayer extends React.Component { - static propTypes = { - value: PropTypes.string, - onChange: PropTypes.func, - sourceLayerIds: PropTypes.array, - isFixed: PropTypes.bool, - } +type FieldSourceLayerProps = { + value?: string + onChange?(...args: unknown[]): unknown + sourceLayerIds?: unknown[] + isFixed?: boolean +}; +export default class FieldSourceLayer extends React.Component { static defaultProps = { onChange: () => {}, sourceLayerIds: [], @@ -27,7 +26,7 @@ export default class FieldSourceLayer extends React.Component { keepMenuWithinWindowBounds={!!this.props.isFixed} value={this.props.value} onChange={this.props.onChange} - options={this.props.sourceLayerIds.map(l => [l, l])} + options={this.props.sourceLayerIds?.map(l => [l, l])} /> } diff --git a/src/components/InputArray.tsx b/src/components/InputArray.tsx index 0d2a79c3a..5864e904f 100644 --- a/src/components/InputArray.tsx +++ b/src/components/InputArray.tsx @@ -3,17 +3,17 @@ import InputString from './InputString' import InputNumber from './InputNumber' export type FieldArrayProps = { - value: string[] | number[] + value: (string | number | undefined)[] type?: string length?: number - default?: string[] | number[] - onChange?(...args: unknown[]): unknown + default?: (string | number | undefined)[] + onChange?(value: (string | number | undefined)[] | undefined): unknown 'aria-label'?: string label?: string }; type FieldArrayState = { - value: string[] | number[] + value: (string | number | undefined)[] initialPropsValue: unknown[] } @@ -59,7 +59,7 @@ export default class FieldArray extends React.Component this.changeValue(i, v)} aria-label={this.props['aria-label'] || this.props.label} /> } else { diff --git a/src/components/InputDynamicArray.tsx b/src/components/InputDynamicArray.tsx index aef615f7e..1e6d77c58 100644 --- a/src/components/InputDynamicArray.tsx +++ b/src/components/InputDynamicArray.tsx @@ -11,10 +11,10 @@ import InputUrl from './InputUrl' export type FieldDynamicArrayProps = { - value?: (string | number)[] - type?: 'url' | 'number' | 'enum' - default?: (string | number)[] - onChange?(...args: unknown[]): unknown + value?: (string | number | undefined)[] + type?: 'url' | 'number' | 'enum' | 'string' + default?: (string | number | undefined)[] + onChange?(values: (string | number | undefined)[] | undefined): unknown style?: object fieldSpec?: { values?: any @@ -25,7 +25,7 @@ export type FieldDynamicArrayProps = { export default class FieldDynamicArray extends React.Component { - changeValue(idx: number, newValue: string | number) { + changeValue(idx: number, newValue: string | number | undefined) { const values = this.values.slice(0) values[idx] = newValue if (this.props.onChange) this.props.onChange(values) diff --git a/src/components/InputJson.tsx b/src/components/InputJson.tsx index cf1d35297..544fbe29e 100644 --- a/src/components/InputJson.tsx +++ b/src/components/InputJson.tsx @@ -17,7 +17,7 @@ export type InputJsonProps = { onChange?(...args: unknown[]): unknown lineNumbers?: boolean lineWrapping?: boolean - getValue(data: any): string + getValue?(data: any): string gutters?: string[] className?: string onFocus?(...args: unknown[]): unknown @@ -58,13 +58,13 @@ export default class InputJson extends React.Component { action: this.props.action, style: this.props.style, value: this.props.value, - default: this.props.fieldSpec.default, + default: this.props.fieldSpec?.default, name: this.props.fieldName, - onChange: (newValue: string) => this.props.onChange(this.props.fieldName, newValue), + onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue), 'aria-label': this.props['aria-label'], } - switch(this.props.fieldSpec.type) { + switch(this.props.fieldSpec?.type) { case 'number': return ( { case 'resolvedImage': case 'formatted': case 'string': - if (iconProperties.indexOf(this.props.fieldName) >= 0) { + if (iconProperties.indexOf(this.props.fieldName!) >= 0) { const options = this.props.fieldSpec.values || []; return } diff --git a/src/components/InputString.tsx b/src/components/InputString.tsx index ee1d0bc6e..b072f0a4f 100644 --- a/src/components/InputString.tsx +++ b/src/components/InputString.tsx @@ -5,13 +5,14 @@ export type InputStringProps = { value?: string style?: object default?: string - onChange?(...args: unknown[]): unknown - onInput?(...args: unknown[]): unknown + onChange?(value: string | undefined): unknown + onInput?(value: string | undefined): unknown multi?: boolean required?: boolean disabled?: boolean spellCheck?: boolean 'aria-label'?: string + title?: string }; type InputStringState = { @@ -72,6 +73,7 @@ export default class InputString extends React.Component) => { this.setState({ editing: true, diff --git a/src/components/LayerEditorGroup.jsx b/src/components/LayerEditorGroup.tsx similarity index 72% rename from src/components/LayerEditorGroup.jsx rename to src/components/LayerEditorGroup.tsx index 652aba449..888f8d9bc 100644 --- a/src/components/LayerEditorGroup.jsx +++ b/src/components/LayerEditorGroup.tsx @@ -1,5 +1,4 @@ import React from 'react' -import PropTypes from 'prop-types' import Icon from '@mdi/react' import { mdiMenuDown, @@ -13,21 +12,22 @@ import { } from 'react-accessible-accordion'; -export default class LayerEditorGroup extends React.Component { - static propTypes = { - "id": PropTypes.string, - "data-wd-key": PropTypes.string, - title: PropTypes.string.isRequired, - isActive: PropTypes.bool.isRequired, - children: PropTypes.element.isRequired, - onActiveToggle: PropTypes.func.isRequired - } +type LayerEditorGroupProps = { + "id"?: string + "data-wd-key"?: string + title: string + isActive: boolean + children: React.ReactElement + onActiveToggle(...args: unknown[]): unknown +}; + +export default class LayerEditorGroup extends React.Component { render() { return this.props.onActiveToggle(!this.props.isActive)} + onClick={_e => this.props.onActiveToggle(!this.props.isActive)} > {this.props.title} diff --git a/src/components/LayerListGroup.jsx b/src/components/LayerListGroup.tsx similarity index 63% rename from src/components/LayerListGroup.jsx rename to src/components/LayerListGroup.tsx index 9d62f075f..b362b8ea0 100644 --- a/src/components/LayerListGroup.jsx +++ b/src/components/LayerListGroup.tsx @@ -1,21 +1,20 @@ import React from 'react' -import PropTypes from 'prop-types' import Collapser from './Collapser' -export default class LayerListGroup extends React.Component { - static propTypes = { - title: PropTypes.string.isRequired, - "data-wd-key": PropTypes.string, - isActive: PropTypes.bool.isRequired, - onActiveToggle: PropTypes.func.isRequired, - 'aria-controls': PropTypes.string, - } +type LayerListGroupProps = { + title: string + "data-wd-key"?: string + isActive: boolean + onActiveToggle(...args: unknown[]): unknown + 'aria-controls'?: string +}; +export default class LayerListGroup extends React.Component { render() { return
  • this.props.onActiveToggle(!this.props.isActive)} + onClick={_e => this.props.onActiveToggle(!this.props.isActive)} >
  • diff --git a/src/components/_FieldId.jsx b/src/components/_FieldId.tsx similarity index 61% rename from src/components/_FieldId.jsx rename to src/components/_FieldId.tsx index b12e5eb5e..59e8d7e5e 100644 --- a/src/components/_FieldId.jsx +++ b/src/components/_FieldId.tsx @@ -1,18 +1,17 @@ import React from 'react' -import PropTypes from 'prop-types' import {latest} from '@maplibre/maplibre-gl-style-spec' import Block from './Block' import FieldString from './FieldString' -export default class BlockId extends React.Component { - static propTypes = { - value: PropTypes.string.isRequired, - wdKey: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - error: PropTypes.object, - } +type BlockIdProps = { + value: string + wdKey: string + onChange(...args: unknown[]): unknown + error?: unknown[] +}; +export default class BlockId extends React.Component { render() { return { render() { return { render() { return { static defaultProps = { onChange: () => {}, sourceIds: [], @@ -28,8 +27,8 @@ export default class BlockSource extends React.Component { > [src, src])} + onChange={this.props.onChange!} + options={this.props.sourceIds!.map(src => [src, src])} /> } diff --git a/src/components/_FieldSourceLayer.jsx b/src/components/_FieldSourceLayer.tsx similarity index 60% rename from src/components/_FieldSourceLayer.jsx rename to src/components/_FieldSourceLayer.tsx index 83e39c6c3..06e2f83c3 100644 --- a/src/components/_FieldSourceLayer.jsx +++ b/src/components/_FieldSourceLayer.tsx @@ -1,18 +1,17 @@ import React from 'react' -import PropTypes from 'prop-types' import {latest} from '@maplibre/maplibre-gl-style-spec' import Block from './Block' import FieldAutocomplete from './FieldAutocomplete' -export default class BlockSourceLayer extends React.Component { - static propTypes = { - value: PropTypes.string, - onChange: PropTypes.func, - sourceLayerIds: PropTypes.array, - isFixed: PropTypes.bool, - } +type BlockSourceLayerProps = { + value?: string + onChange?(...args: unknown[]): unknown + sourceLayerIds?: unknown[] + isFixed?: boolean +}; +export default class BlockSourceLayer extends React.Component { static defaultProps = { onChange: () => {}, sourceLayerIds: [], @@ -26,8 +25,8 @@ export default class BlockSourceLayer extends React.Component { [l, l])} + onChange={this.props.onChange!} + options={this.props.sourceLayerIds!.map(l => [l, l])} /> } diff --git a/src/components/_FieldSymbol.jsx b/src/components/_FieldSymbol.jsx deleted file mode 100644 index ab546bddb..000000000 --- a/src/components/_FieldSymbol.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import FieldAutocomplete from './FieldAutocomplete' - - -export default class FieldSymbol extends React.Component { - static propTypes = { - value: PropTypes.string, - icons: PropTypes.array, - style: PropTypes.object, - onChange: PropTypes.func.isRequired, - } - - static defaultProps = { - icons: [] - } - - render() { - return [f, f])} - onChange={this.props.onChange} - wrapperStyle={this.props.style} - /> - } -} - diff --git a/src/components/_FieldSymbol.tsx b/src/components/_FieldSymbol.tsx new file mode 100644 index 000000000..261354e46 --- /dev/null +++ b/src/components/_FieldSymbol.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import FieldAutocomplete from './FieldAutocomplete' + + +type FieldSymbolProps = { + value?: string + icons?: unknown[] + onChange(...args: unknown[]): unknown +}; + + +export default class FieldSymbol extends React.Component { + static defaultProps = { + icons: [] + } + + render() { + return [f, f])} + onChange={this.props.onChange} + /> + } +} + diff --git a/src/components/_FieldType.jsx b/src/components/_FieldType.tsx similarity index 78% rename from src/components/_FieldType.jsx rename to src/components/_FieldType.tsx index 0987ff42f..8dc53009d 100644 --- a/src/components/_FieldType.jsx +++ b/src/components/_FieldType.tsx @@ -1,20 +1,19 @@ import React from 'react' -import PropTypes from 'prop-types' import {latest} from '@maplibre/maplibre-gl-style-spec' import Block from './Block' import FieldSelect from './FieldSelect' import FieldString from './FieldString' -export default class BlockType extends React.Component { - static propTypes = { - value: PropTypes.string.isRequired, - wdKey: PropTypes.string, - onChange: PropTypes.func.isRequired, - error: PropTypes.object, - disabled: PropTypes.bool, - } +type BlockTypeProps = { + value: string + wdKey?: string + onChange(...args: unknown[]): unknown + error?: unknown[] + disabled?: boolean +}; +export default class BlockType extends React.Component { static defaultProps = { disabled: false, } diff --git a/src/components/_FunctionButtons.jsx b/src/components/_FunctionButtons.tsx similarity index 71% rename from src/components/_FunctionButtons.jsx rename to src/components/_FunctionButtons.tsx index bacd3444d..214474bce 100644 --- a/src/components/_FunctionButtons.jsx +++ b/src/components/_FunctionButtons.tsx @@ -1,37 +1,17 @@ import React from 'react' -import PropTypes from 'prop-types' import InputButton from './InputButton' import {MdFunctions, MdInsertChart} from 'react-icons/md' import {mdiFunctionVariant} from '@mdi/js'; +type FunctionInputButtonsProps = { + fieldSpec?: any + onZoomClick?(...args: unknown[]): unknown + onDataClick?(...args: unknown[]): unknown + onExpressionClick?(...args: unknown[]): unknown +}; -/** - * So here we can't just check is `Array.isArray(value)` because certain - * properties accept arrays as values, for example `text-font`. So we must try - * and create an expression. - */ -function isExpression(value, fieldSpec={}) { - if (!Array.isArray(value)) { - return false; - } - try { - expression.createExpression(value, fieldSpec); - return true; - } - catch (err) { - return false; - } -} - -export default class FunctionInputButtons extends React.Component { - static propTypes = { - fieldSpec: PropTypes.object, - onZoomClick: PropTypes.func, - onDataClick: PropTypes.func, - onExpressionClick: PropTypes.func, - } - +export default class FunctionInputButtons extends React.Component { render() { let makeZoomInputButton, makeDataInputButton, expressionInputButton; diff --git a/src/components/_SpecProperty.jsx b/src/components/_SpecProperty.tsx similarity index 50% rename from src/components/_SpecProperty.jsx rename to src/components/_SpecProperty.tsx index e4df51894..bb7574b31 100644 --- a/src/components/_SpecProperty.jsx +++ b/src/components/_SpecProperty.tsx @@ -1,25 +1,24 @@ import React from 'react' -import PropTypes from 'prop-types' -import SpecField from './SpecField' +import SpecField, {SpecFieldProps} from './SpecField' import FunctionButtons from './_FunctionButtons' -import Block from './Block' import labelFromFieldName from './_labelFromFieldName' -export default class SpecProperty extends React.Component { - static propTypes = { - onZoomClick: PropTypes.func.isRequired, - onDataClick: PropTypes.func.isRequired, - fieldName: PropTypes.string, - fieldType: PropTypes.string, - fieldSpec: PropTypes.object, - value: PropTypes.any, - errors: PropTypes.object, - onExpressionClick: PropTypes.func, - } +type SpecPropertyProps = SpecFieldProps & { + onZoomClick(...args: unknown[]): unknown + onDataClick(...args: unknown[]): unknown + fieldName?: string + fieldType?: string + fieldSpec?: any + value?: any + errors?: unknown[] + onExpressionClick?(...args: unknown[]): unknown +}; + +export default class SpecProperty extends React.Component { static defaultProps = { errors: {}, } @@ -31,17 +30,16 @@ export default class SpecProperty extends React.Component { fieldSpec={this.props.fieldSpec} onZoomClick={this.props.onZoomClick} onDataClick={this.props.onDataClick} - value={this.props.value} onExpressionClick={this.props.onExpressionClick} /> - const error = errors[fieldType+"."+fieldName]; + const error = errors![fieldType+"."+fieldName as any] as any; return } diff --git a/src/components/_ZoomProperty.jsx b/src/components/_ZoomProperty.tsx similarity index 71% rename from src/components/_ZoomProperty.jsx rename to src/components/_ZoomProperty.tsx index 0101bb0da..54be2ab6d 100644 --- a/src/components/_ZoomProperty.jsx +++ b/src/components/_ZoomProperty.tsx @@ -1,5 +1,4 @@ import React from 'react' -import PropTypes from 'prop-types' import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js'; import {latest} from '@maplibre/maplibre-gl-style-spec' @@ -7,7 +6,6 @@ import InputButton from './InputButton' import InputSpec from './InputSpec' import InputNumber from './InputNumber' import InputSelect from './InputSelect' -import FieldDocLabel from './FieldDocLabel' import Block from './Block' import DeleteStopButton from './_DeleteStopButton' @@ -22,12 +20,12 @@ import sortNumerically from '../libs/sort-numerically' * * When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus. */ -function setStopRefs(props, state) { +function setStopRefs(props: ZoomPropertyProps, state: ZoomPropertyState) { // This is initialsed below only if required to improved performance. - let newRefs; + let newRefs: {[key: number]: string} = {}; - if(props.value && props.value.stops) { - props.value.stops.forEach((val, idx) => { + if(props.value && (props.value as ZoomWithStops).stops) { + (props.value as ZoomWithStops).stops.forEach((_val, idx: number) => { if(!state.refs.hasOwnProperty(idx)) { if(!newRefs) { newRefs = {...state}; @@ -40,32 +38,39 @@ function setStopRefs(props, state) { return newRefs; } +type ZoomWithStops = { + stops: [number | undefined, number][] + base?: number +} + -export default class ZoomProperty extends React.Component { - static propTypes = { - onChange: PropTypes.func, - onDeleteStop: PropTypes.func, - onAddStop: PropTypes.func, - onExpressionClick: PropTypes.func, - fieldType: PropTypes.string, - fieldName: PropTypes.string, - fieldSpec: PropTypes.object, - errors: PropTypes.object, - value: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.string, - PropTypes.number, - PropTypes.bool, - PropTypes.array - ]), +type ZoomPropertyProps = { + onChange?(...args: unknown[]): unknown + onChangeToDataFunction?(...args: unknown[]): unknown + onDeleteStop?(...args: unknown[]): unknown + onAddStop?(...args: unknown[]): unknown + onExpressionClick?(...args: unknown[]): unknown + fieldType?: string + fieldName: string + fieldSpec?: { + "property-type"?: string + "function-type"?: string } + errors?: object + value?: ZoomWithStops +}; +type ZoomPropertyState = { + refs: {[key: number]: string} +} + +export default class ZoomProperty extends React.Component { static defaultProps = { errors: {}, } state = { - refs: {} + refs: {} as {[key: number]: string} } componentDidMount() { @@ -78,7 +83,7 @@ export default class ZoomProperty extends React.Component { } } - static getDerivedStateFromProps(props, state) { + static getDerivedStateFromProps(props: ZoomPropertyProps, state: ZoomPropertyState) { const newRefs = setStopRefs(props, state); if(newRefs) { return { @@ -89,7 +94,7 @@ export default class ZoomProperty extends React.Component { } // Order the stops altering the refs to reflect their new position. - orderStopsByZoom(stops) { + orderStopsByZoom(stops: ZoomWithStops["stops"]) { const mappedWithRef = stops .map((stop, idx) => { return { @@ -98,10 +103,10 @@ export default class ZoomProperty extends React.Component { } }) // Sort by zoom - .sort((a, b) => sortNumerically(a.data[0], b.data[0])); + .sort((a, b) => sortNumerically(a.data[0]!, b.data[0]!)); // Fetch the new position of the stops - const newRefs = {}; + const newRefs: {[key:number]: string} = {}; mappedWithRef .forEach((stop, idx) =>{ newRefs[idx] = stop.ref; @@ -114,20 +119,20 @@ export default class ZoomProperty extends React.Component { return mappedWithRef.map((item) => item.data); } - changeZoomStop(changeIdx, stopData, value) { - const stops = this.props.value.stops.slice(0); + changeZoomStop(changeIdx: number, stopData: number | undefined, value: number) { + const stops = (this.props.value as ZoomWithStops).stops.slice(0); stops[changeIdx] = [stopData, value]; const orderedStops = this.orderStopsByZoom(stops); const changedValue = { - ...this.props.value, + ...this.props.value as ZoomWithStops, stops: orderedStops } - this.props.onChange(this.props.fieldName, changedValue) + this.props.onChange!(this.props.fieldName, changedValue) } - changeBase(newValue) { + changeBase(newValue: number | undefined) { const changedValue = { ...this.props.value, base: newValue @@ -136,33 +141,21 @@ export default class ZoomProperty extends React.Component { if (changedValue.base === undefined) { delete changedValue["base"]; } - this.props.onChange(this.props.fieldName, changedValue) + this.props.onChange!(this.props.fieldName, changedValue) } - changeDataType = (type) => { - if (type !== "interpolate") { + changeDataType = (type: string) => { + if (type !== "interpolate" && this.props.onChangeToDataFunction) { this.props.onChangeToDataFunction(type); } } render() { - const {fieldName, fieldType, errors} = this.props; - - const zoomFields = this.props.value.stops.map((stop, idx) => { + const zoomFields = this.props.value?.stops.map((stop, idx) => { const zoomLevel = stop[0] const key = this.state.refs[idx]; const value = stop[1] - const deleteStopBtn= - - const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`; - const foundErrors = Object.entries(errors).filter(([key, error]) => { - return key.startsWith(errorKeyStart); - }); - - const message = foundErrors.map(([key, error]) => { - return error.message; - }).join(""); - const error = message ? {message} : undefined; + const deleteStopBtn= return this.changeZoomStop(idx, zoomLevel, newValue)} + onChange={(_, newValue) => this.changeZoomStop(idx, zoomLevel, newValue as number)} /> @@ -204,7 +197,7 @@ export default class ZoomProperty extends React.Component { value={"interpolate"} onChange={propVal => this.changeDataType(propVal)} title={"Select a type of data scale (default is 'categorical')."} - options={this.getDataFunctionTypes(this.props.fieldSpec)} + options={this.getDataFunctionTypes(this.props.fieldSpec!)} />
    @@ -215,8 +208,8 @@ export default class ZoomProperty extends React.Component { this.changeBase(newValue)} + value={this.props.value?.base} + onChange={(_, newValue) => this.changeBase(newValue as number | undefined)} /> @@ -226,7 +219,7 @@ export default class ZoomProperty extends React.Component { Zoom - Output value + Output value @@ -237,7 +230,7 @@ export default class ZoomProperty extends React.Component {
    @@ -245,7 +238,7 @@ export default class ZoomProperty extends React.Component { @@ -257,7 +250,10 @@ export default class ZoomProperty extends React.Component {
    } - getDataFunctionTypes(fieldSpec) { + getDataFunctionTypes(fieldSpec: { + "property-type"?: string + "function-type"?: string + }) { if (fieldSpec['property-type'] === 'data-driven') { return ["interpolate", "categorical", "interval", "exponential", "identity"]; } @@ -265,5 +261,4 @@ export default class ZoomProperty extends React.Component { return ["interpolate"]; } } - } diff --git a/src/components/_labelFromFieldName.js b/src/components/_labelFromFieldName.ts similarity index 79% rename from src/components/_labelFromFieldName.js rename to src/components/_labelFromFieldName.ts index 666d833c8..d6e4a0642 100644 --- a/src/components/_labelFromFieldName.js +++ b/src/components/_labelFromFieldName.ts @@ -1,6 +1,6 @@ import capitalize from 'lodash.capitalize' -export default function labelFromFieldName(fieldName) { +export default function labelFromFieldName(fieldName: string) { let label; const parts = fieldName.split('-'); if (parts.length > 1) { diff --git a/src/libs/accessibility.ts b/src/libs/accessibility.ts index e2bc5b795..840916532 100644 --- a/src/libs/accessibility.ts +++ b/src/libs/accessibility.ts @@ -1,8 +1,8 @@ import throttle from 'lodash.throttle' -export default { - // Throttle for 3 seconds so when a user enables it they don't have to refresh the page. - reducedMotionEnabled: throttle(() => { - return window.matchMedia("(prefers-reduced-motion: reduce)").matches - }, 3000) -} +// Throttle for 3 seconds so when a user enables it they don't have to refresh the page. +const reducedMotionEnabled = throttle(() => { + return window.matchMedia("(prefers-reduced-motion: reduce)").matches +}, 3000) + +export { reducedMotionEnabled } diff --git a/src/util/format.js b/src/util/format.js deleted file mode 100644 index 44e494d66..000000000 --- a/src/util/format.js +++ /dev/null @@ -1,3 +0,0 @@ -export function formatLayerId (id) { - return id === "" ? "[empty_string]" : `'${id}'`; -} diff --git a/src/util/format.ts b/src/util/format.ts new file mode 100644 index 000000000..4d84da606 --- /dev/null +++ b/src/util/format.ts @@ -0,0 +1,3 @@ +export function formatLayerId (id: string | undefined) { + return id === "" ? "[empty_string]" : `'${id}'`; +} diff --git a/src/util/spec-helper.js b/src/util/spec-helper.ts similarity index 72% rename from src/util/spec-helper.js rename to src/util/spec-helper.ts index acf2779db..2574d28a2 100644 --- a/src/util/spec-helper.js +++ b/src/util/spec-helper.ts @@ -1,7 +1,7 @@ /** * If we don't have a default value just make one up */ -export function findDefaultFromSpec (spec) { +export function findDefaultFromSpec(spec: { type: 'string' | 'color' | 'boolean' | 'array', default?: any }) { if (spec.hasOwnProperty('default')) { return spec.default; }