mirror of
https://github.com/maputnik/editor.git
synced 2025-12-25 07:30:00 +00:00
Migration of jsx files to tsx 1 (#848)
In this PR I have changed some of the jsx files to tsx file. I'm starting off with the "leafs" so that migration of the rest will be easier, hopefully. What I'm basically doing is taking a jsx file, copy paste it into: https://mskelton.dev/ratchet And after that I'm fixing the types as needed. It's not a very long process. Hopefully more PRs will follow and this will be over soon. I don't plan to migrate the storybook as I generally don't understand why is it useful, I'll open an issue to see if anyone thinks differently.
This commit is contained in:
@@ -1,40 +1,43 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {SyntheticEvent} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import FieldDocLabel from './FieldDocLabel'
|
||||
import Doc from './Doc'
|
||||
|
||||
|
||||
/** Wrap a component with a label */
|
||||
export default class Block extends React.Component {
|
||||
static propTypes = {
|
||||
"data-wd-key": PropTypes.string,
|
||||
label: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.element,
|
||||
]),
|
||||
action: PropTypes.element,
|
||||
children: PropTypes.node.isRequired,
|
||||
style: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
fieldSpec: PropTypes.object,
|
||||
wideMode: PropTypes.bool,
|
||||
error: PropTypes.array,
|
||||
}
|
||||
type BlockProps = {
|
||||
"data-wd-key"?: string
|
||||
label?: string
|
||||
action?: React.ReactElement
|
||||
style?: object
|
||||
onChange?(...args: unknown[]): unknown
|
||||
fieldSpec?: object
|
||||
wideMode?: boolean
|
||||
error?: unknown[]
|
||||
};
|
||||
|
||||
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);
|
||||
this.state = {
|
||||
showDoc: false,
|
||||
}
|
||||
}
|
||||
|
||||
onChange(e) {
|
||||
onChange(e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) {
|
||||
const value = e.target.value
|
||||
return this.props.onChange(value === "" ? undefined : value)
|
||||
if (this.props.onChange) {
|
||||
return this.props.onChange(value === "" ? undefined : value)
|
||||
}
|
||||
}
|
||||
|
||||
onToggleDoc = (val) => {
|
||||
onToggleDoc = (val: boolean) => {
|
||||
this.setState({
|
||||
showDoc: val
|
||||
});
|
||||
@@ -46,20 +49,17 @@ export default class Block extends React.Component {
|
||||
* causing the picker to reopen. This causes a scenario where the picker can
|
||||
* never be closed once open.
|
||||
*/
|
||||
onLabelClick = (event) => {
|
||||
onLabelClick = (event: SyntheticEvent<any, any>) => {
|
||||
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) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
render() {
|
||||
const errors = [].concat(this.props.error || []);
|
||||
|
||||
return <label style={this.props.style}
|
||||
data-wd-key={this.props["data-wd-key"]}
|
||||
className={classnames({
|
||||
@@ -1,24 +1,33 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class Doc extends React.Component {
|
||||
static propTypes = {
|
||||
fieldSpec: PropTypes.object.isRequired,
|
||||
const headers = {
|
||||
js: "JS",
|
||||
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 () {
|
||||
const {fieldSpec} = this.props;
|
||||
|
||||
const {doc, values} = fieldSpec;
|
||||
const sdkSupport = fieldSpec['sdk-support'];
|
||||
|
||||
const headers = {
|
||||
js: "JS",
|
||||
android: "Android",
|
||||
ios: "iOS",
|
||||
macos: "macOS",
|
||||
};
|
||||
|
||||
const renderValues = (
|
||||
!!values &&
|
||||
// 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 &&
|
||||
<div className="SpecDoc">
|
||||
<div className="SpecDoc__doc">{doc}</div>
|
||||
<div className="SpecDoc__doc" data-wd-key='spec-field-doc'>{doc}</div>
|
||||
{renderValues &&
|
||||
<ul className="SpecDoc__values">
|
||||
{Object.entries(values).map(([key, value]) => {
|
||||
@@ -61,10 +70,9 @@ export default class Doc extends React.Component {
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td>{key}</td>
|
||||
{Object.keys(headers).map(k => {
|
||||
const value = supportObj[k];
|
||||
{Object.keys(headers).map((k) => {
|
||||
if (supportObj.hasOwnProperty(k)) {
|
||||
return <td key={k}>{supportObj[k]}</td>;
|
||||
return <td key={k}>{supportObj[k as keyof typeof headers]}</td>;
|
||||
}
|
||||
else {
|
||||
return <td key={k}>no</td>;
|
||||
@@ -80,4 +88,4 @@ export default class Doc extends React.Component {
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
src/components/FieldArray.tsx
Normal file
19
src/components/FieldArray.tsx
Normal 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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
src/components/FieldColor.tsx
Normal file
21
src/components/FieldColor.tsx
Normal 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 PropTypes from 'prop-types'
|
||||
|
||||
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
|
||||
|
||||
export default class FieldDocLabel extends React.Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.string
|
||||
]).isRequired,
|
||||
fieldSpec: PropTypes.object,
|
||||
onToggleDoc: PropTypes.func,
|
||||
type FieldDocLabelProps = {
|
||||
label: object | string | undefined
|
||||
fieldSpec?: {
|
||||
doc?: string
|
||||
}
|
||||
onToggleDoc?(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
type FieldDocLabelState = {
|
||||
open: boolean
|
||||
};
|
||||
|
||||
export default class FieldDocLabel extends React.Component<FieldDocLabelProps, FieldDocLabelState> {
|
||||
constructor (props: FieldDocLabelProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
open: false,
|
||||
}
|
||||
}
|
||||
|
||||
onToggleDoc = (open) => {
|
||||
onToggleDoc = (open: boolean) => {
|
||||
this.setState({
|
||||
open,
|
||||
}, () => {
|
||||
@@ -43,6 +44,7 @@ export default class FieldDocLabel extends React.Component {
|
||||
aria-label={this.state.open ? "close property documentation" : "open property documentation"}
|
||||
className={`maputnik-doc-button maputnik-doc-button--${this.state.open ? 'open' : 'closed'}`}
|
||||
onClick={() => this.onToggleDoc(!this.state.open)}
|
||||
data-wd-key={'field-doc-button-'+label}
|
||||
>
|
||||
{this.state.open ? <MdHighlightOff /> : <MdInfoOutline />}
|
||||
</button>
|
||||
@@ -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
src/components/FieldEnum.tsx
Normal file
20
src/components/FieldEnum.tsx
Normal 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 PropTypes from 'prop-types'
|
||||
|
||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||
import Block from './Block'
|
||||
import InputString from './InputString'
|
||||
|
||||
export default class FieldId extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
wdKey: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
error: PropTypes.object,
|
||||
}
|
||||
type FieldIdProps = {
|
||||
value: string
|
||||
wdKey: string
|
||||
onChange(...args: unknown[]): unknown
|
||||
error?: unknown[]
|
||||
};
|
||||
|
||||
export default class FieldId extends React.Component<FieldIdProps> {
|
||||
render() {
|
||||
return <Block label={"ID"} fieldSpec={latest.layer.id}
|
||||
data-wd-key={this.props.wdKey}
|
||||
@@ -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
src/components/FieldJson.tsx
Normal file
13
src/components/FieldJson.tsx
Normal 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 PropTypes from 'prop-types'
|
||||
|
||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||
import Block from './Block'
|
||||
import InputNumber from './InputNumber'
|
||||
|
||||
export default class FieldMaxZoom extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
error: PropTypes.object,
|
||||
}
|
||||
type FieldMaxZoomProps = {
|
||||
value?: number
|
||||
onChange(...args: unknown[]): unknown
|
||||
error?: unknown[]
|
||||
};
|
||||
|
||||
export default class FieldMaxZoom extends React.Component<FieldMaxZoomProps> {
|
||||
render() {
|
||||
return <Block label={"Max Zoom"} fieldSpec={latest.layer.maxzoom}
|
||||
error={this.props.error}
|
||||
@@ -1,17 +1,16 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||
import Block from './Block'
|
||||
import InputNumber from './InputNumber'
|
||||
|
||||
export default class FieldMinZoom extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
error: PropTypes.object,
|
||||
}
|
||||
type FieldMinZoomProps = {
|
||||
value?: number
|
||||
onChange(...args: unknown[]): unknown
|
||||
error?: unknown[]
|
||||
};
|
||||
|
||||
export default class FieldMinZoom extends React.Component<FieldMinZoomProps> {
|
||||
render() {
|
||||
return <Block label={"Min Zoom"} fieldSpec={latest.layer.minzoom}
|
||||
error={this.props.error}
|
||||
@@ -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
src/components/FieldMultiInput.tsx
Normal file
20
src/components/FieldMultiInput.tsx
Normal 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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
src/components/FieldNumber.tsx
Normal file
20
src/components/FieldNumber.tsx
Normal 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>
|
||||
}
|
||||
}
|
||||
@@ -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
src/components/FieldSelect.tsx
Normal file
22
src/components/FieldSelect.tsx
Normal 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>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
src/components/FieldString.tsx
Normal file
19
src/components/FieldString.tsx
Normal 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 PropTypes from 'prop-types'
|
||||
|
||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||
import Block from './Block'
|
||||
import InputSelect from './InputSelect'
|
||||
import InputString from './InputString'
|
||||
|
||||
export default class FieldType extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
wdKey: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
error: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
type FieldTypeProps = {
|
||||
value: string
|
||||
wdKey?: string
|
||||
onChange(...args: unknown[]): unknown
|
||||
error?: unknown[] | undefined
|
||||
disabled?: boolean
|
||||
};
|
||||
|
||||
export default class FieldType extends React.Component<FieldTypeProps> {
|
||||
static defaultProps = {
|
||||
disabled: false,
|
||||
}
|
||||
@@ -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
src/components/FieldUrl.tsx
Normal file
23
src/components/FieldUrl.tsx
Normal 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 PropTypes from 'prop-types'
|
||||
import React, { ReactElement } from 'react'
|
||||
import FieldDocLabel from './FieldDocLabel'
|
||||
import Doc from './Doc'
|
||||
|
||||
type FieldsetProps = {
|
||||
label?: string,
|
||||
fieldSpec?: { doc?: string },
|
||||
action?: ReactElement,
|
||||
};
|
||||
|
||||
type FieldsetState = {
|
||||
showDoc: boolean
|
||||
};
|
||||
|
||||
let IDX = 0;
|
||||
|
||||
export default class Fieldset extends React.Component {
|
||||
constructor (props) {
|
||||
export default class Fieldset extends React.Component<FieldsetProps, FieldsetState> {
|
||||
_labelId: string;
|
||||
|
||||
constructor (props: FieldsetProps) {
|
||||
super(props);
|
||||
this._labelId = `fieldset_label_${(IDX++)}`;
|
||||
this.state = {
|
||||
@@ -15,15 +25,13 @@ export default class Fieldset extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onToggleDoc = (val) => {
|
||||
onToggleDoc = (val: boolean) => {
|
||||
this.setState({
|
||||
showDoc: val
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const {props} = this;
|
||||
|
||||
return <div className="maputnik-input-block" role="group" aria-labelledby={this._labelId}>
|
||||
{this.props.fieldSpec &&
|
||||
<div className="maputnik-input-block-label">
|
||||
@@ -36,14 +44,14 @@ export default class Fieldset extends React.Component {
|
||||
}
|
||||
{!this.props.fieldSpec &&
|
||||
<div className="maputnik-input-block-label">
|
||||
{props.label}
|
||||
{this.props.label}
|
||||
</div>
|
||||
}
|
||||
<div className="maputnik-input-block-action">
|
||||
{this.props.action}
|
||||
</div>
|
||||
<div className="maputnik-input-block-content">
|
||||
{props.children}
|
||||
{this.props.children}
|
||||
</div>
|
||||
{this.props.fieldSpec &&
|
||||
<div
|
||||
@@ -1,14 +1,12 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import InputButton from './InputButton'
|
||||
import {MdDelete} from 'react-icons/md'
|
||||
|
||||
export default class FilterEditorBlock extends React.Component {
|
||||
static propTypes = {
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
children: PropTypes.element.isRequired,
|
||||
}
|
||||
type FilterEditorBlockProps = {
|
||||
onDelete(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
export default class FilterEditorBlock extends React.Component<FilterEditorBlockProps> {
|
||||
render() {
|
||||
return <div className="maputnik-filter-editor-block">
|
||||
<div className="maputnik-filter-editor-block-action">
|
||||
@@ -1,19 +1,18 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import IconLine from './IconLine.jsx'
|
||||
import IconFill from './IconFill.jsx'
|
||||
import IconSymbol from './IconSymbol.jsx'
|
||||
import IconBackground from './IconBackground.jsx'
|
||||
import IconCircle from './IconCircle.jsx'
|
||||
import IconMissing from './IconMissing.jsx'
|
||||
import IconLine from './IconLine'
|
||||
import IconFill from './IconFill'
|
||||
import IconSymbol from './IconSymbol'
|
||||
import IconBackground from './IconBackground'
|
||||
import IconCircle from './IconCircle'
|
||||
import IconMissing from './IconMissing'
|
||||
|
||||
export default class IconLayer extends React.Component {
|
||||
static propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
style: PropTypes.object,
|
||||
}
|
||||
type IconLayerProps = {
|
||||
type: string
|
||||
style?: object
|
||||
};
|
||||
|
||||
export default class IconLayer extends React.Component<IconLayerProps> {
|
||||
render() {
|
||||
const iconProps = { style: this.props.style }
|
||||
switch(this.props.type) {
|
||||
@@ -1,24 +1,29 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import InputString from './InputString'
|
||||
import InputNumber from './InputNumber'
|
||||
|
||||
export default class FieldArray extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.array,
|
||||
type: PropTypes.string,
|
||||
length: PropTypes.number,
|
||||
default: PropTypes.array,
|
||||
onChange: PropTypes.func,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
export type FieldArrayProps = {
|
||||
value: string[] | number[]
|
||||
type?: string
|
||||
length?: number
|
||||
default?: string[] | number[]
|
||||
onChange?(...args: unknown[]): unknown
|
||||
'aria-label'?: string
|
||||
label?: string
|
||||
};
|
||||
|
||||
type FieldArrayState = {
|
||||
value: string[] | number[]
|
||||
initialPropsValue: unknown[]
|
||||
}
|
||||
|
||||
export default class FieldArray extends React.Component<FieldArrayProps, FieldArrayState> {
|
||||
static defaultProps = {
|
||||
value: [],
|
||||
default: [],
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
constructor (props: FieldArrayProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: this.props.value.slice(0),
|
||||
@@ -27,8 +32,8 @@ export default class FieldArray extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
const value = [];
|
||||
static getDerivedStateFromProps(props: FieldArrayProps, state: FieldArrayState) {
|
||||
const value: any[] = [];
|
||||
const initialPropsValue = state.initialPropsValue.slice(0);
|
||||
|
||||
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) => {
|
||||
const val = value[i]
|
||||
return !(val === undefined || val === "");
|
||||
});
|
||||
}
|
||||
|
||||
changeValue(idx, newValue) {
|
||||
changeValue(idx: number, newValue: string) {
|
||||
const value = this.state.value.slice(0);
|
||||
value[idx] = newValue;
|
||||
|
||||
this.setState({
|
||||
value,
|
||||
}, () => {
|
||||
if (this.isComplete(value)) {
|
||||
if (this.isComplete(value) && this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
else {
|
||||
else if (this.props.onChange){
|
||||
// Unset until complete
|
||||
this.props.onChange(undefined);
|
||||
}
|
||||
@@ -85,8 +90,8 @@ export default class FieldArray extends React.Component {
|
||||
if(this.props.type === 'number') {
|
||||
return <InputNumber
|
||||
key={i}
|
||||
default={containsValues ? undefined : this.props.default[i]}
|
||||
value={value[i]}
|
||||
default={containsValues || !this.props.default ? undefined : this.props.default[i] as number}
|
||||
value={value[i] as number}
|
||||
required={containsValues ? true : false}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
@@ -94,8 +99,8 @@ export default class FieldArray extends React.Component {
|
||||
} else {
|
||||
return <InputString
|
||||
key={i}
|
||||
default={containsValues ? undefined : this.props.default[i]}
|
||||
value={value[i]}
|
||||
default={containsValues || !this.props.default ? undefined : this.props.default[i] as string}
|
||||
value={value[i] as string}
|
||||
required={containsValues ? true : false}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
@@ -1,24 +1,25 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import Autocomplete from 'react-autocomplete'
|
||||
|
||||
|
||||
const MAX_HEIGHT = 140;
|
||||
|
||||
export default class InputAutocomplete extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
options: PropTypes.array,
|
||||
onChange: PropTypes.func,
|
||||
keepMenuWithinWindowBounds: PropTypes.bool,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
export type InputAutocompleteProps = {
|
||||
value?: string
|
||||
options: any[]
|
||||
onChange(...args: unknown[]): unknown
|
||||
keepMenuWithinWindowBounds?: boolean
|
||||
'aria-label'?: string
|
||||
};
|
||||
|
||||
export default class InputAutocomplete extends React.Component<InputAutocompleteProps> {
|
||||
state = {
|
||||
maxHeight: MAX_HEIGHT
|
||||
}
|
||||
|
||||
autocompleteMenuEl: HTMLDivElement | null = null;
|
||||
|
||||
static defaultProps = {
|
||||
onChange: () => {},
|
||||
options: [],
|
||||
@@ -26,7 +27,7 @@ export default class InputAutocomplete extends React.Component {
|
||||
|
||||
calcMaxHeight() {
|
||||
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);
|
||||
|
||||
if(limitedMaxHeight != this.state.maxHeight) {
|
||||
@@ -45,7 +46,7 @@ export default class InputAutocomplete extends React.Component {
|
||||
this.calcMaxHeight();
|
||||
}
|
||||
|
||||
onChange (v) {
|
||||
onChange(v: string) {
|
||||
this.props.onChange(v === "" ? undefined : v);
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ export default class InputAutocomplete extends React.Component {
|
||||
}}
|
||||
wrapperProps={{
|
||||
className: "maputnik-autocomplete",
|
||||
style: null
|
||||
style: {}
|
||||
}}
|
||||
inputProps={{
|
||||
'aria-label': this.props['aria-label'],
|
||||
@@ -75,11 +76,12 @@ export default class InputAutocomplete extends React.Component {
|
||||
items={this.props.options}
|
||||
getItemValue={(item) => item[0]}
|
||||
onSelect={v => this.onChange(v)}
|
||||
onChange={(e, v) => this.onChange(v)}
|
||||
onChange={(_e, v) => this.onChange(v)}
|
||||
shouldItemRender={(item, value="") => {
|
||||
if (typeof(value) === "string") {
|
||||
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
|
||||
}
|
||||
return false
|
||||
}}
|
||||
renderItem={(item, isHighlighted) => (
|
||||
<div
|
||||
@@ -1,21 +1,20 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export default class InputButton extends React.Component {
|
||||
static propTypes = {
|
||||
"data-wd-key": PropTypes.string,
|
||||
"aria-label": PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
disabled: PropTypes.bool,
|
||||
type: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
}
|
||||
type InputButtonProps = {
|
||||
"data-wd-key"?: string
|
||||
"aria-label"?: string
|
||||
onClick?(...args: unknown[]): unknown
|
||||
style?: object
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
disabled?: boolean
|
||||
type?: typeof HTMLButtonElement.prototype.type
|
||||
id?: string
|
||||
title?: string
|
||||
};
|
||||
|
||||
export default class InputButton extends React.Component<InputButtonProps> {
|
||||
render() {
|
||||
return <button
|
||||
id={this.props.id}
|
||||
@@ -31,5 +30,4 @@ export default class InputButton extends React.Component {
|
||||
{this.props.children}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class InputCheckbox extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
}
|
||||
export type InputCheckboxProps = {
|
||||
value?: boolean
|
||||
style?: object
|
||||
onChange(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
export default class InputCheckbox extends React.Component<InputCheckboxProps> {
|
||||
static defaultProps = {
|
||||
value: false,
|
||||
}
|
||||
@@ -35,5 +34,4 @@ export default class InputCheckbox extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +1,37 @@
|
||||
import React from 'react'
|
||||
import Color from 'color'
|
||||
import ChromePicker from 'react-color/lib/components/chrome/Chrome'
|
||||
import PropTypes from 'prop-types'
|
||||
import {ColorResult} from 'react-color';
|
||||
import lodash from 'lodash';
|
||||
|
||||
function formatColor(color) {
|
||||
function formatColor(color: ColorResult): string {
|
||||
const rgb = color.rgb
|
||||
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`
|
||||
}
|
||||
|
||||
/*** Number fields with support for min, max and units and documentation*/
|
||||
export default class InputColor extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
doc: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
default: PropTypes.string,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
export type InputColorProps = {
|
||||
onChange(...args: unknown[]): unknown
|
||||
name?: string
|
||||
value?: string
|
||||
doc?: string
|
||||
style?: object
|
||||
default?: string
|
||||
'aria-label'?: string
|
||||
};
|
||||
|
||||
/*** Number fields with support for min, max and units and documentation*/
|
||||
export default class InputColor extends React.Component<InputColorProps> {
|
||||
state = {
|
||||
pickerOpened: false
|
||||
}
|
||||
colorInput: HTMLInputElement | null = null;
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
constructor (props: InputColorProps) {
|
||||
super(props);
|
||||
this.onChangeNoCheck = lodash.throttle(this.onChangeNoCheck, 1000/30);
|
||||
}
|
||||
|
||||
onChangeNoCheck (v) {
|
||||
onChangeNoCheck(v: string) {
|
||||
this.props.onChange(v);
|
||||
}
|
||||
|
||||
@@ -68,31 +69,31 @@ export default class InputColor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onChange (v) {
|
||||
onChange (v: string) {
|
||||
this.props.onChange(v === "" ? undefined : v);
|
||||
}
|
||||
|
||||
render() {
|
||||
const offset = this.calcPickerOffset()
|
||||
var currentColor = this.color.object()
|
||||
currentColor = {
|
||||
const currentColor = this.color.object();
|
||||
const currentChromeColor = {
|
||||
r: currentColor.r,
|
||||
g: currentColor.g,
|
||||
b: currentColor.b,
|
||||
// Rename alpha -> a for ChromePicker
|
||||
a: currentColor.alpha
|
||||
a: currentColor.alpha!
|
||||
}
|
||||
|
||||
const picker = <div
|
||||
className="maputnik-color-picker-offset"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
zIndex: 1,
|
||||
position: 'fixed',
|
||||
zIndex: 1,
|
||||
left: offset.left,
|
||||
top: offset.top,
|
||||
}}>
|
||||
<ChromePicker
|
||||
color={currentColor}
|
||||
color={currentChromeColor}
|
||||
onChange={c => this.onChangeNoCheck(formatColor(c))}
|
||||
/>
|
||||
<div
|
||||
@@ -1,30 +1,34 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
import {MdDelete} from 'react-icons/md'
|
||||
|
||||
import InputString from './InputString'
|
||||
import InputNumber from './InputNumber'
|
||||
import InputButton from './InputButton'
|
||||
import {MdDelete} from 'react-icons/md'
|
||||
import FieldDocLabel from './FieldDocLabel'
|
||||
import InputEnum from './InputEnum'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
import InputUrl from './InputUrl'
|
||||
|
||||
|
||||
export default class FieldDynamicArray extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.array,
|
||||
type: PropTypes.string,
|
||||
default: PropTypes.array,
|
||||
onChange: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
fieldSpec: PropTypes.object,
|
||||
'aria-label': PropTypes.string,
|
||||
export type FieldDynamicArrayProps = {
|
||||
value?: (string | number)[]
|
||||
type?: 'url' | 'number' | 'enum'
|
||||
default?: (string | number)[]
|
||||
onChange?(...args: unknown[]): unknown
|
||||
style?: object
|
||||
fieldSpec?: {
|
||||
values?: any
|
||||
}
|
||||
'aria-label'?: string
|
||||
label: string
|
||||
};
|
||||
|
||||
changeValue(idx, newValue) {
|
||||
|
||||
export default class FieldDynamicArray extends React.Component<FieldDynamicArrayProps> {
|
||||
changeValue(idx: number, newValue: string | number) {
|
||||
const values = this.values.slice(0)
|
||||
values[idx] = newValue
|
||||
this.props.onChange(values)
|
||||
if (this.props.onChange) this.props.onChange(values)
|
||||
}
|
||||
|
||||
get values() {
|
||||
@@ -41,20 +45,20 @@ export default class FieldDynamicArray extends React.Component {
|
||||
}
|
||||
else if (this.props.type === 'enum') {
|
||||
const {fieldSpec} = this.props;
|
||||
const defaultValue = Object.keys(fieldSpec.values)[0];
|
||||
const defaultValue = Object.keys(fieldSpec!.values)[0];
|
||||
values.push(defaultValue);
|
||||
} else {
|
||||
values.push("")
|
||||
}
|
||||
|
||||
this.props.onChange(values)
|
||||
if (this.props.onChange) this.props.onChange(values)
|
||||
}
|
||||
|
||||
deleteValue(valueIdx) {
|
||||
deleteValue(valueIdx: number) {
|
||||
const values = this.values.slice(0)
|
||||
values.splice(valueIdx, 1)
|
||||
|
||||
this.props.onChange(values.length > 0 ? values : undefined);
|
||||
if (this.props.onChange) this.props.onChange(values.length > 0 ? values : undefined);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -63,30 +67,30 @@ export default class FieldDynamicArray extends React.Component {
|
||||
let input;
|
||||
if(this.props.type === 'url') {
|
||||
input = <InputUrl
|
||||
value={v}
|
||||
value={v as string}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
}
|
||||
else if (this.props.type === 'number') {
|
||||
input = <InputNumber
|
||||
value={v}
|
||||
value={v as number}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
}
|
||||
else if (this.props.type === 'enum') {
|
||||
const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)]);
|
||||
const options = Object.keys(this.props.fieldSpec?.values).map(v => [v, capitalize(v)]);
|
||||
input = <InputEnum
|
||||
options={options}
|
||||
value={v}
|
||||
value={v as string}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
}
|
||||
else {
|
||||
input = <InputString
|
||||
value={v}
|
||||
value={v as string}
|
||||
onChange={this.changeValue.bind(this, i)}
|
||||
aria-label={this.props['aria-label'] || this.props.label}
|
||||
/>
|
||||
@@ -120,11 +124,11 @@ export default class FieldDynamicArray extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteValueInputButton extends React.Component {
|
||||
static propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
type DeleteValueInputButtonProps = {
|
||||
onClick?(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
class DeleteValueInputButton extends React.Component<DeleteValueInputButtonProps> {
|
||||
render() {
|
||||
return <InputButton
|
||||
className="maputnik-delete-stop"
|
||||
@@ -133,7 +137,7 @@ class DeleteValueInputButton extends React.Component {
|
||||
>
|
||||
<FieldDocLabel
|
||||
label={<MdDelete />}
|
||||
doc={"Remove array item."}
|
||||
fieldSpec={{doc:" Remove array item."}}
|
||||
/>
|
||||
</InputButton>
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import InputSelect from './InputSelect'
|
||||
import InputMultiInput from './InputMultiInput'
|
||||
|
||||
|
||||
function optionsLabelLength(options) {
|
||||
function optionsLabelLength(options: any[]) {
|
||||
let sum = 0;
|
||||
options.forEach(([_, label]) => {
|
||||
sum += label.length
|
||||
@@ -13,18 +12,20 @@ function optionsLabelLength(options) {
|
||||
}
|
||||
|
||||
|
||||
export default class InputEnum extends React.Component {
|
||||
static propTypes = {
|
||||
"data-wd-key": PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
default: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
options: PropTypes.array,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
export type InputEnumProps = {
|
||||
"data-wd-key"?: string
|
||||
value?: string
|
||||
style?: object
|
||||
default?: string
|
||||
name?: string
|
||||
onChange(...args: unknown[]): unknown
|
||||
options: any[]
|
||||
'aria-label'?: string
|
||||
label?: string
|
||||
};
|
||||
|
||||
|
||||
export default class InputEnum extends React.Component<InputEnumProps> {
|
||||
render() {
|
||||
const {options, value, onChange, name, label} = this.props;
|
||||
|
||||
@@ -32,14 +33,14 @@ export default class InputEnum extends React.Component {
|
||||
return <InputMultiInput
|
||||
name={name}
|
||||
options={options}
|
||||
value={value || this.props.default}
|
||||
value={(value || this.props.default)!}
|
||||
onChange={onChange}
|
||||
aria-label={this.props['aria-label'] || label}
|
||||
/>
|
||||
} else {
|
||||
return <InputSelect
|
||||
options={options}
|
||||
value={value || this.props.default}
|
||||
value={(value || this.props.default)!}
|
||||
onChange={onChange}
|
||||
aria-label={this.props['aria-label'] || label}
|
||||
/>
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import InputAutocomplete from './InputAutocomplete'
|
||||
|
||||
export default class FieldFont extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.array,
|
||||
default: PropTypes.array,
|
||||
fonts: PropTypes.array,
|
||||
style: PropTypes.object,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
export type FieldFontProps = {
|
||||
name: string
|
||||
value?: string[]
|
||||
default?: string[]
|
||||
fonts?: unknown[]
|
||||
style?: object
|
||||
onChange(...args: unknown[]): unknown
|
||||
'aria-label'?: string
|
||||
};
|
||||
|
||||
export default class FieldFont extends React.Component<FieldFontProps> {
|
||||
static defaultProps = {
|
||||
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)
|
||||
changedValues[idx] = newValue
|
||||
const filteredValues = changedValues
|
||||
@@ -46,7 +46,7 @@ export default class FieldFont extends React.Component {
|
||||
<InputAutocomplete
|
||||
aria-label={this.props['aria-label'] || this.props.name}
|
||||
value={value}
|
||||
options={this.props.fonts.map(f => [f, f])}
|
||||
options={this.props.fonts?.map(f => [f, f])}
|
||||
onChange={this.changeFont.bind(this, i)}
|
||||
/>
|
||||
</li>
|
||||
@@ -1,47 +1,45 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames';
|
||||
|
||||
import Block from './Block'
|
||||
import FieldString from './FieldString'
|
||||
import CodeMirror from 'codemirror';
|
||||
import CodeMirror, { ModeSpec } from 'codemirror';
|
||||
|
||||
import 'codemirror/mode/javascript/javascript'
|
||||
import 'codemirror/addon/lint/lint'
|
||||
import 'codemirror/addon/edit/matchbrackets'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import 'codemirror/addon/lint/lint.css'
|
||||
import jsonlint from 'jsonlint'
|
||||
import stringifyPretty from 'json-stringify-pretty-compact'
|
||||
import '../util/codemirror-mgl';
|
||||
|
||||
|
||||
export default class InputJson extends React.Component {
|
||||
static propTypes = {
|
||||
layer: PropTypes.any.isRequired,
|
||||
maxHeight: PropTypes.number,
|
||||
onChange: PropTypes.func,
|
||||
lineNumbers: PropTypes.bool,
|
||||
lineWrapping: PropTypes.bool,
|
||||
getValue: PropTypes.func,
|
||||
gutters: PropTypes.array,
|
||||
className: PropTypes.string,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onJSONValid: PropTypes.func,
|
||||
onJSONInvalid: PropTypes.func,
|
||||
mode: PropTypes.object,
|
||||
lint: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.object,
|
||||
]),
|
||||
}
|
||||
export type InputJsonProps = {
|
||||
layer: any
|
||||
maxHeight?: number
|
||||
onChange?(...args: unknown[]): unknown
|
||||
lineNumbers?: boolean
|
||||
lineWrapping?: boolean
|
||||
getValue(data: any): string
|
||||
gutters?: string[]
|
||||
className?: string
|
||||
onFocus?(...args: unknown[]): unknown
|
||||
onBlur?(...args: unknown[]): unknown
|
||||
onJSONValid?(...args: unknown[]): unknown
|
||||
onJSONInvalid?(...args: unknown[]): unknown
|
||||
mode?: ModeSpec<any>
|
||||
lint?: boolean | object
|
||||
};
|
||||
|
||||
type InputJsonState = {
|
||||
isEditing: boolean
|
||||
showMessage: boolean
|
||||
prevValue: string
|
||||
};
|
||||
|
||||
export default class InputJson extends React.Component<InputJsonProps, InputJsonState> {
|
||||
static defaultProps = {
|
||||
lineNumbers: true,
|
||||
lineWrapping: false,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
getValue: (data) => {
|
||||
getValue: (data: any) => {
|
||||
return stringifyPretty(data, {indent: 2, maxLength: 40});
|
||||
},
|
||||
onFocus: () => {},
|
||||
@@ -49,8 +47,12 @@ export default class InputJson extends React.Component {
|
||||
onJSONInvalid: () => {},
|
||||
onJSONValid: () => {},
|
||||
}
|
||||
_keyEvent: string;
|
||||
_doc: CodeMirror.Editor | undefined;
|
||||
_el: HTMLDivElement | null = null;
|
||||
_cancelNextChange: boolean = false;
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: InputJsonProps) {
|
||||
super(props);
|
||||
this._keyEvent = "keyboard";
|
||||
this.state = {
|
||||
@@ -61,7 +63,7 @@ export default class InputJson extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._doc = CodeMirror(this._el, {
|
||||
this._doc = CodeMirror(this._el!, {
|
||||
value: this.props.getValue(this.props.layer),
|
||||
mode: this.props.mode || {
|
||||
name: "mgl",
|
||||
@@ -84,12 +86,12 @@ export default class InputJson extends React.Component {
|
||||
this._doc.on('blur', this.onBlur);
|
||||
}
|
||||
|
||||
onPointerDown = (cm, e) => {
|
||||
onPointerDown = () => {
|
||||
this._keyEvent = "pointer";
|
||||
}
|
||||
|
||||
onFocus = (cm, e) => {
|
||||
this.props.onFocus();
|
||||
onFocus = () => {
|
||||
if (this.props.onFocus) this.props.onFocus();
|
||||
this.setState({
|
||||
isEditing: true,
|
||||
showMessage: (this._keyEvent === "keyboard"),
|
||||
@@ -98,7 +100,7 @@ export default class InputJson extends React.Component {
|
||||
|
||||
onBlur = () => {
|
||||
this._keyEvent = "keyboard";
|
||||
this.props.onBlur();
|
||||
if (this.props.onBlur) this.props.onBlur();
|
||||
this.setState({
|
||||
isEditing: false,
|
||||
showMessage: false,
|
||||
@@ -106,29 +108,29 @@ export default class InputJson extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnMount () {
|
||||
this._doc.off('change', this.onChange);
|
||||
this._doc.off('focus', this.onFocus);
|
||||
this._doc.off('blur', this.onBlur);
|
||||
this._doc!.off('change', this.onChange);
|
||||
this._doc!.off('focus', this.onFocus);
|
||||
this._doc!.off('blur', this.onBlur);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: InputJsonProps) {
|
||||
if (!this.state.isEditing && prevProps.layer !== this.props.layer) {
|
||||
this._cancelNextChange = true;
|
||||
this._doc.setValue(
|
||||
this._doc!.setValue(
|
||||
this.props.getValue(this.props.layer),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
onChange = (_e: unknown) => {
|
||||
if (this._cancelNextChange) {
|
||||
this._cancelNextChange = false;
|
||||
this.setState({
|
||||
prevValue: this._doc.getValue(),
|
||||
prevValue: this._doc!.getValue(),
|
||||
})
|
||||
return;
|
||||
}
|
||||
const newCode = this._doc.getValue();
|
||||
const newCode = this._doc!.getValue();
|
||||
|
||||
if (this.state.prevValue !== newCode) {
|
||||
let parsedLayer, err;
|
||||
@@ -139,12 +141,12 @@ export default class InputJson extends React.Component {
|
||||
console.warn(_err)
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (err && this.props.onJSONInvalid) {
|
||||
this.props.onJSONInvalid();
|
||||
}
|
||||
else {
|
||||
this.props.onChange(parsedLayer)
|
||||
this.props.onJSONValid();
|
||||
if (this.props.onChange) this.props.onChange(parsedLayer)
|
||||
if (this.props.onJSONValid) this.props.onJSONValid();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ export default class InputJson extends React.Component {
|
||||
|
||||
render() {
|
||||
const {showMessage} = this.state;
|
||||
const style = {};
|
||||
const style = {} as {maxHeight?: number};
|
||||
if (this.props.maxHeight) {
|
||||
style.maxHeight = this.props.maxHeight;
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import InputButton from './InputButton'
|
||||
|
||||
export default class InputMultiInput extends React.Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
options: PropTypes.array.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
}
|
||||
export type InputMultiInputProps = {
|
||||
name?: string
|
||||
value: string
|
||||
options: any[]
|
||||
onChange(...args: unknown[]): unknown
|
||||
'aria-label'?: string
|
||||
};
|
||||
|
||||
export default class InputMultiInput extends React.Component<InputMultiInputProps> {
|
||||
render() {
|
||||
let options = this.props.options
|
||||
if(options.length > 0 && !Array.isArray(options[0])) {
|
||||
@@ -25,7 +24,7 @@ export default class InputMultiInput extends React.Component {
|
||||
>
|
||||
<input type="radio"
|
||||
name={this.props.name}
|
||||
onChange={e => this.props.onChange(val)}
|
||||
onChange={_e => this.props.onChange(val)}
|
||||
value={val}
|
||||
checked={val === selectedValue}
|
||||
/>
|
||||
@@ -1,27 +1,35 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { BaseSyntheticEvent } from 'react'
|
||||
|
||||
let IDX = 0;
|
||||
|
||||
export default class InputNumber extends React.Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.number,
|
||||
default: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
max: PropTypes.number,
|
||||
onChange: PropTypes.func,
|
||||
allowRange: PropTypes.bool,
|
||||
rangeStep: PropTypes.number,
|
||||
wdKey: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
"aria-label": PropTypes.string,
|
||||
}
|
||||
export type InputNumberProps = {
|
||||
value?: number
|
||||
default?: number
|
||||
min?: number
|
||||
max?: number
|
||||
onChange?(...args: unknown[]): unknown
|
||||
allowRange?: boolean
|
||||
rangeStep?: number
|
||||
wdKey?: string
|
||||
required?: boolean
|
||||
"aria-label"?: string
|
||||
};
|
||||
|
||||
type InputNumberState = {
|
||||
uuid: number
|
||||
editing: boolean
|
||||
editingRange?: boolean
|
||||
value?: number
|
||||
dirtyValue?: number
|
||||
}
|
||||
|
||||
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
|
||||
static defaultProps = {
|
||||
rangeStep: 1
|
||||
}
|
||||
_keyboardEvent: boolean = false;
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: InputNumberProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
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) {
|
||||
return {
|
||||
value: props.value,
|
||||
@@ -41,16 +49,15 @@ export default class InputNumber extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
changeValue(newValue) {
|
||||
changeValue(newValue: number | string | undefined) {
|
||||
const value = (newValue === "" || newValue === undefined) ?
|
||||
undefined :
|
||||
parseFloat(newValue);
|
||||
undefined : +newValue;
|
||||
|
||||
const hasChanged = this.props.value !== value;
|
||||
if(this.isValid(value) && hasChanged) {
|
||||
this.props.onChange(value)
|
||||
if (this.props.onChange) this.props.onChange(value)
|
||||
this.setState({
|
||||
value: newValue,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
else if (!this.isValid(value) && hasChanged) {
|
||||
@@ -60,25 +67,25 @@ export default class InputNumber extends React.Component {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
dirtyValue: newValue === "" ? undefined : newValue,
|
||||
dirtyValue: newValue === "" ? undefined : value,
|
||||
})
|
||||
}
|
||||
|
||||
isValid(v) {
|
||||
isValid(v: number | string | undefined) {
|
||||
if (v === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const value = parseFloat(v)
|
||||
const value = +v;
|
||||
if(isNaN(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if(!isNaN(this.props.min) && value < this.props.min) {
|
||||
if(!isNaN(this.props.min!) && value < this.props.min!) {
|
||||
return false
|
||||
}
|
||||
|
||||
if(!isNaN(this.props.max) && value > this.props.max) {
|
||||
if(!isNaN(this.props.max!) && value > this.props.max!) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -88,7 +95,7 @@ export default class InputNumber extends React.Component {
|
||||
resetValue = () => {
|
||||
this.setState({editing: false});
|
||||
// Reset explicitly to default value if value has been cleared
|
||||
if(this.state.value === "") {
|
||||
if(!this.state.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,8 +111,8 @@ export default class InputNumber extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onChangeRange = (e) => {
|
||||
let value = parseFloat(e.target.value, 10);
|
||||
onChangeRange = (e: BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => {
|
||||
let value = parseFloat(e.target.value);
|
||||
const step = this.props.rangeStep;
|
||||
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
|
||||
// that came from a keyboard event we always want to increase by a
|
||||
// single step value.
|
||||
if (value < this.state.dirtyValue) {
|
||||
value = this.state.value - step;
|
||||
if (value < this.state.dirtyValue!) {
|
||||
value = this.state.value! - step;
|
||||
}
|
||||
else {
|
||||
value = this.state.value + step
|
||||
value = this.state.value! + step
|
||||
}
|
||||
dirtyValue = value;
|
||||
}
|
||||
@@ -140,10 +147,10 @@ export default class InputNumber extends React.Component {
|
||||
this._keyboardEvent = false;
|
||||
|
||||
// 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.props.onChange(value);
|
||||
if (this.props.onChange) this.props.onChange(value);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -196,15 +203,15 @@ export default class InputNumber extends React.Component {
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
className="maputnik-number"
|
||||
placeholder={this.props.default}
|
||||
placeholder={this.props.default?.toString()}
|
||||
value={inputValue === undefined ? "" : inputValue}
|
||||
onFocus={e => {
|
||||
onFocus={_e => {
|
||||
this.setState({editing: true});
|
||||
}}
|
||||
onChange={e => {
|
||||
this.changeValue(e.target.value);
|
||||
}}
|
||||
onBlur={e => {
|
||||
onBlur={_e => {
|
||||
this.setState({editing: false});
|
||||
this.resetValue()
|
||||
}}
|
||||
@@ -218,7 +225,7 @@ export default class InputNumber extends React.Component {
|
||||
aria-label={this.props['aria-label']}
|
||||
spellCheck="false"
|
||||
className="maputnik-number"
|
||||
placeholder={this.props.default}
|
||||
placeholder={this.props.default?.toString()}
|
||||
value={value === undefined ? "" : value}
|
||||
onChange={e => this.changeValue(e.target.value)}
|
||||
onFocus={() => {
|
||||
@@ -1,22 +1,20 @@
|
||||
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() {
|
||||
let options = this.props.options
|
||||
let options = this.props.options;
|
||||
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
|
||||
@@ -1,139 +0,0 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import InputColor from './InputColor'
|
||||
import InputNumber from './InputNumber'
|
||||
import InputCheckbox from './InputCheckbox'
|
||||
import InputString from './InputString'
|
||||
import InputSelect from './InputSelect'
|
||||
import InputMultiInput from './InputMultiInput'
|
||||
import InputArray from './InputArray'
|
||||
import InputDynamicArray from './InputDynamicArray'
|
||||
import InputFont from './InputFont'
|
||||
import InputAutocomplete from './InputAutocomplete'
|
||||
import InputEnum from './InputEnum'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
|
||||
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
||||
|
||||
function labelFromFieldName(fieldName) {
|
||||
let label = fieldName.split('-').slice(1).join(' ')
|
||||
if(label.length > 0) {
|
||||
label = label.charAt(0).toUpperCase() + label.slice(1);
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
function optionsLabelLength(options) {
|
||||
let sum = 0;
|
||||
options.forEach(([_, label]) => {
|
||||
sum += label.length
|
||||
})
|
||||
return sum
|
||||
}
|
||||
|
||||
/** Display any field from the Maplibre GL style spec and
|
||||
* choose the correct field component based on the @{fieldSpec}
|
||||
* to display @{value}. */
|
||||
export default class SpecField extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
fieldName: PropTypes.string.isRequired,
|
||||
fieldSpec: PropTypes.object.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.array,
|
||||
PropTypes.bool
|
||||
]),
|
||||
/** Override the style of the field */
|
||||
style: PropTypes.object,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
|
||||
render() {
|
||||
const commonProps = {
|
||||
error: this.props.error,
|
||||
fieldSpec: this.props.fieldSpec,
|
||||
label: this.props.label,
|
||||
action: this.props.action,
|
||||
style: this.props.style,
|
||||
value: this.props.value,
|
||||
default: this.props.fieldSpec.default,
|
||||
name: this.props.fieldName,
|
||||
onChange: newValue => this.props.onChange(this.props.fieldName, newValue),
|
||||
'aria-label': this.props['aria-label'],
|
||||
}
|
||||
|
||||
function childNodes() {
|
||||
switch(this.props.fieldSpec.type) {
|
||||
case 'number': return (
|
||||
<InputNumber
|
||||
{...commonProps}
|
||||
min={this.props.fieldSpec.minimum}
|
||||
max={this.props.fieldSpec.maximum}
|
||||
/>
|
||||
)
|
||||
case 'enum':
|
||||
const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)])
|
||||
|
||||
return <InputEnum
|
||||
{...commonProps}
|
||||
options={options}
|
||||
/>
|
||||
case 'resolvedImage':
|
||||
case 'formatted':
|
||||
case 'string':
|
||||
if (iconProperties.indexOf(this.props.fieldName) >= 0) {
|
||||
const options = this.props.fieldSpec.values || [];
|
||||
return <InputAutocomplete
|
||||
{...commonProps}
|
||||
options={options.map(f => [f, f])}
|
||||
/>
|
||||
} else {
|
||||
return <InputString
|
||||
{...commonProps}
|
||||
/>
|
||||
}
|
||||
case 'color': return (
|
||||
<InputColor
|
||||
{...commonProps}
|
||||
/>
|
||||
)
|
||||
case 'boolean': return (
|
||||
<InputCheckbox
|
||||
{...commonProps}
|
||||
/>
|
||||
)
|
||||
case 'array':
|
||||
if(this.props.fieldName === 'text-font') {
|
||||
return <InputFont
|
||||
{...commonProps}
|
||||
fonts={this.props.fieldSpec.values}
|
||||
/>
|
||||
} else {
|
||||
if (this.props.fieldSpec.length) {
|
||||
return <InputArray
|
||||
{...commonProps}
|
||||
type={this.props.fieldSpec.value}
|
||||
length={this.props.fieldSpec.length}
|
||||
/>
|
||||
} else {
|
||||
return <InputDynamicArray
|
||||
{...commonProps}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
type={this.props.fieldSpec.value}
|
||||
/>
|
||||
}
|
||||
}
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-wd-key={"spec-field:"+this.props.fieldName}>
|
||||
{childNodes.call(this)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
126
src/components/InputSpec.tsx
Normal file
126
src/components/InputSpec.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
|
||||
import InputColor, { InputColorProps } from './InputColor'
|
||||
import InputNumber, { InputNumberProps } from './InputNumber'
|
||||
import InputCheckbox, { InputCheckboxProps } from './InputCheckbox'
|
||||
import InputString, { InputStringProps } from './InputString'
|
||||
import InputArray, { FieldArrayProps } from './InputArray'
|
||||
import InputDynamicArray, { FieldDynamicArrayProps } from './InputDynamicArray'
|
||||
import InputFont, { FieldFontProps } from './InputFont'
|
||||
import InputAutocomplete, { InputAutocompleteProps } from './InputAutocomplete'
|
||||
import InputEnum, { InputEnumProps } from './InputEnum'
|
||||
import capitalize from 'lodash.capitalize'
|
||||
|
||||
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
||||
|
||||
export type SpecFieldProps = {
|
||||
onChange(...args: unknown[]): unknown
|
||||
fieldName: string
|
||||
fieldSpec: {
|
||||
default?: unknown
|
||||
type: 'number' | 'enum' | 'resolvedImage' | 'formatted' | 'string' | 'color' | 'boolean' | 'array'
|
||||
minimum?: number
|
||||
maximum?: number
|
||||
values?: unknown[]
|
||||
length?: number
|
||||
value?: string
|
||||
}
|
||||
value?: string | number | unknown[] | boolean
|
||||
/** Override the style of the field */
|
||||
style?: object
|
||||
'aria-label'?: string
|
||||
error: unknown[]
|
||||
label: string
|
||||
action: ReactElement
|
||||
};
|
||||
|
||||
/** Display any field from the Maplibre GL style spec and
|
||||
* choose the correct field component based on the @{fieldSpec}
|
||||
* to display @{value}. */
|
||||
export default class SpecField extends React.Component<SpecFieldProps> {
|
||||
|
||||
childNodes() {
|
||||
const commonProps = {
|
||||
error: this.props.error,
|
||||
fieldSpec: this.props.fieldSpec,
|
||||
label: this.props.label,
|
||||
action: this.props.action,
|
||||
style: this.props.style,
|
||||
value: this.props.value,
|
||||
default: this.props.fieldSpec.default,
|
||||
name: this.props.fieldName,
|
||||
onChange: (newValue: string) => this.props.onChange(this.props.fieldName, newValue),
|
||||
'aria-label': this.props['aria-label'],
|
||||
}
|
||||
switch(this.props.fieldSpec.type) {
|
||||
case 'number': return (
|
||||
<InputNumber
|
||||
{...commonProps as InputNumberProps}
|
||||
min={this.props.fieldSpec.minimum}
|
||||
max={this.props.fieldSpec.maximum}
|
||||
/>
|
||||
)
|
||||
case 'enum':
|
||||
const options = Object.keys(this.props.fieldSpec.values || []).map(v => [v, capitalize(v)])
|
||||
|
||||
return <InputEnum
|
||||
{...commonProps as Omit<InputEnumProps, "options">}
|
||||
options={options}
|
||||
/>
|
||||
case 'resolvedImage':
|
||||
case 'formatted':
|
||||
case 'string':
|
||||
if (iconProperties.indexOf(this.props.fieldName) >= 0) {
|
||||
const options = this.props.fieldSpec.values || [];
|
||||
return <InputAutocomplete
|
||||
{...commonProps as Omit<InputAutocompleteProps, "options">}
|
||||
options={options.map(f => [f, f])}
|
||||
/>
|
||||
} else {
|
||||
return <InputString
|
||||
{...commonProps as InputStringProps}
|
||||
/>
|
||||
}
|
||||
case 'color': return (
|
||||
<InputColor
|
||||
{...commonProps as InputColorProps}
|
||||
/>
|
||||
)
|
||||
case 'boolean': return (
|
||||
<InputCheckbox
|
||||
{...commonProps as InputCheckboxProps}
|
||||
/>
|
||||
)
|
||||
case 'array':
|
||||
if(this.props.fieldName === 'text-font') {
|
||||
return <InputFont
|
||||
{...commonProps as FieldFontProps}
|
||||
fonts={this.props.fieldSpec.values}
|
||||
/>
|
||||
} else {
|
||||
if (this.props.fieldSpec.length) {
|
||||
return <InputArray
|
||||
{...commonProps as FieldArrayProps}
|
||||
type={this.props.fieldSpec.value}
|
||||
length={this.props.fieldSpec.length}
|
||||
/>
|
||||
} else {
|
||||
return <InputDynamicArray
|
||||
{...commonProps as FieldDynamicArrayProps}
|
||||
fieldSpec={this.props.fieldSpec}
|
||||
type={this.props.fieldSpec.value as FieldDynamicArrayProps['type']}
|
||||
/>
|
||||
}
|
||||
}
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div data-wd-key={"spec-field:"+this.props.fieldName}>
|
||||
{this.childNodes()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,30 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class InputString extends React.Component {
|
||||
static propTypes = {
|
||||
"data-wd-key": PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
default: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onInput: PropTypes.func,
|
||||
multi: PropTypes.bool,
|
||||
required: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
spellCheck: PropTypes.bool,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
export type InputStringProps = {
|
||||
"data-wd-key"?: string
|
||||
value?: string
|
||||
style?: object
|
||||
default?: string
|
||||
onChange?(...args: unknown[]): unknown
|
||||
onInput?(...args: unknown[]): unknown
|
||||
multi?: boolean
|
||||
required?: boolean
|
||||
disabled?: boolean
|
||||
spellCheck?: boolean
|
||||
'aria-label'?: string
|
||||
};
|
||||
|
||||
type InputStringState = {
|
||||
editing: boolean
|
||||
value?: string
|
||||
}
|
||||
|
||||
export default class InputString extends React.Component<InputStringProps, InputStringState> {
|
||||
static defaultProps = {
|
||||
onInput: () => {},
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: InputStringProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
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) {
|
||||
return {
|
||||
value: props.value
|
||||
@@ -68,22 +72,22 @@ export default class InputString extends React.Component {
|
||||
style: this.props.style,
|
||||
value: this.state.value === undefined ? "" : this.state.value,
|
||||
placeholder: this.props.default,
|
||||
onChange: e => {
|
||||
onChange: (e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => {
|
||||
this.setState({
|
||||
editing: true,
|
||||
value: e.target.value
|
||||
}, () => {
|
||||
this.props.onInput(this.state.value);
|
||||
if (this.props.onInput) this.props.onInput(this.state.value);
|
||||
});
|
||||
},
|
||||
onBlur: () => {
|
||||
if(this.state.value!==this.props.value) {
|
||||
this.setState({editing: false});
|
||||
this.props.onChange(this.state.value);
|
||||
if (this.props.onChange) this.props.onChange(this.state.value);
|
||||
}
|
||||
},
|
||||
onKeyDown: (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
if (e.keyCode === 13 && this.props.onChange) {
|
||||
this.props.onChange(this.state.value);
|
||||
}
|
||||
},
|
||||
@@ -1,16 +1,15 @@
|
||||
import React, {Fragment} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import InputString from './InputString'
|
||||
import SmallError from './SmallError'
|
||||
|
||||
|
||||
function validate (url) {
|
||||
function validate(url: string) {
|
||||
if (url === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
let error;
|
||||
const getProtocol = (url) => {
|
||||
const getProtocol = (url: string) => {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.protocol;
|
||||
@@ -48,38 +47,44 @@ function validate (url) {
|
||||
return error;
|
||||
}
|
||||
|
||||
export default class FieldUrl extends React.Component {
|
||||
static propTypes = {
|
||||
"data-wd-key": PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
default: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onInput: PropTypes.func,
|
||||
multi: PropTypes.bool,
|
||||
required: PropTypes.bool,
|
||||
'aria-label': PropTypes.string,
|
||||
}
|
||||
export type FieldUrlProps = {
|
||||
"data-wd-key"?: string
|
||||
value: string
|
||||
style?: object
|
||||
default?: string
|
||||
onChange(...args: unknown[]): unknown
|
||||
onInput?(...args: unknown[]): unknown
|
||||
multi?: boolean
|
||||
required?: boolean
|
||||
'aria-label'?: string
|
||||
type?: string
|
||||
className?: string
|
||||
};
|
||||
|
||||
type FieldUrlState = {
|
||||
error?: React.ReactNode
|
||||
}
|
||||
|
||||
export default class FieldUrl extends React.Component<FieldUrlProps, FieldUrlState> {
|
||||
static defaultProps = {
|
||||
onInput: () => {},
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
constructor (props: FieldUrlProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: validate(props.value)
|
||||
};
|
||||
}
|
||||
|
||||
onInput = (url) => {
|
||||
onInput = (url: string) => {
|
||||
this.setState({
|
||||
error: validate(url)
|
||||
});
|
||||
this.props.onInput(url);
|
||||
if (this.props.onInput) this.props.onInput(url);
|
||||
}
|
||||
|
||||
onChange = (url) => {
|
||||
onChange = (url: string) => {
|
||||
this.setState({
|
||||
error: validate(url)
|
||||
});
|
||||
@@ -1,22 +1,21 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {MdClose} from 'react-icons/md'
|
||||
import AriaModal from 'react-aria-modal'
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
||||
export default class Modal extends React.Component {
|
||||
static propTypes = {
|
||||
"data-wd-key": PropTypes.string,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onOpenToggle: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
underlayClickExits: PropTypes.bool,
|
||||
underlayProps: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
}
|
||||
type ModalProps = {
|
||||
"data-wd-key"?: string
|
||||
isOpen: boolean
|
||||
title: string
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
underlayClickExits?: boolean
|
||||
underlayProps?: any
|
||||
className?: string
|
||||
};
|
||||
|
||||
|
||||
export default class Modal extends React.Component<ModalProps> {
|
||||
static defaultProps = {
|
||||
underlayClickExits: true
|
||||
}
|
||||
@@ -24,7 +23,7 @@ export default class Modal extends React.Component {
|
||||
// See <https://github.com/maputnik/editor/issues/416>
|
||||
onClose = () => {
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -37,6 +36,7 @@ export default class Modal extends React.Component {
|
||||
return <AriaModal
|
||||
titleText={this.props.title}
|
||||
underlayClickExits={this.props.underlayClickExits}
|
||||
// @ts-ignore
|
||||
underlayProps={this.props.underlayProps}
|
||||
data-wd-key={this.props["data-wd-key"]}
|
||||
verticallyCenter={true}
|
||||
@@ -1,19 +1,19 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import InputButton from './InputButton'
|
||||
import Modal from './Modal'
|
||||
|
||||
|
||||
export default class ModalLoading extends React.Component {
|
||||
static propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
message: PropTypes.node.isRequired,
|
||||
}
|
||||
type ModalLoadingProps = {
|
||||
isOpen: boolean
|
||||
onCancel(...args: unknown[]): unknown
|
||||
title: string
|
||||
message: React.ReactNode
|
||||
};
|
||||
|
||||
underlayOnClick(e) {
|
||||
|
||||
export default class ModalLoading extends React.Component<ModalLoadingProps> {
|
||||
underlayOnClick(e: Event) {
|
||||
// This stops click events falling through to underlying modals.
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -24,9 +24,9 @@ export default class ModalLoading extends React.Component {
|
||||
isOpen={this.props.isOpen}
|
||||
underlayClickExits={false}
|
||||
underlayProps={{
|
||||
onClick: (e) => underlayProps(e)
|
||||
// @ts-ignore
|
||||
onClick: (e: Event) => underlayProps(e)
|
||||
}}
|
||||
closeable={false}
|
||||
title={this.props.title}
|
||||
onOpenToggle={() => this.props.onCancel()}
|
||||
>
|
||||
@@ -1,25 +1,24 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { FormEvent } from 'react'
|
||||
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 Modal from './Modal'
|
||||
import InputButton from './InputButton'
|
||||
import FileReaderInput from 'react-file-reader-input'
|
||||
import InputUrl from './InputUrl'
|
||||
|
||||
import {MdFileUpload} from 'react-icons/md'
|
||||
import {MdAddCircleOutline} from 'react-icons/md'
|
||||
|
||||
import style from '../libs/style'
|
||||
import publicStyles from '../config/styles.json'
|
||||
|
||||
class PublicStyle extends React.Component {
|
||||
static propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
thumbnailUrl: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
}
|
||||
type PublicStyleProps = {
|
||||
url: string
|
||||
thumbnailUrl: string
|
||||
title: string
|
||||
onSelect(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
class PublicStyle extends React.Component<PublicStyleProps> {
|
||||
render() {
|
||||
return <div className="maputnik-public-style">
|
||||
<InputButton
|
||||
@@ -43,14 +42,21 @@ class PublicStyle extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default class ModalOpen extends React.Component {
|
||||
static propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onOpenToggle: PropTypes.func.isRequired,
|
||||
onStyleOpen: PropTypes.func.isRequired,
|
||||
}
|
||||
type ModalOpenProps = {
|
||||
isOpen: boolean
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
onStyleOpen(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
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);
|
||||
this.state = {
|
||||
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
|
||||
if(e) e.stopPropagation();
|
||||
|
||||
@@ -76,12 +82,12 @@ export default class ModalOpen extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onStyleSelect = (styleUrl) => {
|
||||
onStyleSelect = (styleUrl: string) => {
|
||||
this.clearError();
|
||||
|
||||
let canceled;
|
||||
let canceled: boolean = false;
|
||||
|
||||
const activeRequest = fetch(styleUrl, {
|
||||
fetch(styleUrl, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
@@ -123,13 +129,13 @@ export default class ModalOpen extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
onSubmitUrl = (e) => {
|
||||
onSubmitUrl = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
this.onStyleSelect(this.state.styleUrl);
|
||||
}
|
||||
|
||||
onUpload = (_, files) => {
|
||||
const [e, file] = files[0];
|
||||
onUpload = (_: any, files: Result[]) => {
|
||||
const [_e, file] = files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
this.clearError();
|
||||
@@ -138,11 +144,11 @@ export default class ModalOpen extends React.Component {
|
||||
reader.onload = e => {
|
||||
let mapStyle;
|
||||
try {
|
||||
mapStyle = JSON.parse(e.target.result)
|
||||
mapStyle = JSON.parse(e.target?.result as string)
|
||||
}
|
||||
catch(err) {
|
||||
this.setState({
|
||||
error: err.toString()
|
||||
error: (err as Error).toString()
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -161,7 +167,7 @@ export default class ModalOpen extends React.Component {
|
||||
this.props.onOpenToggle();
|
||||
}
|
||||
|
||||
onChangeUrl = (url) => {
|
||||
onChangeUrl = (url: string) => {
|
||||
this.setState({
|
||||
styleUrl: url,
|
||||
});
|
||||
@@ -200,7 +206,7 @@ export default class ModalOpen extends React.Component {
|
||||
<section className="maputnik-modal-section">
|
||||
<h1>Upload Style</h1>
|
||||
<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>
|
||||
</FileReaderInput>
|
||||
</section>
|
||||
@@ -246,7 +252,7 @@ export default class ModalOpen extends React.Component {
|
||||
<ModalLoading
|
||||
isOpen={!!this.state.activeRequest}
|
||||
title={'Loading style'}
|
||||
onCancel={(e) => this.onCancelActiveRequest(e)}
|
||||
onCancel={(e: Event) => this.onCancelActiveRequest(e)}
|
||||
message={"Loading: "+this.state.activeRequestUrl}
|
||||
/>
|
||||
</div>
|
||||
@@ -1,8 +1,6 @@
|
||||
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 FieldNumber from './FieldNumber'
|
||||
import FieldString from './FieldString'
|
||||
@@ -13,16 +11,16 @@ import FieldColor from './FieldColor'
|
||||
import Modal from './Modal'
|
||||
import fieldSpecAdditional from '../libs/field-spec-additional'
|
||||
|
||||
export default class ModalSettings extends React.Component {
|
||||
static propTypes = {
|
||||
mapStyle: PropTypes.object.isRequired,
|
||||
onStyleChanged: PropTypes.func.isRequired,
|
||||
onChangeMetadataProperty: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onOpenToggle: PropTypes.func.isRequired,
|
||||
}
|
||||
type ModalSettingsProps = {
|
||||
mapStyle: StyleSpecification
|
||||
onStyleChanged(...args: unknown[]): unknown
|
||||
onChangeMetadataProperty(...args: unknown[]): unknown
|
||||
isOpen: boolean
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
changeTransitionProperty(property, value) {
|
||||
export default class ModalSettings extends React.Component<ModalSettingsProps> {
|
||||
changeTransitionProperty(property: keyof TransitionSpecification, value: number | undefined) {
|
||||
const 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 = {
|
||||
...this.props.mapStyle.light,
|
||||
}
|
||||
@@ -49,6 +47,7 @@ export default class ModalSettings extends React.Component {
|
||||
delete light[property];
|
||||
}
|
||||
else {
|
||||
// @ts-ignore
|
||||
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 = {
|
||||
...this.props.mapStyle,
|
||||
};
|
||||
|
||||
if (value === undefined) {
|
||||
// @ts-ignore
|
||||
delete changedStyle[property];
|
||||
}
|
||||
else {
|
||||
// @ts-ignore
|
||||
changedStyle[property] = value;
|
||||
}
|
||||
this.props.onStyleChanged(changedStyle);
|
||||
}
|
||||
|
||||
render() {
|
||||
const metadata = this.props.mapStyle.metadata || {}
|
||||
const metadata = this.props.mapStyle.metadata || {} as any;
|
||||
const {onChangeMetadataProperty, mapStyle} = this.props;
|
||||
const inputProps = { }
|
||||
|
||||
@@ -98,14 +99,14 @@ export default class ModalSettings extends React.Component {
|
||||
label={"Owner"}
|
||||
fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}}
|
||||
data-wd-key="modal:settings.owner"
|
||||
value={this.props.mapStyle.owner}
|
||||
value={(this.props.mapStyle as any).owner}
|
||||
onChange={this.changeStyleProperty.bind(this, "owner")}
|
||||
/>
|
||||
<FieldUrl {...inputProps}
|
||||
fieldSpec={latest.$root.sprite}
|
||||
label="Sprite URL"
|
||||
data-wd-key="modal:settings.sprite"
|
||||
value={this.props.mapStyle.sprite}
|
||||
value={this.props.mapStyle.sprite as string}
|
||||
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
||||
/>
|
||||
|
||||
@@ -113,7 +114,7 @@ export default class ModalSettings extends React.Component {
|
||||
label="Glyphs URL"
|
||||
fieldSpec={latest.$root.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")}
|
||||
/>
|
||||
|
||||
@@ -138,7 +139,7 @@ export default class ModalSettings extends React.Component {
|
||||
fieldSpec={latest.$root.center}
|
||||
length={2}
|
||||
type="number"
|
||||
value={mapStyle.center}
|
||||
value={mapStyle.center || []}
|
||||
default={latest.$root.center.default || [0, 0]}
|
||||
onChange={this.changeStyleProperty.bind(this, "center")}
|
||||
/>
|
||||
@@ -175,7 +176,7 @@ export default class ModalSettings extends React.Component {
|
||||
label={"Light anchor"}
|
||||
fieldSpec={latest.light.anchor}
|
||||
name="light-anchor"
|
||||
value={light.anchor}
|
||||
value={light.anchor as string}
|
||||
options={Object.keys(latest.light.anchor.values)}
|
||||
default={latest.light.anchor.default}
|
||||
onChange={this.changeLightProperty.bind(this, "anchor")}
|
||||
@@ -185,7 +186,7 @@ export default class ModalSettings extends React.Component {
|
||||
{...inputProps}
|
||||
label={"Light color"}
|
||||
fieldSpec={latest.light.color}
|
||||
value={light.color}
|
||||
value={light.color as string}
|
||||
default={latest.light.color.default}
|
||||
onChange={this.changeLightProperty.bind(this, "color")}
|
||||
/>
|
||||
@@ -194,7 +195,7 @@ export default class ModalSettings extends React.Component {
|
||||
{...inputProps}
|
||||
label={"Light intensity"}
|
||||
fieldSpec={latest.light.intensity}
|
||||
value={light.intensity}
|
||||
value={light.intensity as number}
|
||||
default={latest.light.intensity.default}
|
||||
onChange={this.changeLightProperty.bind(this, "intensity")}
|
||||
/>
|
||||
@@ -205,7 +206,7 @@ export default class ModalSettings extends React.Component {
|
||||
fieldSpec={latest.light.position}
|
||||
type="number"
|
||||
length={latest.light.position.length}
|
||||
value={light.position}
|
||||
value={light.position as number[]}
|
||||
default={latest.light.position.default}
|
||||
onChange={this.changeLightProperty.bind(this, "position")}
|
||||
/>
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Modal from './Modal'
|
||||
|
||||
|
||||
export default class ModalShortcuts extends React.Component {
|
||||
static propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onOpenToggle: PropTypes.func.isRequired,
|
||||
}
|
||||
type ModalShortcutsProps = {
|
||||
isOpen: boolean
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
|
||||
export default class ModalShortcuts extends React.Component<ModalShortcutsProps> {
|
||||
render() {
|
||||
const help = [
|
||||
{
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import InputButton from './InputButton'
|
||||
import Modal from './Modal'
|
||||
|
||||
// @ts-ignore
|
||||
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
||||
|
||||
export default class ModalSurvey extends React.Component {
|
||||
static propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onOpenToggle: PropTypes.func.isRequired,
|
||||
}
|
||||
type ModalSurveyProps = {
|
||||
isOpen: boolean
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
};
|
||||
|
||||
export default class ModalSurvey extends React.Component<ModalSurveyProps> {
|
||||
onClick = () => {
|
||||
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
|
||||
|
||||
@@ -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
src/components/ScrollContainer.tsx
Normal file
14
src/components/ScrollContainer.tsx
Normal 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 PropTypes from 'prop-types'
|
||||
|
||||
import {otherFilterOps} from '../libs/filterops'
|
||||
import InputString from './InputString'
|
||||
import InputAutocomplete from './InputAutocomplete'
|
||||
import InputSelect from './InputSelect'
|
||||
|
||||
function tryParseInt(v) {
|
||||
function tryParseInt(v: string | number) {
|
||||
if (v === '') return v
|
||||
if (isNaN(v)) return v
|
||||
return parseFloat(v)
|
||||
if (isNaN(v as number)) return v
|
||||
return parseFloat(v as string)
|
||||
}
|
||||
|
||||
function tryParseBool(v) {
|
||||
function tryParseBool(v: string | boolean) {
|
||||
const isString = (typeof(v) === "string");
|
||||
if(!isString) {
|
||||
return v;
|
||||
@@ -29,24 +28,24 @@ function tryParseBool(v) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseFilter(v) {
|
||||
v = tryParseInt(v);
|
||||
v = tryParseBool(v);
|
||||
function parseFilter(v: string | boolean | number) {
|
||||
v = tryParseInt(v as any);
|
||||
v = tryParseBool(v as any);
|
||||
return v;
|
||||
}
|
||||
|
||||
export default class SingleFilterEditor extends React.Component {
|
||||
static propTypes = {
|
||||
filter: PropTypes.array.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
properties: PropTypes.object,
|
||||
}
|
||||
type SingleFilterEditorProps = {
|
||||
filter: any[]
|
||||
onChange(...args: unknown[]): unknown
|
||||
properties?: {[key: string]: string}
|
||||
};
|
||||
|
||||
export default class SingleFilterEditor extends React.Component<SingleFilterEditorProps> {
|
||||
static defaultProps = {
|
||||
properties: {},
|
||||
}
|
||||
|
||||
onFilterPartChanged(filterOp, propertyName, filterArgs) {
|
||||
onFilterPartChanged(filterOp: string, propertyName: string, filterArgs: string[]) {
|
||||
let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)]
|
||||
if(filterOp === 'has' || filterOp === '!has') {
|
||||
newFilter = [filterOp, propertyName]
|
||||
@@ -67,15 +66,15 @@ export default class SingleFilterEditor extends React.Component {
|
||||
<InputAutocomplete
|
||||
aria-label="key"
|
||||
value={propertyName}
|
||||
options={Object.keys(this.props.properties).map(propName => [propName, propName])}
|
||||
onChange={newPropertyName => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)}
|
||||
options={Object.keys(this.props.properties!).map(propName => [propName, propName])}
|
||||
onChange={(newPropertyName: string) => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)}
|
||||
/>
|
||||
</div>
|
||||
<div className="maputnik-filter-editor-operator">
|
||||
<InputSelect
|
||||
aria-label="function"
|
||||
value={filterOp}
|
||||
onChange={newFilterOp => this.onFilterPartChanged(newFilterOp, propertyName, filterArgs)}
|
||||
onChange={(newFilterOp: string) => this.onFilterPartChanged(newFilterOp, propertyName, filterArgs)}
|
||||
options={otherFilterOps}
|
||||
/>
|
||||
</div>
|
||||
@@ -84,12 +83,11 @@ export default class SingleFilterEditor extends React.Component {
|
||||
<InputString
|
||||
aria-label="value"
|
||||
value={filterArgs.join(',')}
|
||||
onChange={ v=> this.onFilterPartChanged(filterOp, propertyName, v.split(','))}
|
||||
onChange={(v: string) => this.onFilterPartChanged(filterOp, propertyName, v.split(','))}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import './SmallError.scss';
|
||||
|
||||
|
||||
export default class SmallError extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
}
|
||||
type SmallErrorProps = {
|
||||
children?: React.ReactNode
|
||||
};
|
||||
|
||||
|
||||
export default class SmallError extends React.Component<SmallErrorProps> {
|
||||
render () {
|
||||
return (
|
||||
<div className="SmallError">
|
||||
@@ -15,5 +15,4 @@ export default class SmallError extends React.Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Block from './Block'
|
||||
import InputSpec from './InputSpec'
|
||||
import InputSpec, { SpecFieldProps as InputFieldSpecProps } from './InputSpec'
|
||||
import Fieldset from './Fieldset'
|
||||
|
||||
|
||||
const typeMap = {
|
||||
color: () => Block,
|
||||
enum: ({fieldSpec}) => (Object.keys(fieldSpec.values).length <= 3 ? Fieldset : Block),
|
||||
enum: ({fieldSpec}: any) => (Object.keys(fieldSpec.values).length <= 3 ? Fieldset : Block),
|
||||
boolean: () => Block,
|
||||
array: () => Fieldset,
|
||||
resolvedImage: () => Block,
|
||||
@@ -16,12 +15,11 @@ const typeMap = {
|
||||
formatted: () => Block,
|
||||
};
|
||||
|
||||
export default class SpecField extends React.Component {
|
||||
static propTypes = {
|
||||
...InputSpec.propTypes,
|
||||
name: PropTypes.string,
|
||||
}
|
||||
type SpecFieldProps = InputFieldSpecProps & {
|
||||
name?: string
|
||||
};
|
||||
|
||||
export default class SpecField extends React.Component<SpecFieldProps> {
|
||||
render() {
|
||||
const {props} = this;
|
||||
|
||||
Reference in New Issue
Block a user