mirror of
https://github.com/maputnik/editor.git
synced 2026-02-06 12:40:00 +00:00
Migrate more components
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -69,6 +69,7 @@
|
||||
"@storybook/theming": "^7.6.5",
|
||||
"@types/color": "^3.0.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/lodash.capitalize": "^4.2.9",
|
||||
"@types/lodash.isequal": "^4.5.8",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/react": "^16.14.52",
|
||||
@@ -4732,6 +4733,15 @@
|
||||
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash.capitalize": {
|
||||
"version": "4.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.capitalize/-/lodash.capitalize-4.2.9.tgz",
|
||||
"integrity": "sha512-SV1dav/WbuI816SVig4trFz8ID/m5maVzC8I/E/DejPvmlXvIhw7Y0GWxJ03UhU6qaZOj6qQnR1xxC0mP7ZXoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash.isequal": {
|
||||
"version": "4.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"@storybook/theming": "^7.6.5",
|
||||
"@types/color": "^3.0.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/lodash.capitalize": "^4.2.9",
|
||||
"@types/lodash.isequal": "^4.5.8",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/react": "^16.14.52",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import FieldDocLabel from './FieldDocLabel'
|
||||
import Doc from './Doc'
|
||||
|
||||
type FieldsetProps = {
|
||||
label: string,
|
||||
fieldSpec?: { doc?: string },
|
||||
action?: string,
|
||||
action?: ReactElement,
|
||||
};
|
||||
|
||||
type FieldsetState = {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import InputString from './InputString'
|
||||
import InputNumber from './InputNumber'
|
||||
|
||||
type FieldArrayProps = {
|
||||
export type FieldArrayProps = {
|
||||
value: string[]
|
||||
type?: string
|
||||
length?: number
|
||||
|
||||
@@ -5,7 +5,7 @@ import Autocomplete from 'react-autocomplete'
|
||||
|
||||
const MAX_HEIGHT = 140;
|
||||
|
||||
type InputAutocompleteProps = {
|
||||
export type InputAutocompleteProps = {
|
||||
value?: string
|
||||
options: any[]
|
||||
onChange(...args: unknown[]): unknown
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
type InputCheckboxProps = {
|
||||
export type InputCheckboxProps = {
|
||||
value?: boolean
|
||||
style?: object
|
||||
onChange(...args: unknown[]): unknown
|
||||
|
||||
@@ -9,7 +9,7 @@ function formatColor(color: ColorResult): string {
|
||||
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`
|
||||
}
|
||||
|
||||
type InputColorProps = {
|
||||
export type InputColorProps = {
|
||||
onChange(...args: unknown[]): unknown
|
||||
name?: string
|
||||
value?: string
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
import {MdDelete} from 'react-icons/md'
|
||||
|
||||
import InputString from './InputString'
|
||||
import InputNumber from './InputNumber'
|
||||
import InputButton from './InputButton'
|
||||
import {MdDelete} from 'react-icons/md'
|
||||
import FieldDocLabel from './FieldDocLabel'
|
||||
import InputEnum from './InputEnum'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
import InputUrl from './InputUrl'
|
||||
|
||||
|
||||
export default class FieldDynamicArray extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.array,
|
||||
type: PropTypes.string,
|
||||
default: PropTypes.array,
|
||||
onChange: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
fieldSpec: PropTypes.object,
|
||||
'aria-label': PropTypes.string,
|
||||
export type FieldDynamicArrayProps = {
|
||||
value?: (string | number)[]
|
||||
type?: 'url' | 'number' | 'enum'
|
||||
default?: (string | number)[]
|
||||
onChange?(...args: unknown[]): unknown
|
||||
style?: object
|
||||
fieldSpec?: {
|
||||
values?: any
|
||||
}
|
||||
'aria-label'?: string
|
||||
label: string
|
||||
};
|
||||
|
||||
changeValue(idx, newValue) {
|
||||
|
||||
export default class FieldDynamicArray extends React.Component<FieldDynamicArrayProps> {
|
||||
changeValue(idx: number, newValue: string | number) {
|
||||
const values = this.values.slice(0)
|
||||
values[idx] = newValue
|
||||
this.props.onChange(values)
|
||||
if (this.props.onChange) this.props.onChange(values)
|
||||
}
|
||||
|
||||
get values() {
|
||||
@@ -41,20 +45,20 @@ export default class FieldDynamicArray extends React.Component {
|
||||
}
|
||||
else if (this.props.type === 'enum') {
|
||||
const {fieldSpec} = this.props;
|
||||
const defaultValue = Object.keys(fieldSpec.values)[0];
|
||||
const defaultValue = Object.keys(fieldSpec!.values)[0];
|
||||
values.push(defaultValue);
|
||||
} else {
|
||||
values.push("")
|
||||
}
|
||||
|
||||
this.props.onChange(values)
|
||||
if (this.props.onChange) this.props.onChange(values)
|
||||
}
|
||||
|
||||
deleteValue(valueIdx) {
|
||||
deleteValue(valueIdx: number) {
|
||||
const values = this.values.slice(0)
|
||||
values.splice(valueIdx, 1)
|
||||
|
||||
this.props.onChange(values.length > 0 ? values : undefined);
|
||||
if (this.props.onChange) this.props.onChange(values.length > 0 ? values : undefined);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -63,30 +67,30 @@ export default class FieldDynamicArray extends React.Component {
|
||||
let input;
|
||||
if(this.props.type === 'url') {
|
||||
input = <InputUrl
|
||||
value={v}
|
||||
value={v as string}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
}
|
||||
else if (this.props.type === 'number') {
|
||||
input = <InputNumber
|
||||
value={v}
|
||||
value={v as number}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
}
|
||||
else if (this.props.type === 'enum') {
|
||||
const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)]);
|
||||
const options = Object.keys(this.props.fieldSpec?.values).map(v => [v, capitalize(v)]);
|
||||
input = <InputEnum
|
||||
options={options}
|
||||
value={v}
|
||||
value={v as string}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
}
|
||||
else {
|
||||
input = <InputString
|
||||
value={v}
|
||||
value={v as string}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
@@ -120,11 +124,11 @@ export default class FieldDynamicArray extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteValueInputButton extends React.Component {
|
||||
static propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
type DeleteValueInputButtonProps = {
|
||||
onClick?(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
class DeleteValueInputButton extends React.Component<DeleteValueInputButtonProps> {
|
||||
render() {
|
||||
return <InputButton
|
||||
className="maputnik-delete-stop"
|
||||
@@ -133,7 +137,7 @@ class DeleteValueInputButton extends React.Component {
|
||||
>
|
||||
<FieldDocLabel
|
||||
label={<MdDelete />}
|
||||
doc={"Remove array item."}
|
||||
fieldSpec={{doc:" Remove array item."}}
|
||||
/>
|
||||
</InputButton>
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export type InputEnumProps = {
|
||||
value?: string
|
||||
style?: object
|
||||
default?: string
|
||||
name: string
|
||||
name?: string
|
||||
onChange(...args: unknown[]): unknown
|
||||
options: any[]
|
||||
'aria-label'?: string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import InputAutocomplete from './InputAutocomplete'
|
||||
|
||||
type FieldFontProps = {
|
||||
export type FieldFontProps = {
|
||||
name: string
|
||||
value?: string[]
|
||||
default?: string[]
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type InputMultiInputProps = {
|
||||
name: string
|
||||
name?: string
|
||||
value: string
|
||||
options: any[]
|
||||
onChange(...args: unknown[]): unknown
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { BaseSyntheticEvent } from 'react'
|
||||
|
||||
let IDX = 0;
|
||||
|
||||
type InputNumberProps = {
|
||||
export type InputNumberProps = {
|
||||
value?: number
|
||||
default?: number
|
||||
min?: number
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import InputColor from './InputColor'
|
||||
import InputNumber from './InputNumber'
|
||||
import InputCheckbox from './InputCheckbox'
|
||||
import InputString from './InputString'
|
||||
import InputSelect from './InputSelect'
|
||||
import InputMultiInput from './InputMultiInput'
|
||||
import InputArray from './InputArray'
|
||||
import InputDynamicArray from './InputDynamicArray'
|
||||
import InputFont from './InputFont'
|
||||
import InputAutocomplete from './InputAutocomplete'
|
||||
import InputEnum from './InputEnum'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
|
||||
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
||||
|
||||
function labelFromFieldName(fieldName) {
|
||||
let label = fieldName.split('-').slice(1).join(' ')
|
||||
if(label.length > 0) {
|
||||
label = label.charAt(0).toUpperCase() + label.slice(1);
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
function optionsLabelLength(options) {
|
||||
let sum = 0;
|
||||
options.forEach(([_, label]) => {
|
||||
sum += label.length
|
||||
})
|
||||
return sum
|
||||
}
|
||||
|
||||
/** Display any field from the Maplibre GL style spec and
|
||||
* choose the correct field component based on the @{fieldSpec}
|
||||
* to display @{value}. */
|
||||
export default class SpecField extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
fieldName: PropTypes.string.isRequired,
|
||||
fieldSpec: PropTypes.object.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.array,
|
||||
PropTypes.bool
|
||||
]),
|
||||
/** Override the style of the field */
|
||||
style: PropTypes.object,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
|
||||
render() {
|
||||
const commonProps = {
|
||||
error: this.props.error,
|
||||
fieldSpec: this.props.fieldSpec,
|
||||
label: this.props.label,
|
||||
action: this.props.action,
|
||||
style: this.props.style,
|
||||
value: this.props.value,
|
||||
default: this.props.fieldSpec.default,
|
||||
name: this.props.fieldName,
|
||||
onChange: newValue => this.props.onChange(this.props.fieldName, newValue),
|
||||
'aria-label': this.props['aria-label'],
|
||||
}
|
||||
|
||||
function childNodes() {
|
||||
switch(this.props.fieldSpec.type) {
|
||||
case 'number': return (
|
||||
<InputNumber
|
||||
{...commonProps}
|
||||
min={this.props.fieldSpec.minimum}
|
||||
max={this.props.fieldSpec.maximum}
|
||||
/>
|
||||
)
|
||||
case 'enum':
|
||||
const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)])
|
||||
|
||||
return <InputEnum
|
||||
{...commonProps}
|
||||
options={options}
|
||||
/>
|
||||
case 'resolvedImage':
|
||||
case 'formatted':
|
||||
case 'string':
|
||||
if (iconProperties.indexOf(this.props.fieldName) >= 0) {
|
||||
const options = this.props.fieldSpec.values || [];
|
||||
return <InputAutocomplete
|
||||
{...commonProps}
|
||||
options={options.map(f => [f, f])}
|
||||
/>
|
||||
} else {
|
||||
return <InputString
|
||||
{...commonProps}
|
||||
/>
|
||||
}
|
||||
case 'color': return (
|
||||
<InputColor
|
||||
{...commonProps}
|
||||
/>
|
||||
)
|
||||
case 'boolean': return (
|
||||
<InputCheckbox
|
||||
{...commonProps}
|
||||
/>
|
||||
)
|
||||
case 'array':
|
||||
if(this.props.fieldName === 'text-font') {
|
||||
return <InputFont
|
||||
{...commonProps}
|
||||
fonts={this.props.fieldSpec.values}
|
||||
/>
|
||||
} else {
|
||||
if (this.props.fieldSpec.length) {
|
||||
return <InputArray
|
||||
{...commonProps}
|
||||
type={this.props.fieldSpec.value}
|
||||
length={this.props.fieldSpec.length}
|
||||
/>
|
||||
} else {
|
||||
return <InputDynamicArray
|
||||
{...commonProps}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
type={this.props.fieldSpec.value}
|
||||
/>
|
||||
}
|
||||
}
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-wd-key={"spec-field:"+this.props.fieldName}>
|
||||
{childNodes.call(this)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
126
src/components/InputSpec.tsx
Normal file
126
src/components/InputSpec.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
|
||||
import InputColor, { InputColorProps } from './InputColor'
|
||||
import InputNumber, { InputNumberProps } from './InputNumber'
|
||||
import InputCheckbox, { InputCheckboxProps } from './InputCheckbox'
|
||||
import InputString, { InputStringProps } from './InputString'
|
||||
import InputArray, { FieldArrayProps } from './InputArray'
|
||||
import InputDynamicArray, { FieldDynamicArrayProps } from './InputDynamicArray'
|
||||
import InputFont, { FieldFontProps } from './InputFont'
|
||||
import InputAutocomplete, { InputAutocompleteProps } from './InputAutocomplete'
|
||||
import InputEnum, { InputEnumProps } from './InputEnum'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
|
||||
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
||||
|
||||
export type SpecFieldProps = {
|
||||
onChange(...args: unknown[]): unknown
|
||||
fieldName: string
|
||||
fieldSpec: {
|
||||
default?: unknown
|
||||
type: 'number' | 'enum' | 'resolvedImage' | 'formatted' | 'string' | 'color' | 'boolean' | 'array'
|
||||
minimum?: number
|
||||
maximum?: number
|
||||
values?: unknown[]
|
||||
length?: number
|
||||
value?: string
|
||||
}
|
||||
value?: string | number | unknown[] | boolean
|
||||
/** Override the style of the field */
|
||||
style?: object
|
||||
'aria-label'?: string
|
||||
error: unknown[]
|
||||
label: string
|
||||
action: ReactElement
|
||||
};
|
||||
|
||||
/** Display any field from the Maplibre GL style spec and
|
||||
* choose the correct field component based on the @{fieldSpec}
|
||||
* to display @{value}. */
|
||||
export default class SpecField extends React.Component<SpecFieldProps> {
|
||||
|
||||
childNodes() {
|
||||
const commonProps = {
|
||||
error: this.props.error,
|
||||
fieldSpec: this.props.fieldSpec,
|
||||
label: this.props.label,
|
||||
action: this.props.action,
|
||||
style: this.props.style,
|
||||
value: this.props.value,
|
||||
default: this.props.fieldSpec.default,
|
||||
name: this.props.fieldName,
|
||||
onChange: (newValue: string) => this.props.onChange(this.props.fieldName, newValue),
|
||||
'aria-label': this.props['aria-label'],
|
||||
}
|
||||
switch(this.props.fieldSpec.type) {
|
||||
case 'number': return (
|
||||
<InputNumber
|
||||
{...commonProps as InputNumberProps}
|
||||
min={this.props.fieldSpec.minimum}
|
||||
max={this.props.fieldSpec.maximum}
|
||||
/>
|
||||
)
|
||||
case 'enum':
|
||||
const options = Object.keys(this.props.fieldSpec.values || []).map(v => [v, capitalize(v)])
|
||||
|
||||
return <InputEnum
|
||||
{...commonProps as Omit<InputEnumProps, "options">}
|
||||
options={options}
|
||||
/>
|
||||
case 'resolvedImage':
|
||||
case 'formatted':
|
||||
case 'string':
|
||||
if (iconProperties.indexOf(this.props.fieldName) >= 0) {
|
||||
const options = this.props.fieldSpec.values || [];
|
||||
return <InputAutocomplete
|
||||
{...commonProps as Omit<InputAutocompleteProps, "options">}
|
||||
options={options.map(f => [f, f])}
|
||||
/>
|
||||
} else {
|
||||
return <InputString
|
||||
{...commonProps as InputStringProps}
|
||||
/>
|
||||
}
|
||||
case 'color': return (
|
||||
<InputColor
|
||||
{...commonProps as InputColorProps}
|
||||
/>
|
||||
)
|
||||
case 'boolean': return (
|
||||
<InputCheckbox
|
||||
{...commonProps as InputCheckboxProps}
|
||||
/>
|
||||
)
|
||||
case 'array':
|
||||
if(this.props.fieldName === 'text-font') {
|
||||
return <InputFont
|
||||
{...commonProps as FieldFontProps}
|
||||
fonts={this.props.fieldSpec.values}
|
||||
/>
|
||||
} else {
|
||||
if (this.props.fieldSpec.length) {
|
||||
return <InputArray
|
||||
{...commonProps as FieldArrayProps}
|
||||
type={this.props.fieldSpec.value}
|
||||
length={this.props.fieldSpec.length}
|
||||
/>
|
||||
} else {
|
||||
return <InputDynamicArray
|
||||
{...commonProps as FieldDynamicArrayProps}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
type={this.props.fieldSpec.value as FieldDynamicArrayProps['type']}
|
||||
/>
|
||||
}
|
||||
}
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div data-wd-key={"spec-field:"+this.props.fieldName}>
|
||||
{this.childNodes()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export type InputStringProps = {
|
||||
style?: object
|
||||
default?: string
|
||||
onChange?(...args: unknown[]): unknown
|
||||
onInput(...args: unknown[]): unknown
|
||||
onInput?(...args: unknown[]): unknown
|
||||
multi?: boolean
|
||||
required?: boolean
|
||||
disabled?: boolean
|
||||
@@ -77,7 +77,7 @@ export default class InputString extends React.Component<InputStringProps, Input
|
||||
editing: true,
|
||||
value: e.target.value
|
||||
}, () => {
|
||||
this.props.onInput(this.state.value);
|
||||
if (this.props.onInput) this.props.onInput(this.state.value);
|
||||
});
|
||||
},
|
||||
onBlur: () => {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Block from './Block'
|
||||
import InputSpec from './InputSpec'
|
||||
import InputSpec, { SpecFieldProps as InputFieldSpecProps } from './InputSpec'
|
||||
import Fieldset from './Fieldset'
|
||||
|
||||
|
||||
const typeMap = {
|
||||
color: () => Block,
|
||||
enum: ({fieldSpec}) => (Object.keys(fieldSpec.values).length <= 3 ? Fieldset : Block),
|
||||
enum: ({fieldSpec}: any) => (Object.keys(fieldSpec.values).length <= 3 ? Fieldset : Block),
|
||||
boolean: () => Block,
|
||||
array: () => Fieldset,
|
||||
resolvedImage: () => Block,
|
||||
@@ -16,12 +15,11 @@ const typeMap = {
|
||||
formatted: () => Block,
|
||||
};
|
||||
|
||||
export default class SpecField extends React.Component {
|
||||
static propTypes = {
|
||||
...InputSpec.propTypes,
|
||||
name: PropTypes.string,
|
||||
}
|
||||
type SpecFieldProps = InputFieldSpecProps & {
|
||||
name?: string
|
||||
};
|
||||
|
||||
export default class SpecField extends React.Component<SpecFieldProps> {
|
||||
render() {
|
||||
const {props} = this;
|
||||
|
||||
Reference in New Issue
Block a user