Compare commits

..

5 Commits

Author SHA1 Message Date
HarelM c7c1139e87 More typescript migration 2023-12-21 22:23:31 +02:00
HarelM ab102dccd7 Add a test to make sure this functionality doesn't get broken 2023-12-21 22:00:16 +02:00
HarelM 901be16ae4 Migrate more components, fix spec documentation missing information 2023-12-21 20:44:39 +02:00
HarelM df65cc3eff Migrate more components 2023-12-21 15:16:41 +02:00
HarelM c633368a9a Migrate more components 2023-12-21 14:53:21 +02:00
45 changed files with 530 additions and 481 deletions
+7
View File
@@ -77,11 +77,18 @@ describe("modals", () => {
}); });
it("name", () => { it("name", () => {
when.click("field-doc-button-Name");
should.containText("spec-field-doc", "name for the style");
});
it("show name specifications", () => {
when.setValue(get.dataAttribute("modal:settings.name"), "foobar"); when.setValue(get.dataAttribute("modal:settings.name"), "foobar");
when.click("modal:settings.owner"); when.click("modal:settings.owner");
should.equalStyleStore((obj) => obj.name, "foobar"); should.equalStyleStore((obj) => obj.name, "foobar");
}); });
it("owner", () => { it("owner", () => {
when.setValue(get.dataAttribute("modal:settings.owner"), "foobar"); when.setValue(get.dataAttribute("modal:settings.owner"), "foobar");
when.click("modal:settings.name"); when.click("modal:settings.name");
+39
View File
@@ -67,8 +67,10 @@
"@storybook/react": "^7.6.5", "@storybook/react": "^7.6.5",
"@storybook/react-vite": "^7.6.5", "@storybook/react-vite": "^7.6.5",
"@storybook/theming": "^7.6.5", "@storybook/theming": "^7.6.5",
"@types/codemirror": "^5.60.15",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/lodash.capitalize": "^4.2.9",
"@types/lodash.isequal": "^4.5.8", "@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.9", "@types/lodash.throttle": "^4.1.9",
"@types/react": "^16.14.52", "@types/react": "^16.14.52",
@@ -76,6 +78,7 @@
"@types/react-autocomplete": "^1.8.9", "@types/react-autocomplete": "^1.8.9",
"@types/react-color": "^3.0.10", "@types/react-color": "^3.0.10",
"@types/react-dom": "^16.9.24", "@types/react-dom": "^16.9.24",
"@types/react-file-reader-input": "^2.0.4",
"@types/react-icon-base": "^2.1.6", "@types/react-icon-base": "^2.1.6",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"@vitejs/plugin-react": "^4.2.0", "@vitejs/plugin-react": "^4.2.0",
@@ -4588,6 +4591,15 @@
"@types/chai": "*" "@types/chai": "*"
} }
}, },
"node_modules/@types/codemirror": {
"version": "5.60.15",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.15.tgz",
"integrity": "sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==",
"dev": true,
"dependencies": {
"@types/tern": "*"
}
},
"node_modules/@types/color": { "node_modules/@types/color": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz",
@@ -4732,6 +4744,15 @@
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
"dev": true "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": { "node_modules/@types/lodash.isequal": {
"version": "4.5.8", "version": "4.5.8",
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz", "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz",
@@ -4897,6 +4918,15 @@
"@types/react": "^16" "@types/react": "^16"
} }
}, },
"node_modules/@types/react-file-reader-input": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/react-file-reader-input/-/react-file-reader-input-2.0.4.tgz",
"integrity": "sha512-Jqrfn+w42j8t8Q3npMXXKPdk+reIM0UHLKVc3ykrA7q7bN3Z62SGhsClZX0+Edlqm66lcKwmDQl+WMm+Xor7Xg==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-icon-base": { "node_modules/@types/react-icon-base": {
"version": "2.1.6", "version": "2.1.6",
"resolved": "https://registry.npmjs.org/@types/react-icon-base/-/react-icon-base-2.1.6.tgz", "resolved": "https://registry.npmjs.org/@types/react-icon-base/-/react-icon-base-2.1.6.tgz",
@@ -4966,6 +4996,15 @@
"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==",
"dev": true "dev": true
}, },
"node_modules/@types/tern": {
"version": "0.23.9",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
"dev": true,
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/@types/uuid": { "node_modules/@types/uuid": {
"version": "9.0.7", "version": "9.0.7",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz",
+3
View File
@@ -96,8 +96,10 @@
"@storybook/react": "^7.6.5", "@storybook/react": "^7.6.5",
"@storybook/react-vite": "^7.6.5", "@storybook/react-vite": "^7.6.5",
"@storybook/theming": "^7.6.5", "@storybook/theming": "^7.6.5",
"@types/codemirror": "^5.60.15",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/lodash.capitalize": "^4.2.9",
"@types/lodash.isequal": "^4.5.8", "@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.9", "@types/lodash.throttle": "^4.1.9",
"@types/react": "^16.14.52", "@types/react": "^16.14.52",
@@ -105,6 +107,7 @@
"@types/react-autocomplete": "^1.8.9", "@types/react-autocomplete": "^1.8.9",
"@types/react-color": "^3.0.10", "@types/react-color": "^3.0.10",
"@types/react-dom": "^16.9.24", "@types/react-dom": "^16.9.24",
"@types/react-file-reader-input": "^2.0.4",
"@types/react-icon-base": "^2.1.6", "@types/react-icon-base": "^2.1.6",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"@vitejs/plugin-react": "^4.2.0", "@vitejs/plugin-react": "^4.2.0",
+1 -1
View File
@@ -39,7 +39,7 @@ export default class Doc extends React.Component<DocProps> {
<> <>
{doc && {doc &&
<div className="SpecDoc"> <div className="SpecDoc">
<div className="SpecDoc__doc">{doc}</div> <div className="SpecDoc__doc" data-wd-key='spec-field-doc'>{doc}</div>
{renderValues && {renderValues &&
<ul className="SpecDoc__values"> <ul className="SpecDoc__values">
{Object.entries(values).map(([key, value]) => { {Object.entries(values).map(([key, value]) => {
-21
View File
@@ -1,21 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputArray from './InputArray'
import Fieldset from './Fieldset'
export default class FieldArray extends React.Component {
static propTypes = {
...InputArray.propTypes,
name: PropTypes.string,
}
render() {
const {props} = this;
return <Fieldset label={props.label}>
<InputArray {...props} />
</Fieldset>
}
}
+19
View File
@@ -0,0 +1,19 @@
import React from 'react'
import InputArray, { FieldArrayProps as InputArrayProps } from './InputArray'
import Fieldset from './Fieldset'
type FieldArrayProps = InputArrayProps & {
name?: string
fieldSpec?: {
doc: string
}
};
export default class FieldArray extends React.Component<FieldArrayProps> {
render() {
return <Fieldset label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputArray {...this.props} />
</Fieldset>
}
}
-20
View File
@@ -1,20 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputColor from './InputColor'
export default class FieldColor extends React.Component {
static propTypes = {
...InputColor.propTypes,
}
render() {
const {props} = this;
return <Block label={props.label}>
<InputColor {...props} />
</Block>
}
}
+21
View File
@@ -0,0 +1,21 @@
import React from 'react'
import Block from './Block'
import InputColor, {InputColorProps} from './InputColor'
type FieldColorProps = InputColorProps & {
label?: string
fieldSpec?: {
doc: string
}
};
export default class FieldColor extends React.Component<FieldColorProps> {
render() {
return <Block label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputColor {...this.props} />
</Block>
}
}
+1
View File
@@ -44,6 +44,7 @@ export default class FieldDocLabel extends React.Component<FieldDocLabelProps, F
aria-label={this.state.open ? "close property documentation" : "open property documentation"} aria-label={this.state.open ? "close property documentation" : "open property documentation"}
className={`maputnik-doc-button maputnik-doc-button--${this.state.open ? 'open' : 'closed'}`} className={`maputnik-doc-button maputnik-doc-button--${this.state.open ? 'open' : 'closed'}`}
onClick={() => this.onToggleDoc(!this.state.open)} onClick={() => this.onToggleDoc(!this.state.open)}
data-wd-key={'field-doc-button-'+label}
> >
{this.state.open ? <MdHighlightOff /> : <MdInfoOutline />} {this.state.open ? <MdHighlightOff /> : <MdInfoOutline />}
</button> </button>
+8 -5
View File
@@ -3,15 +3,18 @@ import InputEnum, {InputEnumProps} from './InputEnum'
import Fieldset from './Fieldset'; import Fieldset from './Fieldset';
type FieldEnumProps = InputEnumProps & { label : string }; type FieldEnumProps = InputEnumProps & {
label?: string;
fieldSpec?: {
doc: string
}
};
export default class FieldEnum extends React.Component<FieldEnumProps> { export default class FieldEnum extends React.Component<FieldEnumProps> {
render() { render() {
const {props} = this; return <Fieldset label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputEnum {...this.props} />
return <Fieldset label={props.label}>
<InputEnum {...props} />
</Fieldset> </Fieldset>
} }
} }
-16
View File
@@ -1,16 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import InputJson from './InputJson'
export default class FieldJson extends React.Component {
static propTypes = {
...InputJson.propTypes,
}
render() {
const {props} = this;
return <InputJson {...props} />
}
}
+13
View File
@@ -0,0 +1,13 @@
import React from 'react'
import InputJson, {InputJsonProps} from './InputJson'
type FieldJsonProps = InputJsonProps & {};
export default class FieldJson extends React.Component<FieldJsonProps> {
render() {
return <InputJson {...this.props} />
}
}
@@ -1,17 +1,16 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
export default class FieldMaxZoom extends React.Component { type FieldMaxZoomProps = {
static propTypes = { value?: number
value: PropTypes.number, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[]
error: PropTypes.object, };
}
export default class FieldMaxZoom extends React.Component<FieldMaxZoomProps> {
render() { render() {
return <Block label={"Max Zoom"} fieldSpec={latest.layer.maxzoom} return <Block label={"Max Zoom"} fieldSpec={latest.layer.maxzoom}
error={this.props.error} error={this.props.error}
@@ -1,17 +1,16 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
export default class FieldMinZoom extends React.Component { type FieldMinZoomProps = {
static propTypes = { value?: number
value: PropTypes.number, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[]
error: PropTypes.object, };
}
export default class FieldMinZoom extends React.Component<FieldMinZoomProps> {
render() { render() {
return <Block label={"Min Zoom"} fieldSpec={latest.layer.minzoom} return <Block label={"Min Zoom"} fieldSpec={latest.layer.minzoom}
error={this.props.error} error={this.props.error}
-21
View File
@@ -1,21 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputMultiInput from './InputMultiInput'
import Fieldset from './Fieldset'
export default class FieldMultiInput extends React.Component {
static propTypes = {
...InputMultiInput.propTypes,
}
render() {
const {props} = this;
return <Fieldset label={props.label}>
<InputMultiInput {...props} />
</Fieldset>
}
}
+20
View File
@@ -0,0 +1,20 @@
import React from 'react'
import InputMultiInput, {InputMultiInputProps} from './InputMultiInput'
import Fieldset from './Fieldset'
type FieldMultiInputProps = InputMultiInputProps & {
label?: string
};
export default class FieldMultiInput extends React.Component<FieldMultiInputProps> {
render() {
const {props} = this;
return <Fieldset label={props.label}>
<InputMultiInput {...props} />
</Fieldset>
}
}
-19
View File
@@ -1,19 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import InputNumber from './InputNumber'
import Block from './Block'
export default class FieldNumber extends React.Component {
static propTypes = {
...InputNumber.propTypes,
}
render() {
const {props} = this;
return <Block label={props.label}>
<InputNumber {...props} />
</Block>
}
}
+20
View File
@@ -0,0 +1,20 @@
import React from 'react'
import InputNumber, {InputNumberProps} from './InputNumber'
import Block from './Block'
type FieldNumberProps = InputNumberProps & {
label?: string
fieldSpec?: {
doc: string
}
};
export default class FieldNumber extends React.Component<FieldNumberProps> {
render() {
return <Block label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputNumber {...this.props} />
</Block>
}
}
-20
View File
@@ -1,20 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputSelect from './InputSelect'
export default class FieldSelect extends React.Component {
static propTypes = {
...InputSelect.propTypes,
}
render() {
const {props} = this;
return <Block label={props.label}>
<InputSelect {...props}/>
</Block>
}
}
+22
View File
@@ -0,0 +1,22 @@
import React from 'react'
import Block from './Block'
import InputSelect, {InputSelectProps} from './InputSelect'
type FieldSelectProps = InputSelectProps & {
label?: string
fieldSpec?: {
doc: string
}
};
export default class FieldSelect extends React.Component<FieldSelectProps> {
render() {
return <Block label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputSelect {...this.props}/>
</Block>
}
}
+5 -4
View File
@@ -5,14 +5,15 @@ import InputString, {InputStringProps} from './InputString'
type FieldStringProps = InputStringProps & { type FieldStringProps = InputStringProps & {
name?: string name?: string
label?: string label?: string
fieldSpec?: {
doc: string
}
}; };
export default class FieldString extends React.Component<FieldStringProps> { export default class FieldString extends React.Component<FieldStringProps> {
render() { render() {
const {props} = this; return <Block label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputString {...this.props} />
return <Block label={props.label}>
<InputString {...props} />
</Block> </Block>
} }
} }
+5 -4
View File
@@ -5,16 +5,17 @@ import Block from './Block'
type FieldUrlProps = InputUrlProps & { type FieldUrlProps = InputUrlProps & {
label: string; label: string;
fieldSpec?: {
doc: string
}
}; };
export default class FieldUrl extends React.Component<FieldUrlProps> { export default class FieldUrl extends React.Component<FieldUrlProps> {
render () { render () {
const {props} = this;
return ( return (
<Block label={this.props.label}> <Block label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputUrl {...props} /> <InputUrl {...this.props} />
</Block> </Block>
); );
} }
+3 -3
View File
@@ -1,11 +1,11 @@
import React from 'react' import React, { ReactElement } from 'react'
import FieldDocLabel from './FieldDocLabel' import FieldDocLabel from './FieldDocLabel'
import Doc from './Doc' import Doc from './Doc'
type FieldsetProps = { type FieldsetProps = {
label: string, label?: string,
fieldSpec?: { doc?: string }, fieldSpec?: { doc?: string },
action?: string, action?: ReactElement,
}; };
type FieldsetState = { type FieldsetState = {
+2 -2
View File
@@ -2,8 +2,8 @@ import React from 'react'
import InputString from './InputString' import InputString from './InputString'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
type FieldArrayProps = { export type FieldArrayProps = {
value: string[] value: string[] | number[]
type?: string type?: string
length?: number length?: number
default?: string[] | number[] default?: string[] | number[]
+1 -1
View File
@@ -5,7 +5,7 @@ import Autocomplete from 'react-autocomplete'
const MAX_HEIGHT = 140; const MAX_HEIGHT = 140;
type InputAutocompleteProps = { export type InputAutocompleteProps = {
value?: string value?: string
options: any[] options: any[]
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
type InputCheckboxProps = { export type InputCheckboxProps = {
value?: boolean value?: boolean
style?: object style?: object
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
+1 -1
View File
@@ -9,7 +9,7 @@ function formatColor(color: ColorResult): string {
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})` return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`
} }
type InputColorProps = { export type InputColorProps = {
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
name?: string name?: string
value?: string value?: string
@@ -1,30 +1,34 @@
import React from 'react' 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 InputString from './InputString'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
import InputButton from './InputButton' import InputButton from './InputButton'
import {MdDelete} from 'react-icons/md'
import FieldDocLabel from './FieldDocLabel' import FieldDocLabel from './FieldDocLabel'
import InputEnum from './InputEnum' import InputEnum from './InputEnum'
import capitalize from 'lodash.capitalize'
import InputUrl from './InputUrl' import InputUrl from './InputUrl'
export default class FieldDynamicArray extends React.Component { export type FieldDynamicArrayProps = {
static propTypes = { value?: (string | number)[]
value: PropTypes.array, type?: 'url' | 'number' | 'enum'
type: PropTypes.string, default?: (string | number)[]
default: PropTypes.array, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, style?: object
style: PropTypes.object, fieldSpec?: {
fieldSpec: PropTypes.object, values?: any
'aria-label': PropTypes.string,
} }
'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) const values = this.values.slice(0)
values[idx] = newValue values[idx] = newValue
this.props.onChange(values) if (this.props.onChange) this.props.onChange(values)
} }
get values() { get values() {
@@ -41,20 +45,20 @@ export default class FieldDynamicArray extends React.Component {
} }
else if (this.props.type === 'enum') { else if (this.props.type === 'enum') {
const {fieldSpec} = this.props; const {fieldSpec} = this.props;
const defaultValue = Object.keys(fieldSpec.values)[0]; const defaultValue = Object.keys(fieldSpec!.values)[0];
values.push(defaultValue); values.push(defaultValue);
} else { } else {
values.push("") 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) const values = this.values.slice(0)
values.splice(valueIdx, 1) 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() { render() {
@@ -63,30 +67,30 @@ export default class FieldDynamicArray extends React.Component {
let input; let input;
if(this.props.type === 'url') { if(this.props.type === 'url') {
input = <InputUrl input = <InputUrl
value={v} value={v as string}
onChange={this.changeValue.bind(this, i)} onChange={this.changeValue.bind(this, i)}
aria-label={this.props['aria-label'] || this.props.label} aria-label={this.props['aria-label'] || this.props.label}
/> />
} }
else if (this.props.type === 'number') { else if (this.props.type === 'number') {
input = <InputNumber input = <InputNumber
value={v} value={v as number}
onChange={this.changeValue.bind(this, i)} onChange={this.changeValue.bind(this, i)}
aria-label={this.props['aria-label'] || this.props.label} aria-label={this.props['aria-label'] || this.props.label}
/> />
} }
else if (this.props.type === 'enum') { 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 input = <InputEnum
options={options} options={options}
value={v} value={v as string}
onChange={this.changeValue.bind(this, i)} onChange={this.changeValue.bind(this, i)}
aria-label={this.props['aria-label'] || this.props.label} aria-label={this.props['aria-label'] || this.props.label}
/> />
} }
else { else {
input = <InputString input = <InputString
value={v} value={v as string}
onChange={this.changeValue.bind(this, i)} onChange={this.changeValue.bind(this, i)}
aria-label={this.props['aria-label'] || this.props.label} 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 { type DeleteValueInputButtonProps = {
static propTypes = { onClick?(...args: unknown[]): unknown
onClick: PropTypes.func, };
}
class DeleteValueInputButton extends React.Component<DeleteValueInputButtonProps> {
render() { render() {
return <InputButton return <InputButton
className="maputnik-delete-stop" className="maputnik-delete-stop"
@@ -133,7 +137,7 @@ class DeleteValueInputButton extends React.Component {
> >
<FieldDocLabel <FieldDocLabel
label={<MdDelete />} label={<MdDelete />}
doc={"Remove array item."} fieldSpec={{doc:" Remove array item."}}
/> />
</InputButton> </InputButton>
} }
+1 -1
View File
@@ -17,7 +17,7 @@ export type InputEnumProps = {
value?: string value?: string
style?: object style?: object
default?: string default?: string
name: string name?: string
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
options: any[] options: any[]
'aria-label'?: string 'aria-label'?: string
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import InputAutocomplete from './InputAutocomplete' import InputAutocomplete from './InputAutocomplete'
type FieldFontProps = { export type FieldFontProps = {
name: string name: string
value?: string[] value?: string[]
default?: string[] default?: string[]
@@ -1,47 +1,45 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'; import classnames from 'classnames';
import CodeMirror, { ModeSpec } from 'codemirror';
import Block from './Block'
import FieldString from './FieldString'
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript' import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint' import 'codemirror/addon/lint/lint'
import 'codemirror/addon/edit/matchbrackets' import 'codemirror/addon/edit/matchbrackets'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/addon/lint/lint.css' import 'codemirror/addon/lint/lint.css'
import jsonlint from 'jsonlint'
import stringifyPretty from 'json-stringify-pretty-compact' import stringifyPretty from 'json-stringify-pretty-compact'
import '../util/codemirror-mgl'; import '../util/codemirror-mgl';
export default class InputJson extends React.Component { export type InputJsonProps = {
static propTypes = { layer: any
layer: PropTypes.any.isRequired, maxHeight?: number
maxHeight: PropTypes.number, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, lineNumbers?: boolean
lineNumbers: PropTypes.bool, lineWrapping?: boolean
lineWrapping: PropTypes.bool, getValue(data: any): string
getValue: PropTypes.func, gutters?: string[]
gutters: PropTypes.array, className?: string
className: PropTypes.string, onFocus?(...args: unknown[]): unknown
onFocus: PropTypes.func, onBlur?(...args: unknown[]): unknown
onBlur: PropTypes.func, onJSONValid?(...args: unknown[]): unknown
onJSONValid: PropTypes.func, onJSONInvalid?(...args: unknown[]): unknown
onJSONInvalid: PropTypes.func, mode?: ModeSpec<any>
mode: PropTypes.object, lint?: boolean | object
lint: PropTypes.oneOfType([ };
PropTypes.bool,
PropTypes.object,
]),
}
type InputJsonState = {
isEditing: boolean
showMessage: boolean
prevValue: string
};
export default class InputJson extends React.Component<InputJsonProps, InputJsonState> {
static defaultProps = { static defaultProps = {
lineNumbers: true, lineNumbers: true,
lineWrapping: false, lineWrapping: false,
gutters: ["CodeMirror-lint-markers"], gutters: ["CodeMirror-lint-markers"],
getValue: (data) => { getValue: (data: any) => {
return stringifyPretty(data, {indent: 2, maxLength: 40}); return stringifyPretty(data, {indent: 2, maxLength: 40});
}, },
onFocus: () => {}, onFocus: () => {},
@@ -49,8 +47,12 @@ export default class InputJson extends React.Component {
onJSONInvalid: () => {}, onJSONInvalid: () => {},
onJSONValid: () => {}, onJSONValid: () => {},
} }
_keyEvent: string;
_doc: CodeMirror.Editor | undefined;
_el: HTMLDivElement | null = null;
_cancelNextChange: boolean = false;
constructor(props) { constructor(props: InputJsonProps) {
super(props); super(props);
this._keyEvent = "keyboard"; this._keyEvent = "keyboard";
this.state = { this.state = {
@@ -61,7 +63,7 @@ export default class InputJson extends React.Component {
} }
componentDidMount () { componentDidMount () {
this._doc = CodeMirror(this._el, { this._doc = CodeMirror(this._el!, {
value: this.props.getValue(this.props.layer), value: this.props.getValue(this.props.layer),
mode: this.props.mode || { mode: this.props.mode || {
name: "mgl", name: "mgl",
@@ -84,12 +86,12 @@ export default class InputJson extends React.Component {
this._doc.on('blur', this.onBlur); this._doc.on('blur', this.onBlur);
} }
onPointerDown = (cm, e) => { onPointerDown = () => {
this._keyEvent = "pointer"; this._keyEvent = "pointer";
} }
onFocus = (cm, e) => { onFocus = () => {
this.props.onFocus(); if (this.props.onFocus) this.props.onFocus();
this.setState({ this.setState({
isEditing: true, isEditing: true,
showMessage: (this._keyEvent === "keyboard"), showMessage: (this._keyEvent === "keyboard"),
@@ -98,7 +100,7 @@ export default class InputJson extends React.Component {
onBlur = () => { onBlur = () => {
this._keyEvent = "keyboard"; this._keyEvent = "keyboard";
this.props.onBlur(); if (this.props.onBlur) this.props.onBlur();
this.setState({ this.setState({
isEditing: false, isEditing: false,
showMessage: false, showMessage: false,
@@ -106,29 +108,29 @@ export default class InputJson extends React.Component {
} }
componentWillUnMount () { componentWillUnMount () {
this._doc.off('change', this.onChange); this._doc!.off('change', this.onChange);
this._doc.off('focus', this.onFocus); this._doc!.off('focus', this.onFocus);
this._doc.off('blur', this.onBlur); this._doc!.off('blur', this.onBlur);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: InputJsonProps) {
if (!this.state.isEditing && prevProps.layer !== this.props.layer) { if (!this.state.isEditing && prevProps.layer !== this.props.layer) {
this._cancelNextChange = true; this._cancelNextChange = true;
this._doc.setValue( this._doc!.setValue(
this.props.getValue(this.props.layer), this.props.getValue(this.props.layer),
) )
} }
} }
onChange = (e) => { onChange = (_e: unknown) => {
if (this._cancelNextChange) { if (this._cancelNextChange) {
this._cancelNextChange = false; this._cancelNextChange = false;
this.setState({ this.setState({
prevValue: this._doc.getValue(), prevValue: this._doc!.getValue(),
}) })
return; return;
} }
const newCode = this._doc.getValue(); const newCode = this._doc!.getValue();
if (this.state.prevValue !== newCode) { if (this.state.prevValue !== newCode) {
let parsedLayer, err; let parsedLayer, err;
@@ -139,12 +141,12 @@ export default class InputJson extends React.Component {
console.warn(_err) console.warn(_err)
} }
if (err) { if (err && this.props.onJSONInvalid) {
this.props.onJSONInvalid(); this.props.onJSONInvalid();
} }
else { else {
this.props.onChange(parsedLayer) if (this.props.onChange) this.props.onChange(parsedLayer)
this.props.onJSONValid(); if (this.props.onJSONValid) this.props.onJSONValid();
} }
} }
@@ -155,7 +157,7 @@ export default class InputJson extends React.Component {
render() { render() {
const {showMessage} = this.state; const {showMessage} = this.state;
const style = {}; const style = {} as {maxHeight?: number};
if (this.props.maxHeight) { if (this.props.maxHeight) {
style.maxHeight = this.props.maxHeight; style.maxHeight = this.props.maxHeight;
} }
+2 -2
View File
@@ -1,8 +1,8 @@
import React from 'react' import React from 'react'
import classnames from 'classnames' import classnames from 'classnames'
type InputMultiInputProps = { export type InputMultiInputProps = {
name: string name?: string
value: string value: string
options: any[] options: any[]
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
+1 -1
View File
@@ -2,7 +2,7 @@ import React, { BaseSyntheticEvent } from 'react'
let IDX = 0; let IDX = 0;
type InputNumberProps = { export type InputNumberProps = {
value?: number value?: number
default?: number default?: number
min?: number min?: number
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
type InputSelectProps = { export type InputSelectProps = {
value: string value: string
"data-wd-key"?: string "data-wd-key"?: string
options: [string, any][] | string[] options: [string, any][] | string[]
-139
View File
@@ -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
View 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>
);
}
}
+2 -2
View File
@@ -6,7 +6,7 @@ export type InputStringProps = {
style?: object style?: object
default?: string default?: string
onChange?(...args: unknown[]): unknown onChange?(...args: unknown[]): unknown
onInput(...args: unknown[]): unknown onInput?(...args: unknown[]): unknown
multi?: boolean multi?: boolean
required?: boolean required?: boolean
disabled?: boolean disabled?: boolean
@@ -77,7 +77,7 @@ export default class InputString extends React.Component<InputStringProps, Input
editing: true, editing: true,
value: e.target.value value: e.target.value
}, () => { }, () => {
this.props.onInput(this.state.value); if (this.props.onInput) this.props.onInput(this.state.value);
}); });
}, },
onBlur: () => { onBlur: () => {
+4 -2
View File
@@ -53,10 +53,12 @@ export type FieldUrlProps = {
style?: object style?: object
default?: string default?: string
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
onInput(...args: unknown[]): unknown onInput?(...args: unknown[]): unknown
multi?: boolean multi?: boolean
required?: boolean required?: boolean
'aria-label'?: string 'aria-label'?: string
type?: string
className?: string
}; };
type FieldUrlState = { type FieldUrlState = {
@@ -79,7 +81,7 @@ export default class FieldUrl extends React.Component<FieldUrlProps, FieldUrlSta
this.setState({ this.setState({
error: validate(url) error: validate(url)
}); });
this.props.onInput(url); if (this.props.onInput) this.props.onInput(url);
} }
onChange = (url: string) => { onChange = (url: string) => {
@@ -1,19 +1,19 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputButton from './InputButton' import InputButton from './InputButton'
import Modal from './Modal' import Modal from './Modal'
export default class ModalLoading extends React.Component { type ModalLoadingProps = {
static propTypes = { isOpen: boolean
isOpen: PropTypes.bool.isRequired, onCancel(...args: unknown[]): unknown
onCancel: PropTypes.func.isRequired, title: string
title: PropTypes.string.isRequired, message: React.ReactNode
message: PropTypes.node.isRequired, };
}
underlayOnClick(e) {
export default class ModalLoading extends React.Component<ModalLoadingProps> {
underlayOnClick(e: Event) {
// This stops click events falling through to underlying modals. // This stops click events falling through to underlying modals.
e.stopPropagation(); e.stopPropagation();
} }
@@ -24,9 +24,9 @@ export default class ModalLoading extends React.Component {
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
underlayClickExits={false} underlayClickExits={false}
underlayProps={{ underlayProps={{
onClick: (e) => underlayProps(e) // @ts-ignore
onClick: (e: Event) => underlayProps(e)
}} }}
closeable={false}
title={this.props.title} title={this.props.title}
onOpenToggle={() => this.props.onCancel()} onOpenToggle={() => this.props.onCancel()}
> >
@@ -1,25 +1,24 @@
import React from 'react' import React, { FormEvent } from 'react'
import PropTypes from 'prop-types' import {MdFileUpload} from 'react-icons/md'
import {MdAddCircleOutline} from 'react-icons/md'
import FileReaderInput, { Result } from 'react-file-reader-input'
import ModalLoading from './ModalLoading' import ModalLoading from './ModalLoading'
import Modal from './Modal' import Modal from './Modal'
import InputButton from './InputButton' import InputButton from './InputButton'
import FileReaderInput from 'react-file-reader-input'
import InputUrl from './InputUrl' import InputUrl from './InputUrl'
import {MdFileUpload} from 'react-icons/md'
import {MdAddCircleOutline} from 'react-icons/md'
import style from '../libs/style' import style from '../libs/style'
import publicStyles from '../config/styles.json' import publicStyles from '../config/styles.json'
class PublicStyle extends React.Component { type PublicStyleProps = {
static propTypes = { url: string
url: PropTypes.string.isRequired, thumbnailUrl: string
thumbnailUrl: PropTypes.string.isRequired, title: string
title: PropTypes.string.isRequired, onSelect(...args: unknown[]): unknown
onSelect: PropTypes.func.isRequired, };
}
class PublicStyle extends React.Component<PublicStyleProps> {
render() { render() {
return <div className="maputnik-public-style"> return <div className="maputnik-public-style">
<InputButton <InputButton
@@ -43,14 +42,21 @@ class PublicStyle extends React.Component {
} }
} }
export default class ModalOpen extends React.Component { type ModalOpenProps = {
static propTypes = { isOpen: boolean
isOpen: PropTypes.bool.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, onStyleOpen(...args: unknown[]): unknown
onStyleOpen: PropTypes.func.isRequired, };
}
constructor(props) { type ModalOpenState = {
styleUrl: string
error?: string | null
activeRequest?: any
activeRequestUrl?: string | null
};
export default class ModalOpen extends React.Component<ModalOpenProps, ModalOpenState> {
constructor(props: ModalOpenProps) {
super(props); super(props);
this.state = { this.state = {
styleUrl: "" styleUrl: ""
@@ -63,7 +69,7 @@ export default class ModalOpen extends React.Component {
}) })
} }
onCancelActiveRequest(e) { onCancelActiveRequest(e: Event) {
// Else the click propagates to the underlying modal // Else the click propagates to the underlying modal
if(e) e.stopPropagation(); if(e) e.stopPropagation();
@@ -76,12 +82,12 @@ export default class ModalOpen extends React.Component {
} }
} }
onStyleSelect = (styleUrl) => { onStyleSelect = (styleUrl: string) => {
this.clearError(); this.clearError();
let canceled; let canceled: boolean = false;
const activeRequest = fetch(styleUrl, { fetch(styleUrl, {
mode: 'cors', mode: 'cors',
credentials: "same-origin" credentials: "same-origin"
}) })
@@ -123,13 +129,13 @@ export default class ModalOpen extends React.Component {
}) })
} }
onSubmitUrl = (e) => { onSubmitUrl = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
this.onStyleSelect(this.state.styleUrl); this.onStyleSelect(this.state.styleUrl);
} }
onUpload = (_, files) => { onUpload = (_: any, files: Result[]) => {
const [e, file] = files[0]; const [_e, file] = files[0];
const reader = new FileReader(); const reader = new FileReader();
this.clearError(); this.clearError();
@@ -138,11 +144,11 @@ export default class ModalOpen extends React.Component {
reader.onload = e => { reader.onload = e => {
let mapStyle; let mapStyle;
try { try {
mapStyle = JSON.parse(e.target.result) mapStyle = JSON.parse(e.target?.result as string)
} }
catch(err) { catch(err) {
this.setState({ this.setState({
error: err.toString() error: (err as Error).toString()
}); });
return; return;
} }
@@ -161,7 +167,7 @@ export default class ModalOpen extends React.Component {
this.props.onOpenToggle(); this.props.onOpenToggle();
} }
onChangeUrl = (url) => { onChangeUrl = (url: string) => {
this.setState({ this.setState({
styleUrl: url, styleUrl: url,
}); });
@@ -200,7 +206,7 @@ export default class ModalOpen extends React.Component {
<section className="maputnik-modal-section"> <section className="maputnik-modal-section">
<h1>Upload Style</h1> <h1>Upload Style</h1>
<p>Upload a JSON style from your computer.</p> <p>Upload a JSON style from your computer.</p>
<FileReaderInput onChange={this.onUpload} tabIndex="-1" aria-label="Style file"> <FileReaderInput onChange={this.onUpload} tabIndex={-1} aria-label="Style file">
<InputButton className="maputnik-upload-button"><MdFileUpload /> Upload</InputButton> <InputButton className="maputnik-upload-button"><MdFileUpload /> Upload</InputButton>
</FileReaderInput> </FileReaderInput>
</section> </section>
@@ -246,7 +252,7 @@ export default class ModalOpen extends React.Component {
<ModalLoading <ModalLoading
isOpen={!!this.state.activeRequest} isOpen={!!this.state.activeRequest}
title={'Loading style'} title={'Loading style'}
onCancel={(e) => this.onCancelActiveRequest(e)} onCancel={(e: Event) => this.onCancelActiveRequest(e)}
message={"Loading: "+this.state.activeRequestUrl} message={"Loading: "+this.state.activeRequestUrl}
/> />
</div> </div>
@@ -1,8 +1,6 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import {LightSpecification, StyleSpecification, TransitionSpecification, latest} from '@maplibre/maplibre-gl-style-spec'
import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block'
import FieldArray from './FieldArray' import FieldArray from './FieldArray'
import FieldNumber from './FieldNumber' import FieldNumber from './FieldNumber'
import FieldString from './FieldString' import FieldString from './FieldString'
@@ -13,16 +11,16 @@ import FieldColor from './FieldColor'
import Modal from './Modal' import Modal from './Modal'
import fieldSpecAdditional from '../libs/field-spec-additional' import fieldSpecAdditional from '../libs/field-spec-additional'
export default class ModalSettings extends React.Component { type ModalSettingsProps = {
static propTypes = { mapStyle: StyleSpecification
mapStyle: PropTypes.object.isRequired, onStyleChanged(...args: unknown[]): unknown
onStyleChanged: PropTypes.func.isRequired, onChangeMetadataProperty(...args: unknown[]): unknown
onChangeMetadataProperty: PropTypes.func.isRequired, isOpen: boolean
isOpen: PropTypes.bool.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, };
}
changeTransitionProperty(property, value) { export default class ModalSettings extends React.Component<ModalSettingsProps> {
changeTransitionProperty(property: keyof TransitionSpecification, value: number | undefined) {
const transition = { const transition = {
...this.props.mapStyle.transition, ...this.props.mapStyle.transition,
} }
@@ -40,7 +38,7 @@ export default class ModalSettings extends React.Component {
}); });
} }
changeLightProperty(property, value) { changeLightProperty(property: keyof LightSpecification, value: any) {
const light = { const light = {
...this.props.mapStyle.light, ...this.props.mapStyle.light,
} }
@@ -49,6 +47,7 @@ export default class ModalSettings extends React.Component {
delete light[property]; delete light[property];
} }
else { else {
// @ts-ignore
light[property] = value; light[property] = value;
} }
@@ -58,22 +57,24 @@ export default class ModalSettings extends React.Component {
}); });
} }
changeStyleProperty(property, value) { changeStyleProperty(property: keyof StyleSpecification | "owner", value: any) {
const changedStyle = { const changedStyle = {
...this.props.mapStyle, ...this.props.mapStyle,
}; };
if (value === undefined) { if (value === undefined) {
// @ts-ignore
delete changedStyle[property]; delete changedStyle[property];
} }
else { else {
// @ts-ignore
changedStyle[property] = value; changedStyle[property] = value;
} }
this.props.onStyleChanged(changedStyle); this.props.onStyleChanged(changedStyle);
} }
render() { render() {
const metadata = this.props.mapStyle.metadata || {} const metadata = this.props.mapStyle.metadata || {} as any;
const {onChangeMetadataProperty, mapStyle} = this.props; const {onChangeMetadataProperty, mapStyle} = this.props;
const inputProps = { } const inputProps = { }
@@ -98,14 +99,14 @@ export default class ModalSettings extends React.Component {
label={"Owner"} label={"Owner"}
fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}} fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}}
data-wd-key="modal:settings.owner" data-wd-key="modal:settings.owner"
value={this.props.mapStyle.owner} value={(this.props.mapStyle as any).owner}
onChange={this.changeStyleProperty.bind(this, "owner")} onChange={this.changeStyleProperty.bind(this, "owner")}
/> />
<FieldUrl {...inputProps} <FieldUrl {...inputProps}
fieldSpec={latest.$root.sprite} fieldSpec={latest.$root.sprite}
label="Sprite URL" label="Sprite URL"
data-wd-key="modal:settings.sprite" data-wd-key="modal:settings.sprite"
value={this.props.mapStyle.sprite} value={this.props.mapStyle.sprite as string}
onChange={this.changeStyleProperty.bind(this, "sprite")} onChange={this.changeStyleProperty.bind(this, "sprite")}
/> />
@@ -113,7 +114,7 @@ export default class ModalSettings extends React.Component {
label="Glyphs URL" label="Glyphs URL"
fieldSpec={latest.$root.glyphs} fieldSpec={latest.$root.glyphs}
data-wd-key="modal:settings.glyphs" data-wd-key="modal:settings.glyphs"
value={this.props.mapStyle.glyphs} value={this.props.mapStyle.glyphs as string}
onChange={this.changeStyleProperty.bind(this, "glyphs")} onChange={this.changeStyleProperty.bind(this, "glyphs")}
/> />
@@ -138,7 +139,7 @@ export default class ModalSettings extends React.Component {
fieldSpec={latest.$root.center} fieldSpec={latest.$root.center}
length={2} length={2}
type="number" type="number"
value={mapStyle.center} value={mapStyle.center || []}
default={latest.$root.center.default || [0, 0]} default={latest.$root.center.default || [0, 0]}
onChange={this.changeStyleProperty.bind(this, "center")} onChange={this.changeStyleProperty.bind(this, "center")}
/> />
@@ -175,7 +176,7 @@ export default class ModalSettings extends React.Component {
label={"Light anchor"} label={"Light anchor"}
fieldSpec={latest.light.anchor} fieldSpec={latest.light.anchor}
name="light-anchor" name="light-anchor"
value={light.anchor} value={light.anchor as string}
options={Object.keys(latest.light.anchor.values)} options={Object.keys(latest.light.anchor.values)}
default={latest.light.anchor.default} default={latest.light.anchor.default}
onChange={this.changeLightProperty.bind(this, "anchor")} onChange={this.changeLightProperty.bind(this, "anchor")}
@@ -185,7 +186,7 @@ export default class ModalSettings extends React.Component {
{...inputProps} {...inputProps}
label={"Light color"} label={"Light color"}
fieldSpec={latest.light.color} fieldSpec={latest.light.color}
value={light.color} value={light.color as string}
default={latest.light.color.default} default={latest.light.color.default}
onChange={this.changeLightProperty.bind(this, "color")} onChange={this.changeLightProperty.bind(this, "color")}
/> />
@@ -194,7 +195,7 @@ export default class ModalSettings extends React.Component {
{...inputProps} {...inputProps}
label={"Light intensity"} label={"Light intensity"}
fieldSpec={latest.light.intensity} fieldSpec={latest.light.intensity}
value={light.intensity} value={light.intensity as number}
default={latest.light.intensity.default} default={latest.light.intensity.default}
onChange={this.changeLightProperty.bind(this, "intensity")} onChange={this.changeLightProperty.bind(this, "intensity")}
/> />
@@ -205,7 +206,7 @@ export default class ModalSettings extends React.Component {
fieldSpec={latest.light.position} fieldSpec={latest.light.position}
type="number" type="number"
length={latest.light.position.length} length={latest.light.position.length}
value={light.position} value={light.position as number[]}
default={latest.light.position.default} default={latest.light.position.default}
onChange={this.changeLightProperty.bind(this, "position")} onChange={this.changeLightProperty.bind(this, "position")}
/> />
@@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Modal from './Modal' import Modal from './Modal'
export default class ModalShortcuts extends React.Component { type ModalShortcutsProps = {
static propTypes = { isOpen: boolean
isOpen: PropTypes.bool.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, };
}
export default class ModalShortcuts extends React.Component<ModalShortcutsProps> {
render() { render() {
const help = [ const help = [
{ {
@@ -1,17 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputButton from './InputButton' import InputButton from './InputButton'
import Modal from './Modal' import Modal from './Modal'
// @ts-ignore
import logoImage from 'maputnik-design/logos/logo-color.svg' import logoImage from 'maputnik-design/logos/logo-color.svg'
export default class ModalSurvey extends React.Component { type ModalSurveyProps = {
static propTypes = { isOpen: boolean
isOpen: PropTypes.bool.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, };
}
export default class ModalSurvey extends React.Component<ModalSurveyProps> {
onClick = () => { onClick = () => {
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank'); window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
@@ -1,18 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {otherFilterOps} from '../libs/filterops' import {otherFilterOps} from '../libs/filterops'
import InputString from './InputString' import InputString from './InputString'
import InputAutocomplete from './InputAutocomplete' import InputAutocomplete from './InputAutocomplete'
import InputSelect from './InputSelect' import InputSelect from './InputSelect'
function tryParseInt(v) { function tryParseInt(v: string | number) {
if (v === '') return v if (v === '') return v
if (isNaN(v)) return v if (isNaN(v as number)) return v
return parseFloat(v) return parseFloat(v as string)
} }
function tryParseBool(v) { function tryParseBool(v: string | boolean) {
const isString = (typeof(v) === "string"); const isString = (typeof(v) === "string");
if(!isString) { if(!isString) {
return v; return v;
@@ -29,24 +28,24 @@ function tryParseBool(v) {
} }
} }
function parseFilter(v) { function parseFilter(v: string | boolean | number) {
v = tryParseInt(v); v = tryParseInt(v as any);
v = tryParseBool(v); v = tryParseBool(v as any);
return v; return v;
} }
export default class SingleFilterEditor extends React.Component { type SingleFilterEditorProps = {
static propTypes = { filter: any[]
filter: PropTypes.array.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, properties?: {[key: string]: string}
properties: PropTypes.object, };
}
export default class SingleFilterEditor extends React.Component<SingleFilterEditorProps> {
static defaultProps = { static defaultProps = {
properties: {}, properties: {},
} }
onFilterPartChanged(filterOp, propertyName, filterArgs) { onFilterPartChanged(filterOp: string, propertyName: string, filterArgs: string[]) {
let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)] let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)]
if(filterOp === 'has' || filterOp === '!has') { if(filterOp === 'has' || filterOp === '!has') {
newFilter = [filterOp, propertyName] newFilter = [filterOp, propertyName]
@@ -67,15 +66,15 @@ export default class SingleFilterEditor extends React.Component {
<InputAutocomplete <InputAutocomplete
aria-label="key" aria-label="key"
value={propertyName} value={propertyName}
options={Object.keys(this.props.properties).map(propName => [propName, propName])} options={Object.keys(this.props.properties!).map(propName => [propName, propName])}
onChange={newPropertyName => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)} onChange={(newPropertyName: string) => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)}
/> />
</div> </div>
<div className="maputnik-filter-editor-operator"> <div className="maputnik-filter-editor-operator">
<InputSelect <InputSelect
aria-label="function" aria-label="function"
value={filterOp} value={filterOp}
onChange={newFilterOp => this.onFilterPartChanged(newFilterOp, propertyName, filterArgs)} onChange={(newFilterOp: string) => this.onFilterPartChanged(newFilterOp, propertyName, filterArgs)}
options={otherFilterOps} options={otherFilterOps}
/> />
</div> </div>
@@ -84,12 +83,11 @@ export default class SingleFilterEditor extends React.Component {
<InputString <InputString
aria-label="value" aria-label="value"
value={filterArgs.join(',')} value={filterArgs.join(',')}
onChange={ v=> this.onFilterPartChanged(filterOp, propertyName, v.split(','))} onChange={(v: string) => this.onFilterPartChanged(filterOp, propertyName, v.split(','))}
/> />
</div> </div>
} }
</div> </div>
} }
} }
@@ -1,13 +1,12 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block' import Block from './Block'
import InputSpec from './InputSpec' import InputSpec, { SpecFieldProps as InputFieldSpecProps } from './InputSpec'
import Fieldset from './Fieldset' import Fieldset from './Fieldset'
const typeMap = { const typeMap = {
color: () => Block, 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, boolean: () => Block,
array: () => Fieldset, array: () => Fieldset,
resolvedImage: () => Block, resolvedImage: () => Block,
@@ -16,12 +15,11 @@ const typeMap = {
formatted: () => Block, formatted: () => Block,
}; };
export default class SpecField extends React.Component { type SpecFieldProps = InputFieldSpecProps & {
static propTypes = { name?: string
...InputSpec.propTypes, };
name: PropTypes.string,
}
export default class SpecField extends React.Component<SpecFieldProps> {
render() { render() {
const {props} = this; const {props} = this;