Migration of jsx files to tsx 1 (#848)

In this PR I have changed some of the jsx files to tsx file.
I'm starting off with the "leafs" so that migration of the rest will be
easier, hopefully.

What I'm basically doing is taking a jsx file, copy paste it into:
https://mskelton.dev/ratchet

And after that I'm fixing the types as needed.
It's not a very long process.

Hopefully more PRs will follow and this will be over soon.
I don't plan to migrate the storybook as I generally don't understand
why is it useful, I'll open an issue to see if anyone thinks
differently.
This commit is contained in:
Harel M
2023-12-21 23:46:56 +02:00
committed by GitHub
parent 3bf0e510e6
commit fa182e66fa
64 changed files with 1009 additions and 859 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");
+114
View File
@@ -67,11 +67,19 @@
"@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/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",
"@types/react-aria-modal": "^4.0.9",
"@types/react-autocomplete": "^1.8.9",
"@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/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"@vitejs/plugin-react": "^4.2.0", "@vitejs/plugin-react": "^4.2.0",
"cors": "^2.8.5", "cors": "^2.8.5",
@@ -4583,6 +4591,39 @@
"@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": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz",
"integrity": "sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==",
"dev": true,
"dependencies": {
"@types/color-convert": "*"
}
},
"node_modules/@types/color-convert": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.3.tgz",
"integrity": "sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==",
"dev": true,
"dependencies": {
"@types/color-name": "*"
}
},
"node_modules/@types/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw==",
"dev": true
},
"node_modules/@types/connect": { "node_modules/@types/connect": {
"version": "3.4.38", "version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@@ -4703,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",
@@ -4831,6 +4881,34 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-aria-modal": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/react-aria-modal/-/react-aria-modal-4.0.9.tgz",
"integrity": "sha512-H8SU5QoB+1ldMGYRHNm1MXfzD5oD+ZdYTrwVzT+KEiFrkTqQG2QdwVc6Ro4/RixnU4LNtMpvF5HKAIfzzGJGjw==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-autocomplete": {
"version": "1.8.9",
"resolved": "https://registry.npmjs.org/@types/react-autocomplete/-/react-autocomplete-1.8.9.tgz",
"integrity": "sha512-Il8qJEKvPU0uW+HOPiRHIKxGF61RM6cM5tEnZDmM5ek78OK5kfv04AZbNyqdPsuTnwp8HIRgBnQH2RhgKILjcg==",
"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",
"integrity": "sha512-6K5BAn3zyd8lW8UbckIAVeXGxR82Za9jyGD2DBEynsa7fKaguLDVtjfypzs7fgEV7bULgs7uhds8A8v1wABTvQ==",
"dev": true,
"dependencies": {
"@types/react": "*",
"@types/reactcss": "*"
}
},
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "16.9.24", "version": "16.9.24",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.24.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.24.tgz",
@@ -4840,6 +4918,33 @@
"@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": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@types/react-icon-base/-/react-icon-base-2.1.6.tgz",
"integrity": "sha512-ebbN1JjCm6RxBd3HdI1+8VCdiOI4qMjnl9DIHWJFrB/eYLF4mzIgdL34PIqCJBLY3vlwil9v6IHQvzsa8vgMsg==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/reactcss": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.11.tgz",
"integrity": "sha512-0fFy0ubuPlhksId8r9V8nsLcxBAPQnn15g/ERAElgE9L6rOquMj2CapsxqfyBuHlkp0/ndEUVnkYI7MkTtkGpw==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.20.6", "version": "1.20.6",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz",
@@ -4891,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",
+8
View File
@@ -96,11 +96,19 @@
"@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/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",
"@types/react-aria-modal": "^4.0.9",
"@types/react-autocomplete": "^1.8.9",
"@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/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"@vitejs/plugin-react": "^4.2.0", "@vitejs/plugin-react": "^4.2.0",
"cors": "^2.8.5", "cors": "^2.8.5",
@@ -1,40 +1,43 @@
import React from 'react' import React, {SyntheticEvent} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import FieldDocLabel from './FieldDocLabel' import FieldDocLabel from './FieldDocLabel'
import Doc from './Doc' import Doc from './Doc'
/** Wrap a component with a label */ type BlockProps = {
export default class Block extends React.Component { "data-wd-key"?: string
static propTypes = { label?: string
"data-wd-key": PropTypes.string, action?: React.ReactElement
label: PropTypes.oneOfType([ style?: object
PropTypes.string, onChange?(...args: unknown[]): unknown
PropTypes.element, fieldSpec?: object
]), wideMode?: boolean
action: PropTypes.element, error?: unknown[]
children: PropTypes.node.isRequired, };
style: PropTypes.object,
onChange: PropTypes.func,
fieldSpec: PropTypes.object,
wideMode: PropTypes.bool,
error: PropTypes.array,
}
constructor (props) { type BlockState = {
showDoc: boolean
};
/** Wrap a component with a label */
export default class Block extends React.Component<BlockProps, BlockState> {
_blockEl: HTMLDivElement | null = null;
constructor (props: BlockProps) {
super(props); super(props);
this.state = { this.state = {
showDoc: false, showDoc: false,
} }
} }
onChange(e) { onChange(e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) {
const value = e.target.value const value = e.target.value
return this.props.onChange(value === "" ? undefined : value) if (this.props.onChange) {
return this.props.onChange(value === "" ? undefined : value)
}
} }
onToggleDoc = (val) => { onToggleDoc = (val: boolean) => {
this.setState({ this.setState({
showDoc: val showDoc: val
}); });
@@ -46,20 +49,17 @@ export default class Block extends React.Component {
* causing the picker to reopen. This causes a scenario where the picker can * causing the picker to reopen. This causes a scenario where the picker can
* never be closed once open. * never be closed once open.
*/ */
onLabelClick = (event) => { onLabelClick = (event: SyntheticEvent<any, any>) => {
const el = event.nativeEvent.target; const el = event.nativeEvent.target;
const nativeEvent = event.nativeEvent; const contains = this._blockEl?.contains(el);
const contains = this._blockEl.contains(el);
if (event.nativeEvent.target.nodeName !== "INPUT" && !contains) { if (event.nativeEvent.target.nodeName !== "INPUT" && !contains) {
event.stopPropagation(); event.stopPropagation();
} }
event.preventDefault(); event.preventDefault();
} }
render() { render() {
const errors = [].concat(this.props.error || []);
return <label style={this.props.style} return <label style={this.props.style}
data-wd-key={this.props["data-wd-key"]} data-wd-key={this.props["data-wd-key"]}
className={classnames({ className={classnames({
@@ -1,24 +1,33 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
export default class Doc extends React.Component { const headers = {
static propTypes = { js: "JS",
fieldSpec: PropTypes.object.isRequired, android: "Android",
ios: "iOS",
macos: "macOS",
};
type DocProps = {
fieldSpec: {
doc?: string
values?: {
[key: string]: {
doc?: string
}
}
'sdk-support'?: {
[key: string]: typeof headers
}
} }
};
export default class Doc extends React.Component<DocProps> {
render () { render () {
const {fieldSpec} = this.props; const {fieldSpec} = this.props;
const {doc, values} = fieldSpec; const {doc, values} = fieldSpec;
const sdkSupport = fieldSpec['sdk-support']; const sdkSupport = fieldSpec['sdk-support'];
const headers = {
js: "JS",
android: "Android",
ios: "iOS",
macos: "macOS",
};
const renderValues = ( const renderValues = (
!!values && !!values &&
// HACK: Currently we merge additional values into the style spec, so this is required // HACK: Currently we merge additional values into the style spec, so this is required
@@ -30,7 +39,7 @@ export default class Doc extends React.Component {
<> <>
{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]) => {
@@ -61,10 +70,9 @@ export default class Doc extends React.Component {
return ( return (
<tr key={key}> <tr key={key}>
<td>{key}</td> <td>{key}</td>
{Object.keys(headers).map(k => { {Object.keys(headers).map((k) => {
const value = supportObj[k];
if (supportObj.hasOwnProperty(k)) { if (supportObj.hasOwnProperty(k)) {
return <td key={k}>{supportObj[k]}</td>; return <td key={k}>{supportObj[k as keyof typeof headers]}</td>;
} }
else { else {
return <td key={k}>no</td>; return <td key={k}>no</td>;
-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,26 +1,27 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md' import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
export default class FieldDocLabel extends React.Component { type FieldDocLabelProps = {
static propTypes = { label: object | string | undefined
label: PropTypes.oneOfType([ fieldSpec?: {
PropTypes.object, doc?: string
PropTypes.string
]).isRequired,
fieldSpec: PropTypes.object,
onToggleDoc: PropTypes.func,
} }
onToggleDoc?(...args: unknown[]): unknown
};
constructor (props) { type FieldDocLabelState = {
open: boolean
};
export default class FieldDocLabel extends React.Component<FieldDocLabelProps, FieldDocLabelState> {
constructor (props: FieldDocLabelProps) {
super(props); super(props);
this.state = { this.state = {
open: false, open: false,
} }
} }
onToggleDoc = (open) => { onToggleDoc = (open: boolean) => {
this.setState({ this.setState({
open, open,
}, () => { }, () => {
@@ -43,6 +44,7 @@ export default class FieldDocLabel extends React.Component {
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>
-20
View File
@@ -1,20 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import InputEnum from './InputEnum'
import Block from './Block';
import Fieldset from './Fieldset';
export default class FieldEnum extends React.Component {
static propTypes = {
...InputEnum.propTypes,
}
render() {
const {props} = this;
return <Fieldset label={props.label}>
<InputEnum {...props} />
</Fieldset>
}
}
+20
View File
@@ -0,0 +1,20 @@
import React from 'react'
import InputEnum, {InputEnumProps} from './InputEnum'
import Fieldset from './Fieldset';
type FieldEnumProps = InputEnumProps & {
label?: string;
fieldSpec?: {
doc: string
}
};
export default class FieldEnum extends React.Component<FieldEnumProps> {
render() {
return <Fieldset label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputEnum {...this.props} />
</Fieldset>
}
}
@@ -1,18 +1,17 @@
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 InputString from './InputString' import InputString from './InputString'
export default class FieldId extends React.Component { type FieldIdProps = {
static propTypes = { value: string
value: PropTypes.string.isRequired, wdKey: string
wdKey: PropTypes.string.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[]
error: PropTypes.object, };
}
export default class FieldId extends React.Component<FieldIdProps> {
render() { render() {
return <Block label={"ID"} fieldSpec={latest.layer.id} return <Block label={"ID"} fieldSpec={latest.layer.id}
data-wd-key={this.props.wdKey} data-wd-key={this.props.wdKey}
-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>
}
}
-20
View File
@@ -1,20 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputString from './InputString'
export default class FieldString extends React.Component {
static propTypes = {
...InputString.propTypes,
name: PropTypes.string,
}
render() {
const {props} = this;
return <Block label={props.label}>
<InputString {...props} />
</Block>
}
}
+19
View File
@@ -0,0 +1,19 @@
import React from 'react'
import Block from './Block'
import InputString, {InputStringProps} from './InputString'
type FieldStringProps = InputStringProps & {
name?: string
label?: string
fieldSpec?: {
doc: string
}
};
export default class FieldString extends React.Component<FieldStringProps> {
render() {
return <Block label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputString {...this.props} />
</Block>
}
}
@@ -1,20 +1,19 @@
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 InputSelect from './InputSelect' import InputSelect from './InputSelect'
import InputString from './InputString' import InputString from './InputString'
export default class FieldType extends React.Component { type FieldTypeProps = {
static propTypes = { value: string
value: PropTypes.string.isRequired, wdKey?: string
wdKey: PropTypes.string, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[] | undefined
error: PropTypes.object, disabled?: boolean
disabled: PropTypes.bool, };
}
export default class FieldType extends React.Component<FieldTypeProps> {
static defaultProps = { static defaultProps = {
disabled: false, disabled: false,
} }
-22
View File
@@ -1,22 +0,0 @@
import React, {Fragment} from 'react'
import PropTypes from 'prop-types'
import InputUrl from './InputUrl'
import Block from './Block'
export default class FieldUrl extends React.Component {
static propTypes = {
...InputUrl.propTypes,
}
render () {
const {props} = this;
return (
<Block label={this.props.label}>
<InputUrl {...props} />
</Block>
);
}
}
+23
View File
@@ -0,0 +1,23 @@
import React from 'react'
import InputUrl, {FieldUrlProps as InputUrlProps} from './InputUrl'
import Block from './Block'
type FieldUrlProps = InputUrlProps & {
label: string;
fieldSpec?: {
doc: string
}
};
export default class FieldUrl extends React.Component<FieldUrlProps> {
render () {
return (
<Block label={this.props.label} fieldSpec={this.props.fieldSpec}>
<InputUrl {...this.props} />
</Block>
);
}
}
@@ -1,13 +1,23 @@
import React from 'react' import React, { ReactElement } from 'react'
import PropTypes from 'prop-types'
import FieldDocLabel from './FieldDocLabel' import FieldDocLabel from './FieldDocLabel'
import Doc from './Doc' import Doc from './Doc'
type FieldsetProps = {
label?: string,
fieldSpec?: { doc?: string },
action?: ReactElement,
};
type FieldsetState = {
showDoc: boolean
};
let IDX = 0; let IDX = 0;
export default class Fieldset extends React.Component { export default class Fieldset extends React.Component<FieldsetProps, FieldsetState> {
constructor (props) { _labelId: string;
constructor (props: FieldsetProps) {
super(props); super(props);
this._labelId = `fieldset_label_${(IDX++)}`; this._labelId = `fieldset_label_${(IDX++)}`;
this.state = { this.state = {
@@ -15,15 +25,13 @@ export default class Fieldset extends React.Component {
} }
} }
onToggleDoc = (val) => { onToggleDoc = (val: boolean) => {
this.setState({ this.setState({
showDoc: val showDoc: val
}); });
} }
render () { render () {
const {props} = this;
return <div className="maputnik-input-block" role="group" aria-labelledby={this._labelId}> return <div className="maputnik-input-block" role="group" aria-labelledby={this._labelId}>
{this.props.fieldSpec && {this.props.fieldSpec &&
<div className="maputnik-input-block-label"> <div className="maputnik-input-block-label">
@@ -36,14 +44,14 @@ export default class Fieldset extends React.Component {
} }
{!this.props.fieldSpec && {!this.props.fieldSpec &&
<div className="maputnik-input-block-label"> <div className="maputnik-input-block-label">
{props.label} {this.props.label}
</div> </div>
} }
<div className="maputnik-input-block-action"> <div className="maputnik-input-block-action">
{this.props.action} {this.props.action}
</div> </div>
<div className="maputnik-input-block-content"> <div className="maputnik-input-block-content">
{props.children} {this.props.children}
</div> </div>
{this.props.fieldSpec && {this.props.fieldSpec &&
<div <div
@@ -1,14 +1,12 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputButton from './InputButton' import InputButton from './InputButton'
import {MdDelete} from 'react-icons/md' import {MdDelete} from 'react-icons/md'
export default class FilterEditorBlock extends React.Component { type FilterEditorBlockProps = {
static propTypes = { onDelete(...args: unknown[]): unknown
onDelete: PropTypes.func.isRequired, };
children: PropTypes.element.isRequired,
}
export default class FilterEditorBlock extends React.Component<FilterEditorBlockProps> {
render() { render() {
return <div className="maputnik-filter-editor-block"> return <div className="maputnik-filter-editor-block">
<div className="maputnik-filter-editor-block-action"> <div className="maputnik-filter-editor-block-action">
@@ -1,19 +1,18 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import IconLine from './IconLine.jsx' import IconLine from './IconLine'
import IconFill from './IconFill.jsx' import IconFill from './IconFill'
import IconSymbol from './IconSymbol.jsx' import IconSymbol from './IconSymbol'
import IconBackground from './IconBackground.jsx' import IconBackground from './IconBackground'
import IconCircle from './IconCircle.jsx' import IconCircle from './IconCircle'
import IconMissing from './IconMissing.jsx' import IconMissing from './IconMissing'
export default class IconLayer extends React.Component { type IconLayerProps = {
static propTypes = { type: string
type: PropTypes.string.isRequired, style?: object
style: PropTypes.object, };
}
export default class IconLayer extends React.Component<IconLayerProps> {
render() { render() {
const iconProps = { style: this.props.style } const iconProps = { style: this.props.style }
switch(this.props.type) { switch(this.props.type) {
@@ -1,24 +1,29 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputString from './InputString' import InputString from './InputString'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
export default class FieldArray extends React.Component { export type FieldArrayProps = {
static propTypes = { value: string[] | number[]
value: PropTypes.array, type?: string
type: PropTypes.string, length?: number
length: PropTypes.number, default?: string[] | number[]
default: PropTypes.array, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, 'aria-label'?: string
'aria-label': PropTypes.string, label?: string
} };
type FieldArrayState = {
value: string[] | number[]
initialPropsValue: unknown[]
}
export default class FieldArray extends React.Component<FieldArrayProps, FieldArrayState> {
static defaultProps = { static defaultProps = {
value: [], value: [],
default: [], default: [],
} }
constructor (props) { constructor (props: FieldArrayProps) {
super(props); super(props);
this.state = { this.state = {
value: this.props.value.slice(0), value: this.props.value.slice(0),
@@ -27,8 +32,8 @@ export default class FieldArray extends React.Component {
}; };
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props: FieldArrayProps, state: FieldArrayState) {
const value = []; const value: any[] = [];
const initialPropsValue = state.initialPropsValue.slice(0); const initialPropsValue = state.initialPropsValue.slice(0);
Array(props.length).fill(null).map((_, i) => { Array(props.length).fill(null).map((_, i) => {
@@ -47,24 +52,24 @@ export default class FieldArray extends React.Component {
}; };
} }
isComplete (value) { isComplete(value: unknown[]) {
return Array(this.props.length).fill(null).every((_, i) => { return Array(this.props.length).fill(null).every((_, i) => {
const val = value[i] const val = value[i]
return !(val === undefined || val === ""); return !(val === undefined || val === "");
}); });
} }
changeValue(idx, newValue) { changeValue(idx: number, newValue: string) {
const value = this.state.value.slice(0); const value = this.state.value.slice(0);
value[idx] = newValue; value[idx] = newValue;
this.setState({ this.setState({
value, value,
}, () => { }, () => {
if (this.isComplete(value)) { if (this.isComplete(value) && this.props.onChange) {
this.props.onChange(value); this.props.onChange(value);
} }
else { else if (this.props.onChange){
// Unset until complete // Unset until complete
this.props.onChange(undefined); this.props.onChange(undefined);
} }
@@ -85,8 +90,8 @@ export default class FieldArray extends React.Component {
if(this.props.type === 'number') { if(this.props.type === 'number') {
return <InputNumber return <InputNumber
key={i} key={i}
default={containsValues ? undefined : this.props.default[i]} default={containsValues || !this.props.default ? undefined : this.props.default[i] as number}
value={value[i]} value={value[i] as number}
required={containsValues ? true : false} required={containsValues ? true : false}
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}
@@ -94,8 +99,8 @@ export default class FieldArray extends React.Component {
} else { } else {
return <InputString return <InputString
key={i} key={i}
default={containsValues ? undefined : this.props.default[i]} default={containsValues || !this.props.default ? undefined : this.props.default[i] as string}
value={value[i]} value={value[i] as string}
required={containsValues ? true : false} required={containsValues ? true : false}
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}
@@ -1,24 +1,25 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import Autocomplete from 'react-autocomplete' import Autocomplete from 'react-autocomplete'
const MAX_HEIGHT = 140; const MAX_HEIGHT = 140;
export default class InputAutocomplete extends React.Component { export type InputAutocompleteProps = {
static propTypes = { value?: string
value: PropTypes.string, options: any[]
options: PropTypes.array, onChange(...args: unknown[]): unknown
onChange: PropTypes.func, keepMenuWithinWindowBounds?: boolean
keepMenuWithinWindowBounds: PropTypes.bool, 'aria-label'?: string
'aria-label': PropTypes.string, };
}
export default class InputAutocomplete extends React.Component<InputAutocompleteProps> {
state = { state = {
maxHeight: MAX_HEIGHT maxHeight: MAX_HEIGHT
} }
autocompleteMenuEl: HTMLDivElement | null = null;
static defaultProps = { static defaultProps = {
onChange: () => {}, onChange: () => {},
options: [], options: [],
@@ -26,7 +27,7 @@ export default class InputAutocomplete extends React.Component {
calcMaxHeight() { calcMaxHeight() {
if(this.props.keepMenuWithinWindowBounds) { if(this.props.keepMenuWithinWindowBounds) {
const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top; const maxHeight = window.innerHeight - this.autocompleteMenuEl!.getBoundingClientRect().top;
const limitedMaxHeight = Math.min(maxHeight, MAX_HEIGHT); const limitedMaxHeight = Math.min(maxHeight, MAX_HEIGHT);
if(limitedMaxHeight != this.state.maxHeight) { if(limitedMaxHeight != this.state.maxHeight) {
@@ -45,7 +46,7 @@ export default class InputAutocomplete extends React.Component {
this.calcMaxHeight(); this.calcMaxHeight();
} }
onChange (v) { onChange(v: string) {
this.props.onChange(v === "" ? undefined : v); this.props.onChange(v === "" ? undefined : v);
} }
@@ -64,7 +65,7 @@ export default class InputAutocomplete extends React.Component {
}} }}
wrapperProps={{ wrapperProps={{
className: "maputnik-autocomplete", className: "maputnik-autocomplete",
style: null style: {}
}} }}
inputProps={{ inputProps={{
'aria-label': this.props['aria-label'], 'aria-label': this.props['aria-label'],
@@ -75,11 +76,12 @@ export default class InputAutocomplete extends React.Component {
items={this.props.options} items={this.props.options}
getItemValue={(item) => item[0]} getItemValue={(item) => item[0]}
onSelect={v => this.onChange(v)} onSelect={v => this.onChange(v)}
onChange={(e, v) => this.onChange(v)} onChange={(_e, v) => this.onChange(v)}
shouldItemRender={(item, value="") => { shouldItemRender={(item, value="") => {
if (typeof(value) === "string") { if (typeof(value) === "string") {
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1 return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
} }
return false
}} }}
renderItem={(item, isHighlighted) => ( renderItem={(item, isHighlighted) => (
<div <div
@@ -1,21 +1,20 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
export default class InputButton extends React.Component { type InputButtonProps = {
static propTypes = { "data-wd-key"?: string
"data-wd-key": PropTypes.string, "aria-label"?: string
"aria-label": PropTypes.string, onClick?(...args: unknown[]): unknown
onClick: PropTypes.func, style?: object
style: PropTypes.object, className?: string
className: PropTypes.string, children?: React.ReactNode
children: PropTypes.node, disabled?: boolean
disabled: PropTypes.bool, type?: typeof HTMLButtonElement.prototype.type
type: PropTypes.string, id?: string
id: PropTypes.string, title?: string
title: PropTypes.string, };
}
export default class InputButton extends React.Component<InputButtonProps> {
render() { render() {
return <button return <button
id={this.props.id} id={this.props.id}
@@ -32,4 +31,3 @@ export default class InputButton extends React.Component {
</button> </button>
} }
} }
@@ -1,13 +1,12 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
export default class InputCheckbox extends React.Component { export type InputCheckboxProps = {
static propTypes = { value?: boolean
value: PropTypes.bool, style?: object
style: PropTypes.object, onChange(...args: unknown[]): unknown
onChange: PropTypes.func, };
}
export default class InputCheckbox extends React.Component<InputCheckboxProps> {
static defaultProps = { static defaultProps = {
value: false, value: false,
} }
@@ -36,4 +35,3 @@ export default class InputCheckbox extends React.Component {
</div> </div>
} }
} }
@@ -1,36 +1,37 @@
import React from 'react' import React from 'react'
import Color from 'color' import Color from 'color'
import ChromePicker from 'react-color/lib/components/chrome/Chrome' import ChromePicker from 'react-color/lib/components/chrome/Chrome'
import PropTypes from 'prop-types' import {ColorResult} from 'react-color';
import lodash from 'lodash'; import lodash from 'lodash';
function formatColor(color) { function formatColor(color: ColorResult): string {
const rgb = color.rgb const rgb = color.rgb
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})` return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`
} }
/*** Number fields with support for min, max and units and documentation*/ export type InputColorProps = {
export default class InputColor extends React.Component { onChange(...args: unknown[]): unknown
static propTypes = { name?: string
onChange: PropTypes.func.isRequired, value?: string
name: PropTypes.string, doc?: string
value: PropTypes.string, style?: object
doc: PropTypes.string, default?: string
style: PropTypes.object, 'aria-label'?: string
default: PropTypes.string, };
'aria-label': PropTypes.string,
}
/*** Number fields with support for min, max and units and documentation*/
export default class InputColor extends React.Component<InputColorProps> {
state = { state = {
pickerOpened: false pickerOpened: false
} }
colorInput: HTMLInputElement | null = null;
constructor () { constructor (props: InputColorProps) {
super(); super(props);
this.onChangeNoCheck = lodash.throttle(this.onChangeNoCheck, 1000/30); this.onChangeNoCheck = lodash.throttle(this.onChangeNoCheck, 1000/30);
} }
onChangeNoCheck (v) { onChangeNoCheck(v: string) {
this.props.onChange(v); this.props.onChange(v);
} }
@@ -68,31 +69,31 @@ export default class InputColor extends React.Component {
} }
} }
onChange (v) { onChange (v: string) {
this.props.onChange(v === "" ? undefined : v); this.props.onChange(v === "" ? undefined : v);
} }
render() { render() {
const offset = this.calcPickerOffset() const offset = this.calcPickerOffset()
var currentColor = this.color.object() const currentColor = this.color.object();
currentColor = { const currentChromeColor = {
r: currentColor.r, r: currentColor.r,
g: currentColor.g, g: currentColor.g,
b: currentColor.b, b: currentColor.b,
// Rename alpha -> a for ChromePicker // Rename alpha -> a for ChromePicker
a: currentColor.alpha a: currentColor.alpha!
} }
const picker = <div const picker = <div
className="maputnik-color-picker-offset" className="maputnik-color-picker-offset"
style={{ style={{
position: 'fixed', position: 'fixed',
zIndex: 1, zIndex: 1,
left: offset.left, left: offset.left,
top: offset.top, top: offset.top,
}}> }}>
<ChromePicker <ChromePicker
color={currentColor} color={currentChromeColor}
onChange={c => this.onChangeNoCheck(formatColor(c))} onChange={c => this.onChangeNoCheck(formatColor(c))}
/> />
<div <div
@@ -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,10 +1,9 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputSelect from './InputSelect' import InputSelect from './InputSelect'
import InputMultiInput from './InputMultiInput' import InputMultiInput from './InputMultiInput'
function optionsLabelLength(options) { function optionsLabelLength(options: any[]) {
let sum = 0; let sum = 0;
options.forEach(([_, label]) => { options.forEach(([_, label]) => {
sum += label.length sum += label.length
@@ -13,18 +12,20 @@ function optionsLabelLength(options) {
} }
export default class InputEnum extends React.Component { export type InputEnumProps = {
static propTypes = { "data-wd-key"?: string
"data-wd-key": PropTypes.string, value?: string
value: PropTypes.string, style?: object
style: PropTypes.object, default?: string
default: PropTypes.string, name?: string
name: PropTypes.string, onChange(...args: unknown[]): unknown
onChange: PropTypes.func, options: any[]
options: PropTypes.array, 'aria-label'?: string
'aria-label': PropTypes.string, label?: string
} };
export default class InputEnum extends React.Component<InputEnumProps> {
render() { render() {
const {options, value, onChange, name, label} = this.props; const {options, value, onChange, name, label} = this.props;
@@ -32,14 +33,14 @@ export default class InputEnum extends React.Component {
return <InputMultiInput return <InputMultiInput
name={name} name={name}
options={options} options={options}
value={value || this.props.default} value={(value || this.props.default)!}
onChange={onChange} onChange={onChange}
aria-label={this.props['aria-label'] || label} aria-label={this.props['aria-label'] || label}
/> />
} else { } else {
return <InputSelect return <InputSelect
options={options} options={options}
value={value || this.props.default} value={(value || this.props.default)!}
onChange={onChange} onChange={onChange}
aria-label={this.props['aria-label'] || label} aria-label={this.props['aria-label'] || label}
/> />
@@ -1,17 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputAutocomplete from './InputAutocomplete' import InputAutocomplete from './InputAutocomplete'
export default class FieldFont extends React.Component { export type FieldFontProps = {
static propTypes = { name: string
value: PropTypes.array, value?: string[]
default: PropTypes.array, default?: string[]
fonts: PropTypes.array, fonts?: unknown[]
style: PropTypes.object, style?: object
onChange: PropTypes.func.isRequired, onChange(...args: unknown[]): unknown
'aria-label': PropTypes.string, 'aria-label'?: string
} };
export default class FieldFont extends React.Component<FieldFontProps> {
static defaultProps = { static defaultProps = {
fonts: [] fonts: []
} }
@@ -28,7 +28,7 @@ export default class FieldFont extends React.Component {
} }
} }
changeFont(idx, newValue) { changeFont(idx: number, newValue: string) {
const changedValues = this.values.slice(0) const changedValues = this.values.slice(0)
changedValues[idx] = newValue changedValues[idx] = newValue
const filteredValues = changedValues const filteredValues = changedValues
@@ -46,7 +46,7 @@ export default class FieldFont extends React.Component {
<InputAutocomplete <InputAutocomplete
aria-label={this.props['aria-label'] || this.props.name} aria-label={this.props['aria-label'] || this.props.name}
value={value} value={value}
options={this.props.fonts.map(f => [f, f])} options={this.props.fonts?.map(f => [f, f])}
onChange={this.changeFont.bind(this, i)} onChange={this.changeFont.bind(this, i)}
/> />
</li> </li>
@@ -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;
} }
@@ -1,16 +1,15 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import InputButton from './InputButton'
export default class InputMultiInput extends React.Component { export type InputMultiInputProps = {
static propTypes = { name?: string
name: PropTypes.string.isRequired, value: string
value: PropTypes.string.isRequired, options: any[]
options: PropTypes.array.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, 'aria-label'?: string
} };
export default class InputMultiInput extends React.Component<InputMultiInputProps> {
render() { render() {
let options = this.props.options let options = this.props.options
if(options.length > 0 && !Array.isArray(options[0])) { if(options.length > 0 && !Array.isArray(options[0])) {
@@ -25,7 +24,7 @@ export default class InputMultiInput extends React.Component {
> >
<input type="radio" <input type="radio"
name={this.props.name} name={this.props.name}
onChange={e => this.props.onChange(val)} onChange={_e => this.props.onChange(val)}
value={val} value={val}
checked={val === selectedValue} checked={val === selectedValue}
/> />
@@ -1,27 +1,35 @@
import React from 'react' import React, { BaseSyntheticEvent } from 'react'
import PropTypes from 'prop-types'
let IDX = 0; let IDX = 0;
export default class InputNumber extends React.Component { export type InputNumberProps = {
static propTypes = { value?: number
value: PropTypes.number, default?: number
default: PropTypes.number, min?: number
min: PropTypes.number, max?: number
max: PropTypes.number, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, allowRange?: boolean
allowRange: PropTypes.bool, rangeStep?: number
rangeStep: PropTypes.number, wdKey?: string
wdKey: PropTypes.string, required?: boolean
required: PropTypes.bool, "aria-label"?: string
"aria-label": PropTypes.string, };
}
type InputNumberState = {
uuid: number
editing: boolean
editingRange?: boolean
value?: number
dirtyValue?: number
}
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
static defaultProps = { static defaultProps = {
rangeStep: 1 rangeStep: 1
} }
_keyboardEvent: boolean = false;
constructor(props) { constructor(props: InputNumberProps) {
super(props) super(props)
this.state = { this.state = {
uuid: IDX++, uuid: IDX++,
@@ -31,7 +39,7 @@ export default class InputNumber extends React.Component {
} }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props: InputNumberProps, state: InputNumberState) {
if (!state.editing && props.value !== state.value) { if (!state.editing && props.value !== state.value) {
return { return {
value: props.value, value: props.value,
@@ -41,16 +49,15 @@ export default class InputNumber extends React.Component {
return null; return null;
} }
changeValue(newValue) { changeValue(newValue: number | string | undefined) {
const value = (newValue === "" || newValue === undefined) ? const value = (newValue === "" || newValue === undefined) ?
undefined : undefined : +newValue;
parseFloat(newValue);
const hasChanged = this.props.value !== value; const hasChanged = this.props.value !== value;
if(this.isValid(value) && hasChanged) { if(this.isValid(value) && hasChanged) {
this.props.onChange(value) if (this.props.onChange) this.props.onChange(value)
this.setState({ this.setState({
value: newValue, value: value,
}); });
} }
else if (!this.isValid(value) && hasChanged) { else if (!this.isValid(value) && hasChanged) {
@@ -60,25 +67,25 @@ export default class InputNumber extends React.Component {
} }
this.setState({ this.setState({
dirtyValue: newValue === "" ? undefined : newValue, dirtyValue: newValue === "" ? undefined : value,
}) })
} }
isValid(v) { isValid(v: number | string | undefined) {
if (v === undefined) { if (v === undefined) {
return true; return true;
} }
const value = parseFloat(v) const value = +v;
if(isNaN(value)) { if(isNaN(value)) {
return false return false
} }
if(!isNaN(this.props.min) && value < this.props.min) { if(!isNaN(this.props.min!) && value < this.props.min!) {
return false return false
} }
if(!isNaN(this.props.max) && value > this.props.max) { if(!isNaN(this.props.max!) && value > this.props.max!) {
return false return false
} }
@@ -88,7 +95,7 @@ export default class InputNumber extends React.Component {
resetValue = () => { resetValue = () => {
this.setState({editing: false}); this.setState({editing: false});
// Reset explicitly to default value if value has been cleared // Reset explicitly to default value if value has been cleared
if(this.state.value === "") { if(!this.state.value) {
return; return;
} }
@@ -104,8 +111,8 @@ export default class InputNumber extends React.Component {
} }
} }
onChangeRange = (e) => { onChangeRange = (e: BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => {
let value = parseFloat(e.target.value, 10); let value = parseFloat(e.target.value);
const step = this.props.rangeStep; const step = this.props.rangeStep;
let dirtyValue = value; let dirtyValue = value;
@@ -119,11 +126,11 @@ export default class InputNumber extends React.Component {
// for example we might go from 13 to 13.23, however because we know // for example we might go from 13 to 13.23, however because we know
// that came from a keyboard event we always want to increase by a // that came from a keyboard event we always want to increase by a
// single step value. // single step value.
if (value < this.state.dirtyValue) { if (value < this.state.dirtyValue!) {
value = this.state.value - step; value = this.state.value! - step;
} }
else { else {
value = this.state.value + step value = this.state.value! + step
} }
dirtyValue = value; dirtyValue = value;
} }
@@ -140,10 +147,10 @@ export default class InputNumber extends React.Component {
this._keyboardEvent = false; this._keyboardEvent = false;
// Clamp between min/max // Clamp between min/max
value = Math.max(this.props.min, Math.min(this.props.max, value)); value = Math.max(this.props.min!, Math.min(this.props.max!, value));
this.setState({value, dirtyValue}); this.setState({value, dirtyValue});
this.props.onChange(value); if (this.props.onChange) this.props.onChange(value);
} }
render() { render() {
@@ -196,15 +203,15 @@ export default class InputNumber extends React.Component {
type="text" type="text"
spellCheck="false" spellCheck="false"
className="maputnik-number" className="maputnik-number"
placeholder={this.props.default} placeholder={this.props.default?.toString()}
value={inputValue === undefined ? "" : inputValue} value={inputValue === undefined ? "" : inputValue}
onFocus={e => { onFocus={_e => {
this.setState({editing: true}); this.setState({editing: true});
}} }}
onChange={e => { onChange={e => {
this.changeValue(e.target.value); this.changeValue(e.target.value);
}} }}
onBlur={e => { onBlur={_e => {
this.setState({editing: false}); this.setState({editing: false});
this.resetValue() this.resetValue()
}} }}
@@ -218,7 +225,7 @@ export default class InputNumber extends React.Component {
aria-label={this.props['aria-label']} aria-label={this.props['aria-label']}
spellCheck="false" spellCheck="false"
className="maputnik-number" className="maputnik-number"
placeholder={this.props.default} placeholder={this.props.default?.toString()}
value={value === undefined ? "" : value} value={value === undefined ? "" : value}
onChange={e => this.changeValue(e.target.value)} onChange={e => this.changeValue(e.target.value)}
onFocus={() => { onFocus={() => {
@@ -1,22 +1,20 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
export default class InputSelect extends React.Component {
static propTypes = {
value: PropTypes.string.isRequired,
"data-wd-key": PropTypes.string,
options: PropTypes.array.isRequired,
style: PropTypes.object,
onChange: PropTypes.func.isRequired,
title: PropTypes.string,
'aria-label': PropTypes.string,
}
export type InputSelectProps = {
value: string
"data-wd-key"?: string
options: [string, any][] | string[]
style?: object
onChange(...args: unknown[]): unknown
title?: string
'aria-label'?: string
};
export default class InputSelect extends React.Component<InputSelectProps> {
render() { render() {
let options = this.props.options let options = this.props.options;
if(options.length > 0 && !Array.isArray(options[0])) { if(options.length > 0 && !Array.isArray(options[0])) {
options = options.map(v => [v, v]) options = options.map((v) => [v, v]) as [string, any][];
} }
return <select return <select
-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>
);
}
}
@@ -1,26 +1,30 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
export default class InputString extends React.Component { export type InputStringProps = {
static propTypes = { "data-wd-key"?: string
"data-wd-key": PropTypes.string, value?: string
value: PropTypes.string, style?: object
style: PropTypes.object, default?: string
default: PropTypes.string, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, onInput?(...args: unknown[]): unknown
onInput: PropTypes.func, multi?: boolean
multi: PropTypes.bool, required?: boolean
required: PropTypes.bool, disabled?: boolean
disabled: PropTypes.bool, spellCheck?: boolean
spellCheck: PropTypes.bool, 'aria-label'?: string
'aria-label': PropTypes.string, };
}
type InputStringState = {
editing: boolean
value?: string
}
export default class InputString extends React.Component<InputStringProps, InputStringState> {
static defaultProps = { static defaultProps = {
onInput: () => {}, onInput: () => {},
} }
constructor(props) { constructor(props: InputStringProps) {
super(props) super(props)
this.state = { this.state = {
editing: false, editing: false,
@@ -28,7 +32,7 @@ export default class InputString extends React.Component {
} }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props: InputStringProps, state: InputStringState) {
if (!state.editing) { if (!state.editing) {
return { return {
value: props.value value: props.value
@@ -68,22 +72,22 @@ export default class InputString extends React.Component {
style: this.props.style, style: this.props.style,
value: this.state.value === undefined ? "" : this.state.value, value: this.state.value === undefined ? "" : this.state.value,
placeholder: this.props.default, placeholder: this.props.default,
onChange: e => { onChange: (e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => {
this.setState({ this.setState({
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: () => {
if(this.state.value!==this.props.value) { if(this.state.value!==this.props.value) {
this.setState({editing: false}); this.setState({editing: false});
this.props.onChange(this.state.value); if (this.props.onChange) this.props.onChange(this.state.value);
} }
}, },
onKeyDown: (e) => { onKeyDown: (e) => {
if (e.keyCode === 13) { if (e.keyCode === 13 && this.props.onChange) {
this.props.onChange(this.state.value); this.props.onChange(this.state.value);
} }
}, },
@@ -1,16 +1,15 @@
import React, {Fragment} from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputString from './InputString' import InputString from './InputString'
import SmallError from './SmallError' import SmallError from './SmallError'
function validate (url) { function validate(url: string) {
if (url === "") { if (url === "") {
return; return;
} }
let error; let error;
const getProtocol = (url) => { const getProtocol = (url: string) => {
try { try {
const urlObj = new URL(url); const urlObj = new URL(url);
return urlObj.protocol; return urlObj.protocol;
@@ -48,38 +47,44 @@ function validate (url) {
return error; return error;
} }
export default class FieldUrl extends React.Component { export type FieldUrlProps = {
static propTypes = { "data-wd-key"?: string
"data-wd-key": PropTypes.string, value: string
value: PropTypes.string, style?: object
style: PropTypes.object, default?: string
default: PropTypes.string, onChange(...args: unknown[]): unknown
onChange: PropTypes.func, onInput?(...args: unknown[]): unknown
onInput: PropTypes.func, multi?: boolean
multi: PropTypes.bool, required?: boolean
required: PropTypes.bool, 'aria-label'?: string
'aria-label': PropTypes.string, type?: string
} className?: string
};
type FieldUrlState = {
error?: React.ReactNode
}
export default class FieldUrl extends React.Component<FieldUrlProps, FieldUrlState> {
static defaultProps = { static defaultProps = {
onInput: () => {}, onInput: () => {},
} }
constructor (props) { constructor (props: FieldUrlProps) {
super(props); super(props);
this.state = { this.state = {
error: validate(props.value) error: validate(props.value)
}; };
} }
onInput = (url) => { onInput = (url: string) => {
this.setState({ this.setState({
error: validate(url) error: validate(url)
}); });
this.props.onInput(url); if (this.props.onInput) this.props.onInput(url);
} }
onChange = (url) => { onChange = (url: string) => {
this.setState({ this.setState({
error: validate(url) error: validate(url)
}); });
@@ -1,22 +1,21 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {MdClose} from 'react-icons/md' import {MdClose} from 'react-icons/md'
import AriaModal from 'react-aria-modal' import AriaModal from 'react-aria-modal'
import classnames from 'classnames'; import classnames from 'classnames';
export default class Modal extends React.Component { type ModalProps = {
static propTypes = { "data-wd-key"?: string
"data-wd-key": PropTypes.string, isOpen: boolean
isOpen: PropTypes.bool.isRequired, title: string
title: PropTypes.string.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, underlayClickExits?: boolean
children: PropTypes.node, underlayProps?: any
underlayClickExits: PropTypes.bool, className?: string
underlayProps: PropTypes.object, };
className: PropTypes.string,
}
export default class Modal extends React.Component<ModalProps> {
static defaultProps = { static defaultProps = {
underlayClickExits: true underlayClickExits: true
} }
@@ -24,7 +23,7 @@ export default class Modal extends React.Component {
// See <https://github.com/maputnik/editor/issues/416> // See <https://github.com/maputnik/editor/issues/416>
onClose = () => { onClose = () => {
if (document.activeElement) { if (document.activeElement) {
document.activeElement.blur(); (document.activeElement as HTMLElement).blur();
} }
setTimeout(() => { setTimeout(() => {
@@ -37,6 +36,7 @@ export default class Modal extends React.Component {
return <AriaModal return <AriaModal
titleText={this.props.title} titleText={this.props.title}
underlayClickExits={this.props.underlayClickExits} underlayClickExits={this.props.underlayClickExits}
// @ts-ignore
underlayProps={this.props.underlayProps} underlayProps={this.props.underlayProps}
data-wd-key={this.props["data-wd-key"]} data-wd-key={this.props["data-wd-key"]}
verticallyCenter={true} verticallyCenter={true}
@@ -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');
-15
View File
@@ -1,15 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
export default class ScrollContainer extends React.Component {
static propTypes = {
children: PropTypes.node
}
render() {
return <div className="maputnik-scroll-container">
{this.props.children}
</div>
}
}
+14
View File
@@ -0,0 +1,14 @@
import React from 'react'
type ScrollContainerProps = {
children?: React.ReactNode
};
export default class ScrollContainer extends React.Component<ScrollContainerProps> {
render() {
return <div className="maputnik-scroll-container">
{this.props.children}
</div>
}
}
@@ -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,13 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import './SmallError.scss'; import './SmallError.scss';
export default class SmallError extends React.Component { type SmallErrorProps = {
static propTypes = { children?: React.ReactNode
children: PropTypes.node, };
}
export default class SmallError extends React.Component<SmallErrorProps> {
render () { render () {
return ( return (
<div className="SmallError"> <div className="SmallError">
@@ -16,4 +16,3 @@ export default class SmallError extends React.Component {
); );
} }
} }
@@ -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;