Compare commits

...

13 Commits

Author SHA1 Message Date
HarelM c7c1139e87 More typescript migration 2023-12-21 22:23:31 +02:00
HarelM ab102dccd7 Add a test to make sure this functionality doesn't get broken 2023-12-21 22:00:16 +02:00
HarelM 901be16ae4 Migrate more components, fix spec documentation missing information 2023-12-21 20:44:39 +02:00
HarelM df65cc3eff Migrate more components 2023-12-21 15:16:41 +02:00
HarelM c633368a9a Migrate more components 2023-12-21 14:53:21 +02:00
HarelM 732581c39e More components migration 2023-12-21 13:30:19 +02:00
HarelM f435ab79c1 More components migration 2023-12-21 13:14:34 +02:00
HarelM 50226a9b2e Fix build 2023-12-21 12:00:19 +02:00
HarelM e5688fd9a5 More components migration 2023-12-21 11:58:20 +02:00
HarelM 0e39476bd9 Migrate block and field doc label 2023-12-21 10:39:54 +02:00
HarelM 19fc3970a9 Migrate more components 2023-12-21 10:27:54 +02:00
HarelM 1a09de238a Fix typescript error 2023-12-21 09:24:59 +02:00
HarelM 66a0ee5d4e Initial migration of jsx files 2023-12-21 09:23:06 +02:00
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
if (this.props.onChange) {
return this.props.onChange(value === "" ? undefined : value) return this.props.onChange(value === "" ? undefined : value)
} }
}
onToggleDoc = (val) => { onToggleDoc = (val: boolean) => {
this.setState({ this.setState({
showDoc: val showDoc: val
}); });
@@ -46,10 +49,9 @@ 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();
@@ -58,8 +60,6 @@ export default class Block extends React.Component {
} }
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,19 +69,19 @@ 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
@@ -92,7 +93,7 @@ export default class InputColor extends React.Component {
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;