Migration of jsx files to tsx 2 (#850)

This is to continue the work of migrating all the jsx files into tsx
files.
The MO is basically described here: #848.

About 7 files to go...
This commit is contained in:
Harel M
2023-12-22 23:32:25 +02:00
committed by GitHub
parent fa182e66fa
commit 974dd7bfd9
57 changed files with 827 additions and 827 deletions
+17
View File
@@ -70,12 +70,14 @@
"@types/codemirror": "^5.60.15", "@types/codemirror": "^5.60.15",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/file-saver": "^2.0.7",
"@types/lodash.capitalize": "^4.2.9", "@types/lodash.capitalize": "^4.2.9",
"@types/lodash.isequal": "^4.5.8", "@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.9", "@types/lodash.throttle": "^4.1.9",
"@types/react": "^16.14.52", "@types/react": "^16.14.52",
"@types/react-aria-modal": "^4.0.9", "@types/react-aria-modal": "^4.0.9",
"@types/react-autocomplete": "^1.8.9", "@types/react-autocomplete": "^1.8.9",
"@types/react-collapse": "^5.0.4",
"@types/react-color": "^3.0.10", "@types/react-color": "^3.0.10",
"@types/react-dom": "^16.9.24", "@types/react-dom": "^16.9.24",
"@types/react-file-reader-input": "^2.0.4", "@types/react-file-reader-input": "^2.0.4",
@@ -4711,6 +4713,12 @@
"@types/send": "*" "@types/send": "*"
} }
}, },
"node_modules/@types/file-saver": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
"dev": true
},
"node_modules/@types/find-cache-dir": { "node_modules/@types/find-cache-dir": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz",
@@ -4899,6 +4907,15 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-collapse": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/react-collapse/-/react-collapse-5.0.4.tgz",
"integrity": "sha512-tM5cVB6skGLneNYnRK2E3R56VOHguSeJQHslGPTIMC58ytL3oelT8L/l1onkwHGn5vSEs2BEq2Olzrur+YdliA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-color": { "node_modules/@types/react-color": {
"version": "3.0.10", "version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.10.tgz",
+2
View File
@@ -99,12 +99,14 @@
"@types/codemirror": "^5.60.15", "@types/codemirror": "^5.60.15",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/file-saver": "^2.0.7",
"@types/lodash.capitalize": "^4.2.9", "@types/lodash.capitalize": "^4.2.9",
"@types/lodash.isequal": "^4.5.8", "@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.9", "@types/lodash.throttle": "^4.1.9",
"@types/react": "^16.14.52", "@types/react": "^16.14.52",
"@types/react-aria-modal": "^4.0.9", "@types/react-aria-modal": "^4.0.9",
"@types/react-autocomplete": "^1.8.9", "@types/react-autocomplete": "^1.8.9",
"@types/react-collapse": "^5.0.4",
"@types/react-color": "^3.0.10", "@types/react-color": "^3.0.10",
"@types/react-dom": "^16.9.24", "@types/react-dom": "^16.9.24",
"@types/react-file-reader-input": "^2.0.4", "@types/react-file-reader-input": "^2.0.4",
@@ -2,16 +2,16 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import ScrollContainer from './ScrollContainer' import ScrollContainer from './ScrollContainer'
class AppLayout extends React.Component { type AppLayoutProps = {
static propTypes = { toolbar: React.ReactElement
toolbar: PropTypes.element.isRequired, layerList: React.ReactElement
layerList: PropTypes.element.isRequired, layerEditor?: React.ReactElement
layerEditor: PropTypes.element, map: React.ReactElement
map: PropTypes.element.isRequired, bottom?: React.ReactElement
bottom: PropTypes.element, modals?: React.ReactNode
modals: PropTypes.node, };
}
class AppLayout extends React.Component<AppLayoutProps> {
static childContextTypes = { static childContextTypes = {
reactIconBase: PropTypes.object reactIconBase: PropTypes.object
} }
@@ -1,29 +1,28 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {formatLayerId} from '../util/format'; import {formatLayerId} from '../util/format';
import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
export default class AppMessagePanel extends React.Component { type AppMessagePanelProps = {
static propTypes = { errors?: unknown[]
errors: PropTypes.array, infos?: unknown[]
infos: PropTypes.array, mapStyle?: StyleSpecification
mapStyle: PropTypes.object, onLayerSelect?(...args: unknown[]): unknown
onLayerSelect: PropTypes.func, currentLayer?: object
currentLayer: PropTypes.object, selectedLayerIndex?: number
selectedLayerIndex: PropTypes.number, };
}
export default class AppMessagePanel extends React.Component<AppMessagePanelProps> {
static defaultProps = { static defaultProps = {
onLayerSelect: () => {}, onLayerSelect: () => {},
} }
render() { render() {
const {selectedLayerIndex} = this.props; const {selectedLayerIndex} = this.props;
const errors = this.props.errors.map((error, idx) => { const errors = this.props.errors?.map((error: any, idx) => {
let content; let content;
if (error.parsed && error.parsed.type === "layer") { if (error.parsed && error.parsed.type === "layer") {
const {parsed} = error; const {parsed} = error;
const {mapStyle, currentLayer} = this.props; const layerId = this.props.mapStyle?.layers[parsed.data.index].id;
const layerId = mapStyle.layers[parsed.data.index].id;
content = ( content = (
<> <>
Layer <span>{formatLayerId(layerId)}</span>: {parsed.data.message} Layer <span>{formatLayerId(layerId)}</span>: {parsed.data.message}
@@ -32,7 +31,7 @@ export default class AppMessagePanel extends React.Component {
&nbsp;&mdash;&nbsp; &nbsp;&mdash;&nbsp;
<button <button
className="maputnik-message-panel__switch-button" className="maputnik-message-panel__switch-button"
onClick={() => this.props.onLayerSelect(parsed.data.index)} onClick={() => this.props.onLayerSelect!(parsed.data.index)}
> >
switch to layer switch to layer
</button> </button>
@@ -49,7 +48,7 @@ export default class AppMessagePanel extends React.Component {
</p> </p>
}) })
const infos = this.props.infos.map((m, i) => { const infos = this.props.infos?.map((m, i) => {
return <p key={"info-"+i}>{m}</p> return <p key={"info-"+i}>{m}</p>
}) })
@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import {detect} from 'detect-browser'; import {detect} from 'detect-browser';
@@ -9,27 +8,28 @@ import pkgJson from '../../package.json'
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of. // This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
const browser = detect(); const browser = detect();
const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser.name) > -1; const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser!.name) > -1;
class IconText extends React.Component { type IconTextProps = {
static propTypes = { children?: React.ReactNode
children: PropTypes.node, };
}
class IconText extends React.Component<IconTextProps> {
render() { render() {
return <span className="maputnik-icon-text">{this.props.children}</span> return <span className="maputnik-icon-text">{this.props.children}</span>
} }
} }
class ToolbarLink extends React.Component { type ToolbarLinkProps = {
static propTypes = { className?: string
className: PropTypes.string, children?: React.ReactNode
children: PropTypes.node, href?: string
href: PropTypes.string, onToggleModal?(...args: unknown[]): unknown
onToggleModal: PropTypes.func, };
}
class ToolbarLink extends React.Component<ToolbarLinkProps> {
render() { render() {
return <a return <a
className={classnames('maputnik-toolbar-link', this.props.className)} className={classnames('maputnik-toolbar-link', this.props.className)}
@@ -42,14 +42,14 @@ class ToolbarLink extends React.Component {
} }
} }
class ToolbarLinkHighlighted extends React.Component { type ToolbarLinkHighlightedProps = {
static propTypes = { className?: string
className: PropTypes.string, children?: React.ReactNode
children: PropTypes.node, href?: string
href: PropTypes.string, onToggleModal?(...args: unknown[]): unknown
onToggleModal: PropTypes.func };
}
class ToolbarLinkHighlighted extends React.Component<ToolbarLinkHighlightedProps> {
render() { render() {
return <a return <a
className={classnames('maputnik-toolbar-link', "maputnik-toolbar-link--highlighted", this.props.className)} className={classnames('maputnik-toolbar-link', "maputnik-toolbar-link--highlighted", this.props.className)}
@@ -64,12 +64,12 @@ class ToolbarLinkHighlighted extends React.Component {
} }
} }
class ToolbarSelect extends React.Component { type ToolbarSelectProps = {
static propTypes = { children?: React.ReactNode
children: PropTypes.node, wdKey?: string
wdKey: PropTypes.string };
}
class ToolbarSelect extends React.Component<ToolbarSelectProps> {
render() { render() {
return <div return <div
className='maputnik-toolbar-select' className='maputnik-toolbar-select'
@@ -80,13 +80,13 @@ class ToolbarSelect extends React.Component {
} }
} }
class ToolbarAction extends React.Component { type ToolbarActionProps = {
static propTypes = { children?: React.ReactNode
children: PropTypes.node, onClick?(...args: unknown[]): unknown
onClick: PropTypes.func, wdKey?: string
wdKey: PropTypes.string };
}
class ToolbarAction extends React.Component<ToolbarActionProps> {
render() { render() {
return <button return <button
className='maputnik-toolbar-action' className='maputnik-toolbar-action'
@@ -98,22 +98,22 @@ class ToolbarAction extends React.Component {
} }
} }
export default class AppToolbar extends React.Component { type AppToolbarProps = {
static propTypes = { mapStyle: object
mapStyle: PropTypes.object.isRequired, inspectModeEnabled: boolean
inspectModeEnabled: PropTypes.bool.isRequired, onStyleChanged(...args: unknown[]): unknown
onStyleChanged: PropTypes.func.isRequired, // A new style has been uploaded
// A new style has been uploaded onStyleOpen(...args: unknown[]): unknown
onStyleOpen: PropTypes.func.isRequired, // A dict of source id's and the available source layers
// A dict of source id's and the available source layers sources: object
sources: PropTypes.object.isRequired, children?: React.ReactNode
children: PropTypes.node, onToggleModal(...args: unknown[]): unknown
onToggleModal: PropTypes.func, onSetMapState(...args: unknown[]): unknown
onSetMapState: PropTypes.func, mapState?: string
mapState: PropTypes.string, renderer?: string
renderer: PropTypes.string, };
}
export default class AppToolbar extends React.Component<AppToolbarProps> {
state = { state = {
isOpen: { isOpen: {
settings: false, settings: false,
@@ -124,16 +124,16 @@ export default class AppToolbar extends React.Component {
} }
} }
handleSelection(val) { handleSelection(val: string | undefined) {
this.props.onSetMapState(val); this.props.onSetMapState(val);
} }
onSkip = (target) => { onSkip = (target: string) => {
if (target === "map") { if (target === "map") {
document.querySelector(".maplibregl-canvas").focus(); (document.querySelector(".maplibregl-canvas") as HTMLCanvasElement).focus();
} }
else { else {
const el = document.querySelector("#skip-target-"+target); const el = document.querySelector("#skip-target-"+target) as HTMLButtonElement;
el.focus(); el.focus();
} }
} }
@@ -190,21 +190,21 @@ export default class AppToolbar extends React.Component {
<button <button
data-wd-key="root:skip:layer-list" data-wd-key="root:skip:layer-list"
className="maputnik-toolbar-skip" className="maputnik-toolbar-skip"
onClick={e => this.onSkip("layer-list")} onClick={_e => this.onSkip("layer-list")}
> >
Layers list Layers list
</button> </button>
<button <button
data-wd-key="root:skip:layer-editor" data-wd-key="root:skip:layer-editor"
className="maputnik-toolbar-skip" className="maputnik-toolbar-skip"
onClick={e => this.onSkip("layer-editor")} onClick={_e => this.onSkip("layer-editor")}
> >
Layer editor Layer editor
</button> </button>
<button <button
data-wd-key="root:skip:map-view" data-wd-key="root:skip:map-view"
className="maputnik-toolbar-skip" className="maputnik-toolbar-skip"
onClick={e => this.onSkip("map")} onClick={_e => this.onSkip("map")}
> >
Map view Map view
</button> </button>
@@ -246,7 +246,7 @@ export default class AppToolbar extends React.Component {
className="maputnik-select" className="maputnik-select"
data-wd-key="maputnik-select" data-wd-key="maputnik-select"
onChange={(e) => this.handleSelection(e.target.value)} onChange={(e) => this.handleSelection(e.target.value)}
value={currentView.id} value={currentView?.id}
> >
{views.filter(v => v.group === "general").map((item) => { {views.filter(v => v.group === "general").map((item) => {
return ( return (
@@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import { Collapse as ReactCollapse } from 'react-collapse' import { Collapse as ReactCollapse } from 'react-collapse'
import {reducedMotionEnabled} from '../../libs/accessibility' import {reducedMotionEnabled} from '../libs/accessibility'
export default class Collapse extends React.Component { type CollapseProps = {
static propTypes = { isActive: boolean
isActive: PropTypes.bool.isRequired, children: React.ReactElement
children: PropTypes.element.isRequired };
}
export default class Collapse extends React.Component<CollapseProps> {
static defaultProps = { static defaultProps = {
isActive: true isActive: true
} }
@@ -1,13 +1,12 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md' import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md'
export default class Collapser extends React.Component { type CollapserProps = {
static propTypes = { isCollapsed: boolean
isCollapsed: PropTypes.bool.isRequired, style?: object
style: PropTypes.object, };
}
export default class Collapser extends React.Component<CollapserProps> {
render() { render() {
const iconStyle = { const iconStyle = {
width: 20, width: 20,
-20
View File
@@ -1,20 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputAutocomplete from './InputAutocomplete'
export default class FieldAutocomplete extends React.Component {
static propTypes = {
...InputAutocomplete.propTypes,
}
render() {
const {props} = this;
return <Block label={props.label}>
<InputAutocomplete {...props} />
</Block>
}
}
+18
View File
@@ -0,0 +1,18 @@
import React from 'react'
import Block from './Block'
import InputAutocomplete, { InputAutocompleteProps } from './InputAutocomplete'
type FieldAutocompleteProps = InputAutocompleteProps & {
label?: string;
};
export default class FieldAutocomplete extends React.Component<FieldAutocompleteProps> {
render() {
return <Block label={this.props.label}>
<InputAutocomplete {...this.props} />
</Block>
}
}
-20
View File
@@ -1,20 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputCheckbox from './InputCheckbox'
export default class FieldCheckbox extends React.Component {
static propTypes = {
...InputCheckbox.propTypes,
}
render() {
const {props} = this;
return <Block label={this.props.label}>
<InputCheckbox {...props} />
</Block>
}
}
+18
View File
@@ -0,0 +1,18 @@
import React from 'react'
import Block from './Block'
import InputCheckbox, {InputCheckboxProps} from './InputCheckbox'
type FieldCheckboxProps = InputCheckboxProps & {
label?: string;
};
export default class FieldCheckbox extends React.Component<FieldCheckboxProps> {
render() {
return <Block label={this.props.label}>
<InputCheckbox {...this.props} />
</Block>
}
}
@@ -1,15 +1,14 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block' import Block from './Block'
import InputString from './InputString' import InputString from './InputString'
export default class FieldComment extends React.Component { type FieldCommentProps = {
static propTypes = { value?: string
value: PropTypes.string, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, };
}
export default class FieldComment extends React.Component<FieldCommentProps> {
render() { render() {
const fieldSpec = { const fieldSpec = {
doc: "Comments for the current layer. This is non-standard and not in the spec." doc: "Comments for the current layer. This is non-standard and not in the spec."
-21
View File
@@ -1,21 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
import InputDynamicArray from './InputDynamicArray'
import Fieldset from './Fieldset'
export default class FieldDynamicArray extends React.Component {
static propTypes = {
...InputDynamicArray.propTypes,
name: PropTypes.string,
}
render() {
const {props} = this;
return <Fieldset label={props.label}>
<InputDynamicArray {...props} />
</Fieldset>
}
}
+16
View File
@@ -0,0 +1,16 @@
import React from 'react'
import InputDynamicArray, {FieldDynamicArrayProps as InputDynamicArrayProps} from './InputDynamicArray'
import Fieldset from './Fieldset'
type FieldDynamicArrayProps = InputDynamicArrayProps & {
name?: string
};
export default class FieldDynamicArray extends React.Component<FieldDynamicArrayProps> {
render() {
return <Fieldset label={this.props.label}>
<InputDynamicArray {...this.props} />
</Fieldset>
}
}
@@ -1,19 +1,18 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import SpecProperty from './_SpecProperty' import SpecProperty from './_SpecProperty'
import DataProperty from './_DataProperty' import DataProperty, { Stop } from './_DataProperty'
import ZoomProperty from './_ZoomProperty' import ZoomProperty from './_ZoomProperty'
import ExpressionProperty from './_ExpressionProperty' import ExpressionProperty from './_ExpressionProperty'
import {function as styleFunction} from '@maplibre/maplibre-gl-style-spec'; import {function as styleFunction} from '@maplibre/maplibre-gl-style-spec';
import {findDefaultFromSpec} from '../util/spec-helper'; import {findDefaultFromSpec} from '../util/spec-helper';
function isLiteralExpression (value) { function isLiteralExpression(value: any) {
return (Array.isArray(value) && value.length === 2 && value[0] === "literal"); return (Array.isArray(value) && value.length === 2 && value[0] === "literal");
} }
function isGetExpression (value) { function isGetExpression(value: any) {
return ( return (
Array.isArray(value) && Array.isArray(value) &&
value.length === 2 && value.length === 2 &&
@@ -21,14 +20,14 @@ function isGetExpression (value) {
); );
} }
function isZoomField(value) { function isZoomField(value: any) {
return ( return (
typeof(value) === 'object' && typeof(value) === 'object' &&
value.stops && value.stops &&
typeof(value.property) === 'undefined' && typeof(value.property) === 'undefined' &&
Array.isArray(value.stops) && Array.isArray(value.stops) &&
value.stops.length > 1 && value.stops.length > 1 &&
value.stops.every(stop => { value.stops.every((stop: Stop) => {
return ( return (
Array.isArray(stop) && Array.isArray(stop) &&
stop.length === 2 stop.length === 2
@@ -37,7 +36,7 @@ function isZoomField(value) {
); );
} }
function isIdentityProperty (value) { function isIdentityProperty(value: any) {
return ( return (
typeof(value) === 'object' && typeof(value) === 'object' &&
value.type === "identity" && value.type === "identity" &&
@@ -45,14 +44,14 @@ function isIdentityProperty (value) {
); );
} }
function isDataStopProperty (value) { function isDataStopProperty(value: any) {
return ( return (
typeof(value) === 'object' && typeof(value) === 'object' &&
value.stops && value.stops &&
typeof(value.property) !== 'undefined' && typeof(value.property) !== 'undefined' &&
value.stops.length > 1 && value.stops.length > 1 &&
Array.isArray(value.stops) && Array.isArray(value.stops) &&
value.stops.every(stop => { value.stops.every((stop: Stop) => {
return ( return (
Array.isArray(stop) && Array.isArray(stop) &&
stop.length === 2 && stop.length === 2 &&
@@ -62,26 +61,26 @@ function isDataStopProperty (value) {
); );
} }
function isDataField(value) { function isDataField(value: any) {
return ( return (
isIdentityProperty(value) || isIdentityProperty(value) ||
isDataStopProperty(value) isDataStopProperty(value)
); );
} }
function isPrimative (value) { function isPrimative(value: any): value is string | boolean | number {
const valid = ["string", "boolean", "number"]; const valid = ["string", "boolean", "number"];
return valid.includes(typeof(value)); return valid.includes(typeof(value));
} }
function isArrayOfPrimatives (values) { function isArrayOfPrimatives(values: any): values is Array<string | boolean | number> {
if (Array.isArray(values)) { if (Array.isArray(values)) {
return values.every(isPrimative); return values.every(isPrimative);
} }
return false; return false;
} }
function getDataType (value, fieldSpec={}) { function getDataType(value: any, fieldSpec={} as any) {
if (value === undefined) { if (value === undefined) {
return "value"; return "value";
} }
@@ -103,35 +102,33 @@ function getDataType (value, fieldSpec={}) {
} }
type FieldFunctionProps = {
onChange(fieldName: string, value: any): unknown
fieldName: string
fieldType: string
fieldSpec: any
errors?: unknown[]
value?: any
};
type FieldFunctionState = {
dataType: string
isEditing: boolean
}
/** Supports displaying spec field for zoom function objects /** Supports displaying spec field for zoom function objects
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property * https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
*/ */
export default class FieldFunction extends React.Component { export default class FieldFunction extends React.Component<FieldFunctionProps, FieldFunctionState> {
static propTypes = { constructor (props: FieldFunctionProps) {
onChange: PropTypes.func.isRequired, super(props);
fieldName: PropTypes.string.isRequired,
fieldType: PropTypes.string.isRequired,
fieldSpec: PropTypes.object.isRequired,
errors: PropTypes.object,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
}
constructor (props) {
super();
this.state = { this.state = {
dataType: getDataType(props.value, props.fieldSpec), dataType: getDataType(props.value, props.fieldSpec),
isEditing: false, isEditing: false,
} }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props: FieldFunctionProps, state: FieldFunctionState) {
// Because otherwise when editing values we end up accidentally changing field type. // Because otherwise when editing values we end up accidentally changing field type.
if (state.isEditing) { if (state.isEditing) {
return {}; return {};
@@ -144,7 +141,7 @@ export default class FieldFunction extends React.Component {
} }
} }
getFieldFunctionType(fieldSpec) { getFieldFunctionType(fieldSpec: any) {
if (fieldSpec.expression.interpolated) { if (fieldSpec.expression.interpolated) {
return "exponential" return "exponential"
} }
@@ -183,7 +180,7 @@ export default class FieldFunction extends React.Component {
}); });
} }
deleteStop = (stopIdx) => { deleteStop = (stopIdx: number) => {
const stops = this.props.value.stops.slice(0) const stops = this.props.value.stops.slice(0)
stops.splice(stopIdx, 1) stops.splice(stopIdx, 1)
@@ -207,7 +204,7 @@ export default class FieldFunction extends React.Component {
if (value.stops) { if (value.stops) {
zoomFunc = { zoomFunc = {
base: value.base, base: value.base,
stops: value.stops.map(stop => { stops: value.stops.map((stop: Stop) => {
return [stop[0].zoom, stop[1] || findDefaultFromSpec(this.props.fieldSpec)]; return [stop[0].zoom, stop[1] || findDefaultFromSpec(this.props.fieldSpec)];
}) })
} }
@@ -292,7 +289,7 @@ export default class FieldFunction extends React.Component {
property: "", property: "",
type: functionType, type: functionType,
base: value.base, base: value.base,
stops: value.stops.map(stop => { stops: value.stops.map((stop: Stop) => {
return [{zoom: stop[0], value: stopValue}, stop[1] || findDefaultFromSpec(this.props.fieldSpec)]; return [{zoom: stop[0], value: stopValue}, stop[1] || findDefaultFromSpec(this.props.fieldSpec)];
}) })
} }
+2 -4
View File
@@ -10,10 +10,8 @@ type FieldMultiInputProps = InputMultiInputProps & {
export default class FieldMultiInput extends React.Component<FieldMultiInputProps> { export default class FieldMultiInput extends React.Component<FieldMultiInputProps> {
render() { render() {
const {props} = this; return <Fieldset label={this.props.label}>
<InputMultiInput {...this.props} />
return <Fieldset label={props.label}>
<InputMultiInput {...props} />
</Fieldset> </Fieldset>
} }
} }
@@ -1,19 +1,18 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import InputAutocomplete from './InputAutocomplete' import InputAutocomplete from './InputAutocomplete'
export default class FieldSource extends React.Component { type FieldSourceProps = {
static propTypes = { value?: string
value: PropTypes.string, wdKey?: string
wdKey: PropTypes.string, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, sourceIds?: unknown[]
sourceIds: PropTypes.array, error?: unknown[]
error: PropTypes.object, };
}
export default class FieldSource extends React.Component<FieldSourceProps> {
static defaultProps = { static defaultProps = {
onChange: () => {}, onChange: () => {},
sourceIds: [], sourceIds: [],
@@ -29,7 +28,7 @@ export default class FieldSource extends React.Component {
<InputAutocomplete <InputAutocomplete
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
options={this.props.sourceIds.map(src => [src, src])} options={this.props.sourceIds?.map(src => [src, src])}
/> />
</Block> </Block>
} }
@@ -1,18 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import InputAutocomplete from './InputAutocomplete' import InputAutocomplete from './InputAutocomplete'
export default class FieldSourceLayer extends React.Component { type FieldSourceLayerProps = {
static propTypes = { value?: string
value: PropTypes.string, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, sourceLayerIds?: unknown[]
sourceLayerIds: PropTypes.array, isFixed?: boolean
isFixed: PropTypes.bool, };
}
export default class FieldSourceLayer extends React.Component<FieldSourceLayerProps> {
static defaultProps = { static defaultProps = {
onChange: () => {}, onChange: () => {},
sourceLayerIds: [], sourceLayerIds: [],
@@ -27,7 +26,7 @@ export default class FieldSourceLayer extends React.Component {
keepMenuWithinWindowBounds={!!this.props.isFixed} keepMenuWithinWindowBounds={!!this.props.isFixed}
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
options={this.props.sourceLayerIds.map(l => [l, l])} options={this.props.sourceLayerIds?.map(l => [l, l])}
/> />
</Block> </Block>
} }
+6 -6
View File
@@ -3,17 +3,17 @@ import InputString from './InputString'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
export type FieldArrayProps = { export type FieldArrayProps = {
value: string[] | number[] value: (string | number | undefined)[]
type?: string type?: string
length?: number length?: number
default?: string[] | number[] default?: (string | number | undefined)[]
onChange?(...args: unknown[]): unknown onChange?(value: (string | number | undefined)[] | undefined): unknown
'aria-label'?: string 'aria-label'?: string
label?: string label?: string
}; };
type FieldArrayState = { type FieldArrayState = {
value: string[] | number[] value: (string | number | undefined)[]
initialPropsValue: unknown[] initialPropsValue: unknown[]
} }
@@ -59,7 +59,7 @@ export default class FieldArray extends React.Component<FieldArrayProps, FieldAr
}); });
} }
changeValue(idx: number, newValue: string) { changeValue(idx: number, newValue: string | number | undefined) {
const value = this.state.value.slice(0); const value = this.state.value.slice(0);
value[idx] = newValue; value[idx] = newValue;
@@ -93,7 +93,7 @@ export default class FieldArray extends React.Component<FieldArrayProps, FieldAr
default={containsValues || !this.props.default ? undefined : this.props.default[i] as number} default={containsValues || !this.props.default ? undefined : this.props.default[i] as number}
value={value[i] as number} value={value[i] as number}
required={containsValues ? true : false} required={containsValues ? true : false}
onChange={this.changeValue.bind(this, i)} onChange={(v) => this.changeValue(i, v)}
aria-label={this.props['aria-label'] || this.props.label} aria-label={this.props['aria-label'] || this.props.label}
/> />
} else { } else {
+5 -5
View File
@@ -11,10 +11,10 @@ import InputUrl from './InputUrl'
export type FieldDynamicArrayProps = { export type FieldDynamicArrayProps = {
value?: (string | number)[] value?: (string | number | undefined)[]
type?: 'url' | 'number' | 'enum' type?: 'url' | 'number' | 'enum' | 'string'
default?: (string | number)[] default?: (string | number | undefined)[]
onChange?(...args: unknown[]): unknown onChange?(values: (string | number | undefined)[] | undefined): unknown
style?: object style?: object
fieldSpec?: { fieldSpec?: {
values?: any values?: any
@@ -25,7 +25,7 @@ export type FieldDynamicArrayProps = {
export default class FieldDynamicArray extends React.Component<FieldDynamicArrayProps> { export default class FieldDynamicArray extends React.Component<FieldDynamicArrayProps> {
changeValue(idx: number, newValue: string | number) { changeValue(idx: number, newValue: string | number | undefined) {
const values = this.values.slice(0) const values = this.values.slice(0)
values[idx] = newValue values[idx] = newValue
if (this.props.onChange) this.props.onChange(values) if (this.props.onChange) this.props.onChange(values)
+4 -4
View File
@@ -17,7 +17,7 @@ export type InputJsonProps = {
onChange?(...args: unknown[]): unknown onChange?(...args: unknown[]): unknown
lineNumbers?: boolean lineNumbers?: boolean
lineWrapping?: boolean lineWrapping?: boolean
getValue(data: any): string getValue?(data: any): string
gutters?: string[] gutters?: string[]
className?: string className?: string
onFocus?(...args: unknown[]): unknown onFocus?(...args: unknown[]): unknown
@@ -58,13 +58,13 @@ export default class InputJson extends React.Component<InputJsonProps, InputJson
this.state = { this.state = {
isEditing: false, isEditing: false,
showMessage: false, showMessage: false,
prevValue: this.props.getValue(this.props.layer), prevValue: this.props.getValue!(this.props.layer),
}; };
} }
componentDidMount () { componentDidMount () {
this._doc = CodeMirror(this._el!, { this._doc = CodeMirror(this._el!, {
value: this.props.getValue(this.props.layer), value: this.props.getValue!(this.props.layer),
mode: this.props.mode || { mode: this.props.mode || {
name: "mgl", name: "mgl",
}, },
@@ -117,7 +117,7 @@ export default class InputJson extends React.Component<InputJsonProps, InputJson
if (!this.state.isEditing && prevProps.layer !== this.props.layer) { if (!this.state.isEditing && prevProps.layer !== this.props.layer) {
this._cancelNextChange = true; this._cancelNextChange = true;
this._doc!.setValue( this._doc!.setValue(
this.props.getValue(this.props.layer), this.props.getValue!(this.props.layer),
) )
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@ export type InputNumberProps = {
default?: number default?: number
min?: number min?: number
max?: number max?: number
onChange?(...args: unknown[]): unknown onChange?(value: number | undefined): unknown
allowRange?: boolean allowRange?: boolean
rangeStep?: number rangeStep?: number
wdKey?: string wdKey?: string
+1 -1
View File
@@ -5,7 +5,7 @@ export type InputSelectProps = {
"data-wd-key"?: string "data-wd-key"?: string
options: [string, any][] | string[] options: [string, any][] | string[]
style?: object style?: object
onChange(...args: unknown[]): unknown onChange(value: string): unknown
title?: string title?: string
'aria-label'?: string 'aria-label'?: string
}; };
+11 -11
View File
@@ -14,11 +14,11 @@ import capitalize from 'lodash.capitalize'
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image'] const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
export type SpecFieldProps = { export type SpecFieldProps = {
onChange(...args: unknown[]): unknown onChange?(fieldName: string | undefined, value: number | undefined | (string | number | undefined)[]): unknown
fieldName: string fieldName?: string
fieldSpec: { fieldSpec?: {
default?: unknown default?: unknown
type: 'number' | 'enum' | 'resolvedImage' | 'formatted' | 'string' | 'color' | 'boolean' | 'array' type?: 'number' | 'enum' | 'resolvedImage' | 'formatted' | 'string' | 'color' | 'boolean' | 'array'
minimum?: number minimum?: number
maximum?: number maximum?: number
values?: unknown[] values?: unknown[]
@@ -29,9 +29,9 @@ export type SpecFieldProps = {
/** Override the style of the field */ /** Override the style of the field */
style?: object style?: object
'aria-label'?: string 'aria-label'?: string
error: unknown[] error?: unknown[]
label: string label?: string
action: ReactElement action?: ReactElement
}; };
/** Display any field from the Maplibre GL style spec and /** Display any field from the Maplibre GL style spec and
@@ -47,12 +47,12 @@ export default class SpecField extends React.Component<SpecFieldProps> {
action: this.props.action, action: this.props.action,
style: this.props.style, style: this.props.style,
value: this.props.value, value: this.props.value,
default: this.props.fieldSpec.default, default: this.props.fieldSpec?.default,
name: this.props.fieldName, name: this.props.fieldName,
onChange: (newValue: string) => this.props.onChange(this.props.fieldName, newValue), onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
'aria-label': this.props['aria-label'], 'aria-label': this.props['aria-label'],
} }
switch(this.props.fieldSpec.type) { switch(this.props.fieldSpec?.type) {
case 'number': return ( case 'number': return (
<InputNumber <InputNumber
{...commonProps as InputNumberProps} {...commonProps as InputNumberProps}
@@ -70,7 +70,7 @@ export default class SpecField extends React.Component<SpecFieldProps> {
case 'resolvedImage': case 'resolvedImage':
case 'formatted': case 'formatted':
case 'string': case 'string':
if (iconProperties.indexOf(this.props.fieldName) >= 0) { if (iconProperties.indexOf(this.props.fieldName!) >= 0) {
const options = this.props.fieldSpec.values || []; const options = this.props.fieldSpec.values || [];
return <InputAutocomplete return <InputAutocomplete
{...commonProps as Omit<InputAutocompleteProps, "options">} {...commonProps as Omit<InputAutocompleteProps, "options">}
+4 -2
View File
@@ -5,13 +5,14 @@ export type InputStringProps = {
value?: string value?: string
style?: object style?: object
default?: string default?: string
onChange?(...args: unknown[]): unknown onChange?(value: string | undefined): unknown
onInput?(...args: unknown[]): unknown onInput?(value: string | undefined): unknown
multi?: boolean multi?: boolean
required?: boolean required?: boolean
disabled?: boolean disabled?: boolean
spellCheck?: boolean spellCheck?: boolean
'aria-label'?: string 'aria-label'?: string
title?: string
}; };
type InputStringState = { type InputStringState = {
@@ -72,6 +73,7 @@ export default class InputString extends React.Component<InputStringProps, Input
style: this.props.style, style: this.props.style,
value: this.state.value === undefined ? "" : this.state.value, value: this.state.value === undefined ? "" : this.state.value,
placeholder: this.props.default, placeholder: this.props.default,
title: this.props.title,
onChange: (e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => { onChange: (e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => {
this.setState({ this.setState({
editing: true, editing: true,
@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Icon from '@mdi/react' import Icon from '@mdi/react'
import { import {
mdiMenuDown, mdiMenuDown,
@@ -13,21 +12,22 @@ import {
} from 'react-accessible-accordion'; } from 'react-accessible-accordion';
export default class LayerEditorGroup extends React.Component { type LayerEditorGroupProps = {
static propTypes = { "id"?: string
"id": PropTypes.string, "data-wd-key"?: string
"data-wd-key": PropTypes.string, title: string
title: PropTypes.string.isRequired, isActive: boolean
isActive: PropTypes.bool.isRequired, children: React.ReactElement
children: PropTypes.element.isRequired, onActiveToggle(...args: unknown[]): unknown
onActiveToggle: PropTypes.func.isRequired };
}
export default class LayerEditorGroup extends React.Component<LayerEditorGroupProps> {
render() { render() {
return <AccordionItem uuid={this.props.id}> return <AccordionItem uuid={this.props.id}>
<AccordionItemHeading className="maputnik-layer-editor-group" <AccordionItemHeading className="maputnik-layer-editor-group"
data-wd-key={"layer-editor-group:"+this.props["data-wd-key"]} data-wd-key={"layer-editor-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)} onClick={_e => this.props.onActiveToggle(!this.props.isActive)}
> >
<AccordionItemButton className="maputnik-layer-editor-group__button"> <AccordionItemButton className="maputnik-layer-editor-group__button">
<span style={{flexGrow: 1}}>{this.props.title}</span> <span style={{flexGrow: 1}}>{this.props.title}</span>
@@ -1,21 +1,20 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Collapser from './Collapser' import Collapser from './Collapser'
export default class LayerListGroup extends React.Component { type LayerListGroupProps = {
static propTypes = { title: string
title: PropTypes.string.isRequired, "data-wd-key"?: string
"data-wd-key": PropTypes.string, isActive: boolean
isActive: PropTypes.bool.isRequired, onActiveToggle(...args: unknown[]): unknown
onActiveToggle: PropTypes.func.isRequired, 'aria-controls'?: string
'aria-controls': PropTypes.string, };
}
export default class LayerListGroup extends React.Component<LayerListGroupProps> {
render() { render() {
return <li className="maputnik-layer-list-group"> return <li className="maputnik-layer-list-group">
<div className="maputnik-layer-list-group-header" <div className="maputnik-layer-list-group-header"
data-wd-key={"layer-list-group:"+this.props["data-wd-key"]} data-wd-key={"layer-list-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)} onClick={_e => this.props.onActiveToggle(!this.props.isActive)}
> >
<button <button
className="maputnik-layer-list-group-title" className="maputnik-layer-list-group-title"
@@ -1,18 +1,16 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import IconLayer from './IconLayer' import IconLayer from './IconLayer'
import {latest} from '@maplibre/maplibre-gl-style-spec'
function groupFeaturesBySourceLayer(features) { function groupFeaturesBySourceLayer(features: any[]) {
const sources = {} const sources = {} as any
let returnedFeatures = {}; let returnedFeatures = {} as any;
features.forEach(feature => { features.forEach(feature => {
if(returnedFeatures.hasOwnProperty(feature.layer.id)) { if(returnedFeatures.hasOwnProperty(feature.layer.id)) {
returnedFeatures[feature.layer.id]++ returnedFeatures[feature.layer.id]++
const featureObject = sources[feature.layer['source-layer']].find(f => f.layer.id === feature.layer.id) const featureObject = sources[feature.layer['source-layer']].find((f: any) => f.layer.id === feature.layer.id)
featureObject.counter = returnedFeatures[feature.layer.id] featureObject.counter = returnedFeatures[feature.layer.id]
} else { } else {
@@ -26,14 +24,14 @@ function groupFeaturesBySourceLayer(features) {
return sources return sources
} }
class FeatureLayerPopup extends React.Component { type FeatureLayerPopupProps = {
static propTypes = { onLayerSelect(...args: unknown[]): unknown
onLayerSelect: PropTypes.func.isRequired, features: any[]
features: PropTypes.array, zoom?: number
zoom: PropTypes.number, };
}
_getFeatureColor(feature, zoom) { class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
_getFeatureColor(feature: any, _zoom?: number) {
// Guard because openlayers won't have this // Guard because openlayers won't have this
if (!feature.layer.paint) { if (!feature.layer.paint) {
return; return;
@@ -57,7 +55,6 @@ class FeatureLayerPopup extends React.Component {
} }
if(propName) { if(propName) {
const propertySpec = latest["paint_"+feature.layer.type][propName];
let color = feature.layer.paint[propName]; let color = feature.layer.paint[propName];
return String(color); return String(color);
} }
@@ -78,7 +75,7 @@ class FeatureLayerPopup extends React.Component {
const sources = groupFeaturesBySourceLayer(this.props.features) const sources = groupFeaturesBySourceLayer(this.props.features)
const items = Object.keys(sources).map(vectorLayerId => { const items = Object.keys(sources).map(vectorLayerId => {
const layers = sources[vectorLayerId].map((feature, idx) => { const layers = sources[vectorLayerId].map((feature: any, idx: number) => {
const featureColor = this._getFeatureColor(feature, this.props.zoom); const featureColor = this._getFeatureColor(feature, this.props.zoom);
return <div return <div
@@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {throttle} from 'lodash'; import {throttle} from 'lodash';
import PropTypes from 'prop-types'
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'; import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup';
@@ -9,10 +8,10 @@ import {apply} from 'ol-mapbox-style';
import {Map, View, Overlay} from 'ol'; import {Map, View, Overlay} from 'ol';
import {toLonLat} from 'ol/proj'; import {toLonLat} from 'ol/proj';
import {toStringHDMS} from 'ol/coordinate'; import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
function renderCoords (coords) { function renderCoords (coords: string[]) {
if (!coords || coords.length < 2) { if (!coords || coords.length < 2) {
return null; return null;
} }
@@ -23,36 +22,49 @@ function renderCoords (coords) {
} }
} }
export default class MapOpenLayers extends React.Component { type MapOpenLayersProps = {
static propTypes = { onDataChange?(...args: unknown[]): unknown
onDataChange: PropTypes.func, mapStyle: object
mapStyle: PropTypes.object.isRequired, accessToken?: string
accessToken: PropTypes.string, style?: object
style: PropTypes.object, onLayerSelect(...args: unknown[]): unknown
onLayerSelect: PropTypes.func.isRequired, debugToolbox: boolean
debugToolbox: PropTypes.bool.isRequired, replaceAccessTokens(...args: unknown[]): unknown
replaceAccessTokens: PropTypes.func.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, };
}
type MapOpenLayersState = {
zoom: string
rotation: string
cursor: string[]
center: string[]
selectedFeatures?: any[]
};
export default class MapOpenLayers extends React.Component<MapOpenLayersProps, MapOpenLayersState> {
static defaultProps = { static defaultProps = {
onMapLoaded: () => {}, onMapLoaded: () => {},
onDataChange: () => {}, onDataChange: () => {},
onLayerSelect: () => {}, onLayerSelect: () => {},
} }
updateStyle: any;
map: any;
container: HTMLDivElement | null = null;
overlay: Overlay | undefined;
popupContainer: HTMLElement | null = null;
constructor(props) { constructor(props: MapOpenLayersProps) {
super(props); super(props);
this.state = { this.state = {
zoom: 0, zoom: "0",
rotation: 0, rotation: "0",
cursor: [], cursor: [] as string[],
center: [], center: [],
}; };
this.updateStyle = throttle(this._updateStyle.bind(this), 200); this.updateStyle = throttle(this._updateStyle.bind(this), 200);
} }
_updateStyle(newMapStyle) { _updateStyle(newMapStyle: StyleSpecification) {
if(!this.map) return; if(!this.map) return;
// See <https://github.com/openlayers/ol-mapbox-style/issues/215#issuecomment-493198815> // See <https://github.com/openlayers/ol-mapbox-style/issues/215#issuecomment-493198815>
@@ -60,7 +72,7 @@ export default class MapOpenLayers extends React.Component {
apply(this.map, newMapStyle); apply(this.map, newMapStyle);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: MapOpenLayersProps) {
if (this.props.mapStyle !== prevProps.mapStyle) { if (this.props.mapStyle !== prevProps.mapStyle) {
this.updateStyle( this.updateStyle(
this.props.replaceAccessTokens(this.props.mapStyle) this.props.replaceAccessTokens(this.props.mapStyle)
@@ -70,7 +82,7 @@ export default class MapOpenLayers extends React.Component {
componentDidMount() { componentDidMount() {
this.overlay = new Overlay({ this.overlay = new Overlay({
element: this.popupContainer, element: this.popupContainer!,
autoPan: true, autoPan: true,
autoPanAnimation: { autoPanAnimation: {
duration: 250 duration: 250
@@ -78,7 +90,7 @@ export default class MapOpenLayers extends React.Component {
}); });
const map = new Map({ const map = new Map({
target: this.container, target: this.container!,
overlays: [this.overlay], overlays: [this.overlay],
view: new View({ view: new View({
zoom: 1, zoom: 1,
@@ -98,7 +110,7 @@ export default class MapOpenLayers extends React.Component {
const onMoveEnd = () => { const onMoveEnd = () => {
const zoom = map.getView().getZoom(); const zoom = map.getView().getZoom();
const center = toLonLat(map.getView().getCenter()); const center = toLonLat(map.getView().getCenter()!);
this.props.onChange({ this.props.onChange({
zoom, zoom,
@@ -112,15 +124,15 @@ export default class MapOpenLayers extends React.Component {
onMoveEnd(); onMoveEnd();
map.on('moveend', onMoveEnd); map.on('moveend', onMoveEnd);
map.on('postrender', (evt) => { map.on('postrender', (_e) => {
const center = toLonLat(map.getView().getCenter()); const center = toLonLat(map.getView().getCenter()!);
this.setState({ this.setState({
center: [ center: [
center[0].toFixed(2), center[0].toFixed(2),
center[1].toFixed(2), center[1].toFixed(2),
], ],
rotation: map.getView().getRotation().toFixed(2), rotation: map.getView().getRotation().toFixed(2),
zoom: map.getView().getZoom().toFixed(2) zoom: map.getView().getZoom()!.toFixed(2)
}); });
}); });
@@ -132,9 +144,9 @@ export default class MapOpenLayers extends React.Component {
); );
} }
closeOverlay = (e) => { closeOverlay = (e: any) => {
e.target.blur(); e.target.blur();
this.overlay.setPosition(undefined); this.overlay!.setPosition(undefined);
} }
render() { render() {
@@ -1,29 +1,32 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec'
import InputButton from './InputButton' import InputButton from './InputButton'
import Modal from './Modal' import Modal from './Modal'
import FieldType from './FieldType' import FieldType from './FieldType'
import FieldId from './FieldId' import FieldId from './FieldId'
import FieldSource from './FieldSource' import FieldSource from './FieldSource'
import FieldSourceLayer from './FieldSourceLayer' import FieldSourceLayer from './FieldSourceLayer'
export default class ModalAdd extends React.Component { type ModalAddProps = {
static propTypes = { layers: unknown[]
layers: PropTypes.array.isRequired, onLayersChange(...args: unknown[]): unknown
onLayersChange: PropTypes.func.isRequired, isOpen: boolean
isOpen: PropTypes.bool.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, // A dict of source id's and the available source layers
sources: any
};
// A dict of source id's and the available source layers type ModalAddState = {
sources: PropTypes.object.isRequired, type: string
} id: string
source?: string
'source-layer'?: string
};
export default class ModalAdd extends React.Component<ModalAddProps, ModalAddState> {
addLayer = () => { addLayer = () => {
const changedLayers = this.props.layers.slice(0) const changedLayers = this.props.layers.slice(0)
const layer = { const layer: ModalAddState = {
id: this.state.id, id: this.state.id,
type: this.state.type, type: this.state.type,
} }
@@ -41,20 +44,21 @@ export default class ModalAdd extends React.Component {
this.props.onOpenToggle(false) this.props.onOpenToggle(false)
} }
constructor(props) { constructor(props: ModalAddProps) {
super(props) super(props)
this.state = { const state: ModalAddState = {
type: 'fill', type: 'fill',
id: '', id: '',
} }
if(props.sources.length > 0) { if(props.sources.length > 0) {
this.state.source = Object.keys(this.props.sources)[0] state.source = Object.keys(this.props.sources)[0];
this.state['source-layer'] = this.props.sources[this.state.source][0] state['source-layer'] = this.props.sources[state.source as keyof ModalAddProps["sources"]][0]
} }
this.state = state;
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(_prevProps: ModalAddProps, prevState: ModalAddState) {
// Check if source is valid for new type // Check if source is valid for new type
const oldType = prevState.type; const oldType = prevState.type;
const newType = this.state.type; const newType = this.state.type;
@@ -67,9 +71,9 @@ export default class ModalAdd extends React.Component {
oldType !== newType oldType !== newType
&& prevState.source !== "" && prevState.source !== ""
// Was a valid source previously // Was a valid source previously
&& availableSourcesOld.indexOf(prevState.source) > -1 && availableSourcesOld.indexOf(prevState.source!) > -1
// And is not a valid source now // And is not a valid source now
&& availableSourcesNew.indexOf(this.state.source) < 0 && availableSourcesNew.indexOf(this.state.source!) < 0
) { ) {
// Clear the source // Clear the source
this.setState({ this.setState({
@@ -78,12 +82,12 @@ export default class ModalAdd extends React.Component {
} }
} }
getLayersForSource(source) { getLayersForSource(source: string) {
const sourceObj = this.props.sources[source] || {}; const sourceObj = this.props.sources[source] || {};
return sourceObj.layers || []; return sourceObj.layers || [];
} }
getSources(type) { getSources(type: string) {
const sources = []; const sources = [];
const types = { const types = {
@@ -108,8 +112,9 @@ export default class ModalAdd extends React.Component {
] ]
} }
for(let [key, val] of Object.entries(this.props.sources)) { for(let [key, val] of Object.entries(this.props.sources) as any) {
if(types[val.type] && types[val.type].indexOf(type) > -1) { const valType = val.type as keyof typeof types;
if(types[valType] && types[valType].indexOf(type) > -1) {
sources.push(key); sources.push(key);
} }
} }
@@ -120,7 +125,7 @@ export default class ModalAdd extends React.Component {
render() { render() {
const sources = this.getSources(this.state.type); const sources = this.getSources(this.state.type);
const layers = this.getLayersForSource(this.state.source); const layers = this.getLayersForSource(this.state.source!);
return <Modal return <Modal
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
@@ -131,25 +136,23 @@ export default class ModalAdd extends React.Component {
> >
<div className="maputnik-add-layer"> <div className="maputnik-add-layer">
<FieldId <FieldId
label="ID"
fieldSpec={latest.layer.id}
value={this.state.id} value={this.state.id}
wdKey="add-layer.layer-id" wdKey="add-layer.layer-id"
onChange={v => { onChange={(v: string) => {
this.setState({ id: v }) this.setState({ id: v })
}} }}
/> />
<FieldType <FieldType
value={this.state.type} value={this.state.type}
wdKey="add-layer.layer-type" wdKey="add-layer.layer-type"
onChange={v => this.setState({ type: v })} onChange={(v: string) => this.setState({ type: v })}
/> />
{this.state.type !== 'background' && {this.state.type !== 'background' &&
<FieldSource <FieldSource
sourceIds={sources} sourceIds={sources}
wdKey="add-layer.layer-source-block" wdKey="add-layer.layer-source-block"
value={this.state.source} value={this.state.source}
onChange={v => this.setState({ source: v })} onChange={(v: string) => this.setState({ source: v })}
/> />
} }
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 && {['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
@@ -157,7 +160,7 @@ export default class ModalAdd extends React.Component {
isFixed={true} isFixed={true}
sourceLayerIds={layers} sourceLayerIds={layers}
value={this.state['source-layer']} value={this.state['source-layer']}
onChange={v => this.setState({ 'source-layer': v })} onChange={(v: string) => this.setState({ 'source-layer': v })}
/> />
} }
<InputButton <InputButton
@@ -1,21 +1,27 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Modal from './Modal' import Modal from './Modal'
export default class ModalDebug extends React.Component { type ModalDebugProps = {
static propTypes = { isOpen: boolean
isOpen: PropTypes.bool.isRequired, renderer: string
renderer: PropTypes.string.isRequired, onChangeMaboxGlDebug(...args: unknown[]): unknown
onChangeMaboxGlDebug: PropTypes.func.isRequired, onChangeOpenlayersDebug(...args: unknown[]): unknown
onChangeOpenlayersDebug: PropTypes.func.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, maplibreGlDebugOptions?: object
maplibreGlDebugOptions: PropTypes.object, openlayersDebugOptions?: object
openlayersDebugOptions: PropTypes.object, mapView: {
mapView: PropTypes.object, zoom: number
center: {
lng: string
lat: string
}
} }
};
export default class ModalDebug extends React.Component<ModalDebugProps> {
render() { render() {
const {mapView} = this.props; const {mapView} = this.props;
@@ -33,10 +39,10 @@ export default class ModalDebug extends React.Component {
<h1>Options</h1> <h1>Options</h1>
{this.props.renderer === 'mlgljs' && {this.props.renderer === 'mlgljs' &&
<ul> <ul>
{Object.entries(this.props.maplibreGlDebugOptions).map(([key, val]) => { {Object.entries(this.props.maplibreGlDebugOptions!).map(([key, val]) => {
return <li key={key}> return <li key={key}>
<label> <label>
<input type="checkbox" checked={val} onClick={(e) => this.props.onChangeMaboxGlDebug(key, e.target.checked)} /> {key} <input type="checkbox" checked={val} onChange={(e) => this.props.onChangeMaboxGlDebug(key, e.target.checked)} /> {key}
</label> </label>
</li> </li>
})} })}
@@ -44,10 +50,10 @@ export default class ModalDebug extends React.Component {
} }
{this.props.renderer === 'ol' && {this.props.renderer === 'ol' &&
<ul> <ul>
{Object.entries(this.props.openlayersDebugOptions).map(([key, val]) => { {Object.entries(this.props.openlayersDebugOptions!).map(([key, val]) => {
return <li key={key}> return <li key={key}>
<label> <label>
<input type="checkbox" checked={val} onClick={(e) => this.props.onChangeOpenlayersDebug(key, e.target.checked)} /> {key} <input type="checkbox" checked={val} onChange={(e) => this.props.onChangeOpenlayersDebug(key, e.target.checked)} /> {key}
</label> </label>
</li> </li>
})} })}
@@ -1,13 +1,13 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Slugify from 'slugify' import Slugify from 'slugify'
import {saveAs} from 'file-saver' import {saveAs} from 'file-saver'
import {version} from 'maplibre-gl' import {version} from 'maplibre-gl/package.json'
import {format} from '@maplibre/maplibre-gl-style-spec' import {StyleSpecification, format} from '@maplibre/maplibre-gl-style-spec'
import {MdFileDownload} from 'react-icons/md'
import FieldString from './FieldString' import FieldString from './FieldString'
import InputButton from './InputButton' import InputButton from './InputButton'
import Modal from './Modal' import Modal from './Modal'
import {MdFileDownload} from 'react-icons/md'
import style from '../libs/style' import style from '../libs/style'
import fieldSpecAdditional from '../libs/field-spec-additional' import fieldSpecAdditional from '../libs/field-spec-additional'
@@ -15,17 +15,15 @@ import fieldSpecAdditional from '../libs/field-spec-additional'
const MAPLIBRE_GL_VERSION = version; const MAPLIBRE_GL_VERSION = version;
export default class ModalExport extends React.Component { type ModalExportProps = {
static propTypes = { mapStyle: StyleSpecification & { id: string }
mapStyle: PropTypes.object.isRequired, onStyleChanged(...args: unknown[]): unknown
onStyleChanged: PropTypes.func.isRequired, isOpen: boolean
isOpen: PropTypes.bool.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, };
}
constructor(props) {
super(props); export default class ModalExport extends React.Component<ModalExportProps> {
}
tokenizedStyle () { tokenizedStyle () {
return format( return format(
@@ -88,11 +86,11 @@ export default class ModalExport extends React.Component {
saveAs(blob, exportName + ".json"); saveAs(blob, exportName + ".json");
} }
changeMetadataProperty(property, value) { changeMetadataProperty(property: string, value: any) {
const changedStyle = { const changedStyle = {
...this.props.mapStyle, ...this.props.mapStyle,
metadata: { metadata: {
...this.props.mapStyle.metadata, ...this.props.mapStyle.metadata as any,
[property]: value [property]: value
} }
} }
@@ -119,13 +117,13 @@ export default class ModalExport extends React.Component {
<FieldString <FieldString
label={fieldSpecAdditional.maputnik.maptiler_access_token.label} label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token} fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']} value={(this.props.mapStyle.metadata || {} as any)['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/> />
<FieldString <FieldString
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label} label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token} fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']} value={(this.props.mapStyle.metadata || {} as any)['maputnik:thunderforest_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")} onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
/> />
</div> </div>
@@ -1,12 +1,10 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification, latest} from '@maplibre/maplibre-gl-style-spec'
import {latest} from '@maplibre/maplibre-gl-style-spec'
import Modal from './Modal' import Modal from './Modal'
import InputButton from './InputButton' import InputButton from './InputButton'
import Block from './Block'
import FieldString from './FieldString' import FieldString from './FieldString'
import FieldSelect from './FieldSelect' import FieldSelect from './FieldSelect'
import ModalSourcesTypeEditor from './ModalSourcesTypeEditor' import ModalSourcesTypeEditor, { EditorMode } from './ModalSourcesTypeEditor'
import style from '../libs/style' import style from '../libs/style'
import { deleteSource, addSource, changeSource } from '../libs/source' import { deleteSource, addSource, changeSource } from '../libs/source'
@@ -14,32 +12,32 @@ import publicSources from '../config/tilesets.json'
import {MdAddCircleOutline, MdDelete} from 'react-icons/md' import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
class PublicSource extends React.Component { type PublicSourceProps = {
static propTypes = { id: string
id: PropTypes.string.isRequired, type: string
type: PropTypes.string.isRequired, title: string
title: PropTypes.string.isRequired, onSelect(...args: unknown[]): unknown
onSelect: PropTypes.func.isRequired, };
}
class PublicSource extends React.Component<PublicSourceProps> {
render() { render() {
return <div className="maputnik-public-source"> return <div className="maputnik-public-source">
<InputButton <InputButton
className="maputnik-public-source-select" className="maputnik-public-source-select"
onClick={() => this.props.onSelect(this.props.id)} onClick={() => this.props.onSelect(this.props.id)}
> >
<div className="maputnik-public-source-info"> <div className="maputnik-public-source-info">
<p className="maputnik-public-source-name">{this.props.title}</p> <p className="maputnik-public-source-name">{this.props.title}</p>
<p className="maputnik-public-source-id">#{this.props.id}</p> <p className="maputnik-public-source-id">#{this.props.id}</p>
</div> </div>
<span className="maputnik-space" /> <span className="maputnik-space" />
<MdAddCircleOutline /> <MdAddCircleOutline />
</InputButton> </InputButton>
</div> </div>
} }
} }
function editorMode(source) { function editorMode(source: SourceSpecification) {
if(source.type === 'raster') { if(source.type === 'raster') {
if(source.tiles) return 'tilexyz_raster' if(source.tiles) return 'tilexyz_raster'
return 'tilejson_raster' return 'tilejson_raster'
@@ -69,16 +67,15 @@ function editorMode(source) {
return null return null
} }
class ActiveModalSourcesTypeEditor extends React.Component { type ActiveModalSourcesTypeEditorProps = {
static propTypes = { sourceId: string
sourceId: PropTypes.string.isRequired, source: SourceSpecification
source: PropTypes.object.isRequired, onDelete(...args: unknown[]): unknown
onDelete: PropTypes.func.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, };
}
class ActiveModalSourcesTypeEditor extends React.Component<ActiveModalSourcesTypeEditorProps> {
render() { render() {
const inputProps = { }
return <div className="maputnik-active-source-type-editor"> return <div className="maputnik-active-source-type-editor">
<div className="maputnik-active-source-type-editor-header"> <div className="maputnik-active-source-type-editor-header">
<span className="maputnik-active-source-type-editor-header-id">#{this.props.sourceId}</span> <span className="maputnik-active-source-type-editor-header-id">#{this.props.sourceId}</span>
@@ -103,12 +100,18 @@ class ActiveModalSourcesTypeEditor extends React.Component {
} }
} }
class AddSource extends React.Component { type AddSourceProps = {
static propTypes = { onAdd(...args: unknown[]): unknown
onAdd: PropTypes.func.isRequired, };
}
constructor(props) { type AddSourceState = {
mode: EditorMode
sourceId: string
source: SourceSpecification
};
class AddSource extends React.Component<AddSourceProps, AddSourceState> {
constructor(props: AddSourceProps) {
super(props) super(props)
this.state = { this.state = {
mode: 'tilejson_vector', mode: 'tilejson_vector',
@@ -117,7 +120,7 @@ class AddSource extends React.Component {
} }
} }
defaultSource(mode) { defaultSource(mode: EditorMode): SourceSpecification {
const source = (this.state || {}).source || {} const source = (this.state || {}).source || {}
const {protocol} = window.location; const {protocol} = window.location;
@@ -128,38 +131,38 @@ class AddSource extends React.Component {
} }
case 'geojson_json': return { case 'geojson_json': return {
type: 'geojson', type: 'geojson',
cluster: source.cluster || false, cluster: (source as GeoJSONSourceSpecification).cluster || false,
data: {} data: {}
} }
case 'tilejson_vector': return { case 'tilejson_vector': return {
type: 'vector', type: 'vector',
url: source.url || `${protocol}//localhost:3000/tilejson.json` url: (source as VectorSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
} }
case 'tilexyz_vector': return { case 'tilexyz_vector': return {
type: 'vector', type: 'vector',
tiles: source.tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`], tiles: (source as VectorSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
minZoom: source.minzoom || 0, minzoom: (source as VectorSourceSpecification).minzoom || 0,
maxZoom: source.maxzoom || 14 maxzoom: (source as VectorSourceSpecification).maxzoom || 14
} }
case 'tilejson_raster': return { case 'tilejson_raster': return {
type: 'raster', type: 'raster',
url: source.url || `${protocol}//localhost:3000/tilejson.json` url: (source as RasterSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
} }
case 'tilexyz_raster': return { case 'tilexyz_raster': return {
type: 'raster', type: 'raster',
tiles: source.tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`], tiles: (source as RasterSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
minzoom: source.minzoom || 0, minzoom: (source as RasterSourceSpecification).minzoom || 0,
maxzoom: source.maxzoom || 14 maxzoom: (source as RasterSourceSpecification).maxzoom || 14
} }
case 'tilejson_raster-dem': return { case 'tilejson_raster-dem': return {
type: 'raster-dem', type: 'raster-dem',
url: source.url || `${protocol}//localhost:3000/tilejson.json` url: (source as RasterDEMSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
} }
case 'tilexyz_raster-dem': return { case 'tilexyz_raster-dem': return {
type: 'raster-dem', type: 'raster-dem',
tiles: source.tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`], tiles: (source as RasterDEMSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
minzoom: source.minzoom || 0, minzoom: (source as RasterDEMSourceSpecification).minzoom || 0,
maxzoom: source.maxzoom || 14 maxzoom: (source as RasterDEMSourceSpecification).maxzoom || 14
} }
case 'image': return { case 'image': return {
type: 'image', type: 'image',
@@ -183,7 +186,7 @@ class AddSource extends React.Component {
[0,0], [0,0],
], ],
} }
default: return {} default: return {} as any
} }
} }
@@ -192,7 +195,7 @@ class AddSource extends React.Component {
this.props.onAdd(sourceId, source); this.props.onAdd(sourceId, source);
} }
onChangeSource = (source) => { onChangeSource = (source: SourceSpecification) => {
this.setState({source}); this.setState({source});
} }
@@ -213,7 +216,7 @@ class AddSource extends React.Component {
label={"Source ID"} label={"Source ID"}
fieldSpec={{doc: "Unique ID that identifies the source and is used in the layer to reference the source."}} fieldSpec={{doc: "Unique ID that identifies the source and is used in the layer to reference the source."}}
value={this.state.sourceId} value={this.state.sourceId}
onChange={v => this.setState({ sourceId: v})} onChange={(v: string) => this.setState({ sourceId: v})}
/> />
<FieldSelect <FieldSelect
label={"Source Type"} label={"Source Type"}
@@ -230,8 +233,8 @@ class AddSource extends React.Component {
['image', 'Image'], ['image', 'Image'],
['video', 'Video'], ['video', 'Video'],
]} ]}
onChange={mode => this.setState({mode: mode, source: this.defaultSource(mode)})} onChange={mode => this.setState({mode: mode as EditorMode, source: this.defaultSource(mode as EditorMode)})}
value={this.state.mode} value={this.state.mode as string}
/> />
<ModalSourcesTypeEditor <ModalSourcesTypeEditor
onChange={this.onChangeSource} onChange={this.onChangeSource}
@@ -240,7 +243,7 @@ class AddSource extends React.Component {
/> />
<InputButton <InputButton
className="maputnik-add-source-button" className="maputnik-add-source-button"
onClick={this.onAdd} onClick={this.onAdd}
> >
Add Source Add Source
</InputButton> </InputButton>
@@ -248,15 +251,15 @@ class AddSource extends React.Component {
} }
} }
export default class ModalSources extends React.Component { type ModalSourcesProps = {
static propTypes = { mapStyle: StyleSpecification
mapStyle: PropTypes.object.isRequired, isOpen: boolean
isOpen: PropTypes.bool.isRequired, onOpenToggle(...args: unknown[]): unknown
onOpenToggle: PropTypes.func.isRequired, onStyleChanged(...args: unknown[]): unknown
onStyleChanged: PropTypes.func.isRequired, };
}
stripTitle(source) { export default class ModalSources extends React.Component<ModalSourcesProps> {
stripTitle(source: SourceSpecification & {title?: string}): SourceSpecification {
const strippedSource = {...source} const strippedSource = {...source}
delete strippedSource['title'] delete strippedSource['title']
return strippedSource return strippedSource
@@ -270,13 +273,13 @@ export default class ModalSources extends React.Component {
key={sourceId} key={sourceId}
sourceId={sourceId} sourceId={sourceId}
source={source} source={source}
onChange={src => this.props.onStyleChanged(changeSource(mapStyle, sourceId, src))} onChange={(src: SourceSpecification) => this.props.onStyleChanged(changeSource(mapStyle, sourceId, src))}
onDelete={() => this.props.onStyleChanged(deleteSource(mapStyle, sourceId))} onDelete={() => this.props.onStyleChanged(deleteSource(mapStyle, sourceId))}
/> />
}) })
const tilesetOptions = Object.keys(publicSources).filter(sourceId => !(sourceId in mapStyle.sources)).map(sourceId => { const tilesetOptions = Object.keys(publicSources).filter((sourceId: string) => !(sourceId in mapStyle.sources)).map((sourceId: string) => {
const source = publicSources[sourceId] const source = publicSources[sourceId as keyof typeof publicSources] as SourceSpecification & {title: string};
return <PublicSource return <PublicSource
key={sourceId} key={sourceId}
id={sourceId} id={sourceId}
@@ -286,7 +289,6 @@ export default class ModalSources extends React.Component {
/> />
}) })
const inputProps = { }
return <Modal return <Modal
data-wd-key="modal:sources" data-wd-key="modal:sources"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
@@ -303,17 +305,17 @@ export default class ModalSources extends React.Component {
<p> <p>
Add one of the publicly available sources to your style. Add one of the publicly available sources to your style.
</p> </p>
<div className="maputnik-public-sources" style={{maxwidth: 500}}> <div className="maputnik-public-sources" style={{maxWidth: 500}}>
{tilesetOptions} {tilesetOptions}
</div> </div>
</section> </section>
<section className="maputnik-modal-section"> <section className="maputnik-modal-section">
<h1>Add New Source</h1> <h1>Add New Source</h1>
<p>Add a new source to your style. You can only choose the source type and id at creation time!</p> <p>Add a new source to your style. You can only choose the source type and id at creation time!</p>
<AddSource <AddSource
onAdd={(sourceId, source) => this.props.onStyleChanged(addSource(mapStyle, sourceId, source))} onAdd={(sourceId: string, source: SourceSpecification) => this.props.onStyleChanged(addSource(mapStyle, sourceId, source))}
/> />
</section> </section>
</Modal> </Modal>
} }
@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import FieldUrl from './FieldUrl' import FieldUrl from './FieldUrl'
@@ -10,14 +9,18 @@ import FieldArray from './FieldArray'
import FieldJson from './FieldJson' import FieldJson from './FieldJson'
import FieldCheckbox from './FieldCheckbox' import FieldCheckbox from './FieldCheckbox'
export type EditorMode = "video" | "image" | "tilejson_vector" | "tilexyz_raster" | "tilejson_raster" | "tilexyz_raster-dem" | "tilejson_raster-dem" | "tilexyz_vector" | "geojson_url" | "geojson_json" | null;
class TileJSONSourceEditor extends React.Component { type TileJSONSourceEditorProps = {
static propTypes = { source: {
source: PropTypes.object.isRequired, url: string
onChange: PropTypes.func.isRequired,
children: PropTypes.node,
} }
onChange(...args: unknown[]): unknown
children?: React.ReactNode
};
class TileJSONSourceEditor extends React.Component<TileJSONSourceEditorProps> {
render() { render() {
return <div> return <div>
<FieldUrl <FieldUrl
@@ -34,14 +37,18 @@ class TileJSONSourceEditor extends React.Component {
} }
} }
class TileURLSourceEditor extends React.Component { type TileURLSourceEditorProps = {
static propTypes = { source: {
source: PropTypes.object.isRequired, tiles: string[]
onChange: PropTypes.func.isRequired, minzoom: number
children: PropTypes.node, maxzoom: number
} }
onChange(...args: unknown[]): unknown
children?: React.ReactNode
};
changeTileUrls(tiles) { class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
changeTileUrls(tiles: string[]) {
this.props.onChange({ this.props.onChange({
...this.props.source, ...this.props.source,
tiles, tiles,
@@ -86,14 +93,17 @@ class TileURLSourceEditor extends React.Component {
} }
} }
class ImageSourceEditor extends React.Component { type ImageSourceEditorProps = {
static propTypes = { source: {
source: PropTypes.object.isRequired, coordinates: [number, number][]
onChange: PropTypes.func.isRequired, url: string
} }
onChange(...args: unknown[]): unknown
};
class ImageSourceEditor extends React.Component<ImageSourceEditorProps> {
render() { render() {
const changeCoord = (idx, val) => { const changeCoord = (idx: number, val: [number, number]) => {
const coordinates = this.props.source.coordinates.slice(0); const coordinates = this.props.source.coordinates.slice(0);
coordinates[idx] = val; coordinates[idx] = val;
@@ -122,7 +132,7 @@ class ImageSourceEditor extends React.Component {
type="number" type="number"
value={this.props.source.coordinates[idx]} value={this.props.source.coordinates[idx]}
default={[0, 0]} default={[0, 0]}
onChange={(val) => changeCoord(idx, val)} onChange={(val: [number, number]) => changeCoord(idx, val)}
/> />
); );
})} })}
@@ -130,14 +140,17 @@ class ImageSourceEditor extends React.Component {
} }
} }
class VideoSourceEditor extends React.Component { type VideoSourceEditorProps = {
static propTypes = { source: {
source: PropTypes.object.isRequired, coordinates: [number, number][]
onChange: PropTypes.func.isRequired, urls: string[]
} }
onChange(...args: unknown[]): unknown
};
class VideoSourceEditor extends React.Component<VideoSourceEditorProps> {
render() { render() {
const changeCoord = (idx, val) => { const changeCoord = (idx: number, val: [number, number]) => {
const coordinates = this.props.source.coordinates.slice(0); const coordinates = this.props.source.coordinates.slice(0);
coordinates[idx] = val; coordinates[idx] = val;
@@ -147,7 +160,7 @@ class VideoSourceEditor extends React.Component {
}); });
} }
const changeUrls = (urls) => { const changeUrls = (urls: string[]) => {
this.props.onChange({ this.props.onChange({
...this.props.source, ...this.props.source,
urls, urls,
@@ -160,7 +173,7 @@ class VideoSourceEditor extends React.Component {
fieldSpec={latest.source_video.urls} fieldSpec={latest.source_video.urls}
type="string" type="string"
value={this.props.source.urls} value={this.props.source.urls}
default={""} default={[]}
onChange={changeUrls} onChange={changeUrls}
/> />
{["top left", "top right", "bottom right", "bottom left"].map((label, idx) => { {["top left", "top right", "bottom right", "bottom left"].map((label, idx) => {
@@ -172,7 +185,7 @@ class VideoSourceEditor extends React.Component {
type="number" type="number"
value={this.props.source.coordinates[idx]} value={this.props.source.coordinates[idx]}
default={[0, 0]} default={[0, 0]}
onChange={val => changeCoord(idx, val)} onChange={(val: [number, number]) => changeCoord(idx, val)}
/> />
); );
})} })}
@@ -180,12 +193,14 @@ class VideoSourceEditor extends React.Component {
} }
} }
class GeoJSONSourceUrlEditor extends React.Component { type GeoJSONSourceUrlEditorProps = {
static propTypes = { source: {
source: PropTypes.object.isRequired, data: string
onChange: PropTypes.func.isRequired,
} }
onChange(...args: unknown[]): unknown
};
class GeoJSONSourceUrlEditor extends React.Component<GeoJSONSourceUrlEditorProps> {
render() { render() {
return <FieldUrl return <FieldUrl
label={"GeoJSON URL"} label={"GeoJSON URL"}
@@ -199,12 +214,15 @@ class GeoJSONSourceUrlEditor extends React.Component {
} }
} }
class GeoJSONSourceFieldJsonEditor extends React.Component { type GeoJSONSourceFieldJsonEditorProps = {
static propTypes = { source: {
source: PropTypes.object.isRequired, data: any,
onChange: PropTypes.func.isRequired, cluster: boolean
} }
onChange(...args: unknown[]): unknown
};
class GeoJSONSourceFieldJsonEditor extends React.Component<GeoJSONSourceFieldJsonEditorProps> {
render() { render() {
return <div> return <div>
<Block label={"GeoJSON"} fieldSpec={latest.source_geojson.data}> <Block label={"GeoJSON"} fieldSpec={latest.source_geojson.data}>
@@ -238,13 +256,13 @@ class GeoJSONSourceFieldJsonEditor extends React.Component {
} }
} }
export default class ModalSourcesTypeEditor extends React.Component { type ModalSourcesTypeEditorProps = {
static propTypes = { mode: EditorMode
mode: PropTypes.string.isRequired, source: any
source: PropTypes.object.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, };
}
export default class ModalSourcesTypeEditor extends React.Component<ModalSourcesTypeEditorProps> {
render() { render() {
const commonProps = { const commonProps = {
source: this.props.source, source: this.props.source,
@@ -1,12 +1,12 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import FieldFunction from './FieldFunction' import FieldFunction from './FieldFunction'
import { LayerSpecification } from '@maplibre/maplibre-gl-style-spec'
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image'] const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
/** Extract field spec by {@fieldName} from the {@layerType} in the /** Extract field spec by {@fieldName} from the {@layerType} in the
* style specification from either the paint or layout group */ * style specification from either the paint or layout group */
function getFieldSpec(spec, layerType, fieldName) { function getFieldSpec(spec: any, layerType: LayerSpecification["type"], fieldName: string) {
const groupName = getGroupName(spec, layerType, fieldName) const groupName = getGroupName(spec, layerType, fieldName)
const group = spec[groupName + '_' + layerType] const group = spec[groupName + '_' + layerType]
const fieldSpec = group[fieldName] const fieldSpec = group[fieldName]
@@ -25,7 +25,7 @@ function getFieldSpec(spec, layerType, fieldName) {
return fieldSpec return fieldSpec
} }
function getGroupName(spec, layerType, fieldName) { function getGroupName(spec: any, layerType: LayerSpecification["type"], fieldName: string) {
const paint = spec['paint_' + layerType] || {} const paint = spec['paint_' + layerType] || {}
if (fieldName in paint) { if (fieldName in paint) {
return 'paint' return 'paint'
@@ -34,18 +34,18 @@ function getGroupName(spec, layerType, fieldName) {
} }
} }
export default class PropertyGroup extends React.Component { type PropertyGroupProps = {
static propTypes = { layer: LayerSpecification
layer: PropTypes.object.isRequired, groupFields: string[]
groupFields: PropTypes.array.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, spec: any
spec: PropTypes.object.isRequired, errors?: unknown[]
errors: PropTypes.object, };
}
onPropertyChange = (property, newValue) => { export default class PropertyGroup extends React.Component<PropertyGroupProps> {
onPropertyChange = (property: string, newValue: any) => {
const group = getGroupName(this.props.spec, this.props.layer.type, property) const group = getGroupName(this.props.spec, this.props.layer.type, property)
this.props.onChange(group , property, newValue) this.props.onChange(group ,property, newValue)
} }
render() { render() {
@@ -55,7 +55,9 @@ export default class PropertyGroup extends React.Component {
const paint = this.props.layer.paint || {} const paint = this.props.layer.paint || {}
const layout = this.props.layer.layout || {} const layout = this.props.layer.layout || {}
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName] const fieldValue = fieldName in paint
? paint[fieldName as keyof typeof paint]
: layout[fieldName as keyof typeof layout]
const fieldType = fieldName in paint ? 'paint' : 'layout'; const fieldType = fieldName in paint ? 'paint' : 'layout';
return <FieldFunction return <FieldFunction
+7 -9
View File
@@ -15,21 +15,19 @@ const typeMap = {
formatted: () => Block, formatted: () => Block,
}; };
type SpecFieldProps = InputFieldSpecProps & { export type SpecFieldProps = InputFieldSpecProps & {
name?: string name?: string
}; };
export default class SpecField extends React.Component<SpecFieldProps> { export default class SpecField extends React.Component<SpecFieldProps> {
render() { render() {
const {props} = this; const fieldType = this.props.fieldSpec?.type;
const fieldType = props.fieldSpec.type; const typeBlockFn = typeMap[fieldType!];
const typeBlockFn = typeMap[fieldType];
let TypeBlock; let TypeBlock;
if (typeBlockFn) { if (typeBlockFn) {
TypeBlock = typeBlockFn(props); TypeBlock = typeBlockFn(this.props);
} }
else { else {
console.warn("No such type for '%s'", fieldType); console.warn("No such type for '%s'", fieldType);
@@ -37,11 +35,11 @@ export default class SpecField extends React.Component<SpecFieldProps> {
} }
return <TypeBlock return <TypeBlock
label={props.label} label={this.props.label}
action={props.action} action={this.props.action}
fieldSpec={this.props.fieldSpec} fieldSpec={this.props.fieldSpec}
> >
<InputSpec {...props} /> <InputSpec {...this.props} />
</TypeBlock> </TypeBlock>
} }
} }
@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js'; import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
@@ -8,7 +7,6 @@ import InputSpec from './InputSpec'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
import InputString from './InputString' import InputString from './InputString'
import InputSelect from './InputSelect' import InputSelect from './InputSelect'
import FieldDocLabel from './FieldDocLabel'
import Block from './Block' import Block from './Block'
import docUid from '../libs/document-uid' import docUid from '../libs/document-uid'
import sortNumerically from '../libs/sort-numerically' import sortNumerically from '../libs/sort-numerically'
@@ -19,12 +17,12 @@ import DeleteStopButton from './_DeleteStopButton'
function setStopRefs(props, state) { function setStopRefs(props: DataPropertyProps, state: DataPropertyState) {
// This is initialsed below only if required to improved performance. // This is initialsed below only if required to improved performance.
let newRefs; let newRefs: {[key: number]: string} | undefined;
if(props.value && props.value.stops) { if(props.value && props.value.stops) {
props.value.stops.forEach((val, idx) => { props.value.stops.forEach((_val, idx) => {
if(!state.refs.hasOwnProperty(idx)) { if(!state.refs.hasOwnProperty(idx)) {
if(!newRefs) { if(!newRefs) {
newRefs = {...state}; newRefs = {...state};
@@ -37,27 +35,39 @@ function setStopRefs(props, state) {
return newRefs; return newRefs;
} }
export default class DataProperty extends React.Component { type DataPropertyProps = {
static propTypes = { onChange?(fieldName: string, value: any): unknown
onChange: PropTypes.func, onDeleteStop?(...args: unknown[]): unknown
onDeleteStop: PropTypes.func, onAddStop?(...args: unknown[]): unknown
onAddStop: PropTypes.func, onExpressionClick?(...args: unknown[]): unknown
onExpressionClick: PropTypes.func, onChangeToZoomFunction?(...args: unknown[]): unknown
fieldName: PropTypes.string, fieldName: string
fieldType: PropTypes.string, fieldType?: string
fieldSpec: PropTypes.object, fieldSpec?: object
value: PropTypes.oneOfType([ value?: DataPropertyValue
PropTypes.object, errors?: object
PropTypes.string, };
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
errors: PropTypes.object,
}
type DataPropertyState = {
refs: {[key: number]: string}
};
type DataPropertyValue = {
default?: any
property?: string
base?: number
type?: string
stops: Stop[]
}
export type Stop = [{
zoom: number
value: number
}, number]
export default class DataProperty extends React.Component<DataPropertyProps, DataPropertyState> {
state = { state = {
refs: {} refs: {} as {[key: number]: string}
} }
componentDidMount() { componentDidMount() {
@@ -70,7 +80,7 @@ export default class DataProperty extends React.Component {
} }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props: DataPropertyProps, state: DataPropertyState) {
const newRefs = setStopRefs(props, state); const newRefs = setStopRefs(props, state);
if(newRefs) { if(newRefs) {
return { return {
@@ -80,7 +90,7 @@ export default class DataProperty extends React.Component {
return null; return null;
} }
getFieldFunctionType(fieldSpec) { getFieldFunctionType(fieldSpec: any) {
if (fieldSpec.expression.interpolated) { if (fieldSpec.expression.interpolated) {
return "exponential" return "exponential"
} }
@@ -90,7 +100,7 @@ export default class DataProperty extends React.Component {
return "categorical" return "categorical"
} }
getDataFunctionTypes(fieldSpec) { getDataFunctionTypes(fieldSpec: any) {
if (fieldSpec.expression.interpolated) { if (fieldSpec.expression.interpolated) {
return ["interpolate", "categorical", "interval", "exponential", "identity"] return ["interpolate", "categorical", "interval", "exponential", "identity"]
} }
@@ -100,7 +110,7 @@ export default class DataProperty extends React.Component {
} }
// Order the stops altering the refs to reflect their new position. // Order the stops altering the refs to reflect their new position.
orderStopsByZoom(stops) { orderStopsByZoom(stops: Stop[]) {
const mappedWithRef = stops const mappedWithRef = stops
.map((stop, idx) => { .map((stop, idx) => {
return { return {
@@ -112,7 +122,7 @@ export default class DataProperty extends React.Component {
.sort((a, b) => sortNumerically(a.data[0].zoom, b.data[0].zoom)); .sort((a, b) => sortNumerically(a.data[0].zoom, b.data[0].zoom));
// Fetch the new position of the stops // Fetch the new position of the stops
const newRefs = {}; const newRefs = {} as {[key: number]: string};
mappedWithRef mappedWithRef
.forEach((stop, idx) =>{ .forEach((stop, idx) =>{
newRefs[idx] = stop.ref; newRefs[idx] = stop.ref;
@@ -125,7 +135,7 @@ export default class DataProperty extends React.Component {
return mappedWithRef.map((item) => item.data); return mappedWithRef.map((item) => item.data);
} }
onChange = (fieldName, value) => { onChange = (fieldName: string, value: any) => {
if (value.type === "identity") { if (value.type === "identity") {
value = { value = {
type: value.type, type: value.type,
@@ -139,21 +149,21 @@ export default class DataProperty extends React.Component {
type: value.type, type: value.type,
// Default props if they don't already exist. // Default props if they don't already exist.
stops: [ stops: [
[{zoom: 6, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec)], [{zoom: 6, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec as any)],
[{zoom: 10, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec)] [{zoom: 10, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec as any)]
], ],
...value, ...value,
} }
} }
this.props.onChange(fieldName, value); this.props.onChange!(fieldName, value);
} }
changeStop(changeIdx, stopData, value) { changeStop(changeIdx: number, stopData: { zoom: number | undefined, value: number }, value: number) {
const stops = this.props.value.stops.slice(0) const stops = this.props.value?.stops.slice(0) || []
// const changedStop = stopData.zoom === undefined ? stopData.value : stopData // const changedStop = stopData.zoom === undefined ? stopData.value : stopData
stops[changeIdx] = [ stops[changeIdx] = [
{ {
...stopData, value: stopData.value,
zoom: (stopData.zoom === undefined) ? 0 : stopData.zoom, zoom: (stopData.zoom === undefined) ? 0 : stopData.zoom,
}, },
value value
@@ -168,7 +178,7 @@ export default class DataProperty extends React.Component {
this.onChange(this.props.fieldName, changedValue) this.onChange(this.props.fieldName, changedValue)
} }
changeBase(newValue) { changeBase(newValue: number | undefined) {
const changedValue = { const changedValue = {
...this.props.value, ...this.props.value,
base: newValue base: newValue
@@ -177,11 +187,11 @@ export default class DataProperty extends React.Component {
if (changedValue.base === undefined) { if (changedValue.base === undefined) {
delete changedValue["base"]; delete changedValue["base"];
} }
this.props.onChange(this.props.fieldName, changedValue) this.props.onChange!(this.props.fieldName, changedValue)
} }
changeDataType(propVal) { changeDataType(propVal: string) {
if (propVal === "interpolate") { if (propVal === "interpolate" && this.props.onChangeToZoomFunction) {
this.props.onChangeToZoomFunction(); this.props.onChangeToZoomFunction();
} }
else { else {
@@ -192,41 +202,39 @@ export default class DataProperty extends React.Component {
} }
} }
changeDataProperty(propName, propVal) { changeDataProperty(propName: "property" | "default", propVal: any) {
if (propVal) { if (propVal) {
this.props.value[propName] = propVal this.props.value![propName] = propVal
} }
else { else {
delete this.props.value[propName] delete this.props.value![propName]
} }
this.onChange(this.props.fieldName, this.props.value) this.onChange(this.props.fieldName, this.props.value)
} }
render() { render() {
const {fieldName, fieldType, errors} = this.props; if (typeof this.props.value?.type === "undefined") {
this.props.value!.type = this.getFieldFunctionType(this.props.fieldSpec)
if (typeof this.props.value.type === "undefined") {
this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec)
} }
let dataFields; let dataFields;
if (this.props.value.stops) { if (this.props.value?.stops) {
dataFields = this.props.value.stops.map((stop, idx) => { dataFields = this.props.value.stops.map((stop, idx) => {
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined; const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
const key = this.state.refs[idx]; const key = this.state.refs[idx];
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0]; const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
const value = stop[1] const value = stop[1]
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} /> const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
const dataProps = { const dataProps = {
'aria-label': "Input value", 'aria-label': "Input value",
label: "Data value", label: "Data value",
value: dataLevel, value: dataLevel as any,
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value) onChange: (newData: string | number | undefined) => this.changeStop(idx, { zoom: zoomLevel, value: newData as number }, value)
} }
let dataInput; let dataInput;
if(this.props.value.type === "categorical") { if(this.props.value?.type === "categorical") {
dataInput = <InputString {...dataProps} /> dataInput = <InputString {...dataProps} />
} }
else { else {
@@ -246,16 +254,6 @@ export default class DataProperty extends React.Component {
</div> </div>
} }
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
const foundErrors = Object.entries(errors).filter(([key, error]) => {
return key.startsWith(errorKeyStart);
});
const message = foundErrors.map(([key, error]) => {
return error.message;
}).join("");
const error = message ? {message} : undefined;
return <tr key={key}> return <tr key={key}>
<td> <td>
{zoomInput} {zoomInput}
@@ -269,7 +267,7 @@ export default class DataProperty extends React.Component {
fieldName={this.props.fieldName} fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec} fieldSpec={this.props.fieldSpec}
value={value} value={value}
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)} onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue as number)}
/> />
</td> </td>
<td> <td>
@@ -289,14 +287,14 @@ export default class DataProperty extends React.Component {
> >
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<InputSelect <InputSelect
value={this.props.value.type} value={this.props.value!.type}
onChange={propVal => this.changeDataType(propVal)} onChange={propVal => this.changeDataType(propVal)}
title={"Select a type of data scale (default is 'categorical')."} title={"Select a type of data scale (default is 'categorical')."}
options={this.getDataFunctionTypes(this.props.fieldSpec)} options={this.getDataFunctionTypes(this.props.fieldSpec)}
/> />
</div> </div>
</Block> </Block>
{this.props.value.type !== "identity" && {this.props.value?.type !== "identity" &&
<Block <Block
label={"Base"} label={"Base"}
key="base" key="base"
@@ -305,8 +303,8 @@ export default class DataProperty extends React.Component {
<InputSpec <InputSpec
fieldName={"base"} fieldName={"base"}
fieldSpec={latest.function.base} fieldSpec={latest.function.base}
value={this.props.value.base} value={this.props.value?.base}
onChange={(_, newValue) => this.changeBase(newValue)} onChange={(_, newValue) => this.changeBase(newValue as number)}
/> />
</div> </div>
</Block> </Block>
@@ -317,7 +315,7 @@ export default class DataProperty extends React.Component {
> >
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<InputString <InputString
value={this.props.value.property} value={this.props.value?.property}
title={"Input a data property to base styles off of."} title={"Input a data property to base styles off of."}
onChange={propVal => this.changeDataProperty("property", propVal)} onChange={propVal => this.changeDataProperty("property", propVal)}
/> />
@@ -331,7 +329,7 @@ export default class DataProperty extends React.Component {
<InputSpec <InputSpec
fieldName={this.props.fieldName} fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec} fieldSpec={this.props.fieldSpec}
value={this.props.value.default} value={this.props.value?.default}
onChange={(_, propVal) => this.changeDataProperty("default", propVal)} onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
/> />
</Block> </Block>
@@ -344,7 +342,7 @@ export default class DataProperty extends React.Component {
<tr> <tr>
<th>Zoom</th> <th>Zoom</th>
<th>Input value</th> <th>Input value</th>
<th rowSpan="2">Output value</th> <th rowSpan={2}>Output value</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -357,7 +355,7 @@ export default class DataProperty extends React.Component {
{dataFields && {dataFields &&
<InputButton <InputButton
className="maputnik-add-stop" className="maputnik-add-stop"
onClick={this.props.onAddStop.bind(this)} onClick={this.props.onAddStop?.bind(this)}
> >
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24"> <svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiTableRowPlusAfter} /> <path fill="currentColor" d={mdiTableRowPlusAfter} />
@@ -366,7 +364,7 @@ export default class DataProperty extends React.Component {
} }
<InputButton <InputButton
className="maputnik-add-stop" className="maputnik-add-stop"
onClick={this.props.onExpressionClick.bind(this)} onClick={this.props.onExpressionClick?.bind(this)}
> >
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24"> <svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiFunctionVariant} /> <path fill="currentColor" d={mdiFunctionVariant} />
@@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputButton from './InputButton' import InputButton from './InputButton'
import {MdDelete} from 'react-icons/md' import {MdDelete} from 'react-icons/md'
export default class DeleteStopButton extends React.Component { type DeleteStopButtonProps = {
static propTypes = { onClick?(...args: unknown[]): unknown
onClick: PropTypes.func, };
}
export default class DeleteStopButton extends React.Component<DeleteStopButtonProps> {
render() { render() {
return <InputButton return <InputButton
className="maputnik-delete-stop" className="maputnik-delete-stop"
@@ -1,45 +1,46 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import {MdDelete, MdUndo} from 'react-icons/md'
import stringifyPretty from 'json-stringify-pretty-compact'
import Block from './Block' import Block from './Block'
import InputButton from './InputButton' import InputButton from './InputButton'
import {MdDelete, MdUndo} from 'react-icons/md'
import FieldString from './FieldString'
import labelFromFieldName from './_labelFromFieldName' import labelFromFieldName from './_labelFromFieldName'
import stringifyPretty from 'json-stringify-pretty-compact'
import FieldJson from './FieldJson' import FieldJson from './FieldJson'
export default class ExpressionProperty extends React.Component { type ExpressionPropertyProps = {
static propTypes = { onDelete?(...args: unknown[]): unknown
onDelete: PropTypes.func, fieldName: string
fieldName: PropTypes.string, fieldType?: string
fieldType: PropTypes.string, fieldSpec?: object
fieldSpec: PropTypes.object, value?: any
value: PropTypes.any, errors?: {[key: string]: any}
errors: PropTypes.object, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, onUndo?(...args: unknown[]): unknown
onUndo: PropTypes.func, canUndo?(...args: unknown[]): unknown
canUndo: PropTypes.func, onFocus?(...args: unknown[]): unknown
onFocus: PropTypes.func, onBlur?(...args: unknown[]): unknown
onBlur: PropTypes.func, };
}
type ExpressionPropertyState = {
jsonError: boolean
};
export default class ExpressionProperty extends React.Component<ExpressionPropertyProps, ExpressionPropertyState> {
static defaultProps = { static defaultProps = {
errors: {}, errors: {},
onFocus: () => {}, onFocus: () => {},
onBlur: () => {}, onBlur: () => {},
} }
constructor (props) { constructor (props:ExpressionPropertyProps) {
super(); super(props);
this.state = { this.state = {
jsonError: false, jsonError: false,
}; };
} }
onJSONInvalid = (err) => { onJSONInvalid = (_err: Error) => {
this.setState({ this.setState({
jsonError: true, jsonError: true,
}) })
@@ -82,11 +83,11 @@ export default class ExpressionProperty extends React.Component {
const fieldKey = fieldType === undefined ? fieldName : `${fieldType}.${fieldName}`; const fieldKey = fieldType === undefined ? fieldName : `${fieldType}.${fieldName}`;
const fieldError = errors[fieldKey]; const fieldError = errors![fieldKey];
const errorKeyStart = `${fieldKey}[`; const errorKeyStart = `${fieldKey}[`;
const foundErrors = []; const foundErrors = [];
function getValue (data) { function getValue(data: any) {
return stringifyPretty(data, {indent: 2, maxLength: 38}) return stringifyPretty(data, {indent: 2, maxLength: 38})
} }
@@ -94,11 +95,11 @@ export default class ExpressionProperty extends React.Component {
foundErrors.push({message: "Invalid JSON"}); foundErrors.push({message: "Invalid JSON"});
} }
else { else {
Object.entries(errors) Object.entries(errors!)
.filter(([key, error]) => { .filter(([key, _error]) => {
return key.startsWith(errorKeyStart); return key.startsWith(errorKeyStart);
}) })
.forEach(([key, error]) => { .forEach(([_key, error]) => {
return foundErrors.push(error); return foundErrors.push(error);
}) })
@@ -1,15 +1,14 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block' import Block from './Block'
import FieldString from './FieldString' import FieldString from './FieldString'
export default class BlockComment extends React.Component { type BlockCommentProps = {
static propTypes = { value?: string
value: PropTypes.string, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, };
}
export default class BlockComment extends React.Component<BlockCommentProps> {
render() { render() {
const fieldSpec = { const fieldSpec = {
doc: "Comments for the current layer. This is non-standard and not in the spec." doc: "Comments for the current layer. This is non-standard and not in the spec."
@@ -1,17 +1,17 @@
import React from 'react' import React from 'react'
import Block from './Block' import Block from './Block'
import PropTypes from 'prop-types'
import FieldAutocomplete from './FieldAutocomplete' import FieldAutocomplete from './FieldAutocomplete'
export default class FieldFont extends React.Component { type FieldFontProps = {
static propTypes = { value?: string[]
value: PropTypes.array, default?: string[]
default: PropTypes.array, fonts?: unknown[]
fonts: PropTypes.array, style?: object
style: PropTypes.object, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, label?: string
} };
export default class FieldFont extends React.Component<FieldFontProps> {
static defaultProps = { static defaultProps = {
fonts: [] fonts: []
} }
@@ -28,7 +28,7 @@ export default class FieldFont extends React.Component {
} }
} }
changeFont(idx, newValue) { changeFont(idx: number, newValue: string) {
const changedValues = this.values.slice(0) const changedValues = this.values.slice(0)
changedValues[idx] = newValue changedValues[idx] = newValue
const filteredValues = changedValues const filteredValues = changedValues
@@ -45,7 +45,7 @@ export default class FieldFont extends React.Component {
> >
<FieldAutocomplete <FieldAutocomplete
value={value} value={value}
options={this.props.fonts.map(f => [f, f])} options={this.props.fonts!.map(f => [f, f])}
onChange={this.changeFont.bind(this, i)} onChange={this.changeFont.bind(this, i)}
/> />
</li> </li>
@@ -1,18 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import FieldString from './FieldString' import FieldString from './FieldString'
export default class BlockId extends React.Component { type BlockIdProps = {
static propTypes = { value: string
value: PropTypes.string.isRequired, wdKey: string
wdKey: PropTypes.string.isRequired, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[]
error: PropTypes.object, };
}
export default class BlockId extends React.Component<BlockIdProps> {
render() { render() {
return <Block label={"ID"} fieldSpec={latest.layer.id} return <Block label={"ID"} fieldSpec={latest.layer.id}
data-wd-key={this.props.wdKey} data-wd-key={this.props.wdKey}
@@ -1,17 +1,16 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import FieldNumber from './FieldNumber' import FieldNumber from './FieldNumber'
export default class BlockMaxZoom extends React.Component { type BlockMaxZoomProps = {
static propTypes = { value?: number
value: PropTypes.number, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[]
error: PropTypes.object, };
}
export default class BlockMaxZoom extends React.Component<BlockMaxZoomProps> {
render() { render() {
return <Block label={"Max Zoom"} fieldSpec={latest.layer.maxzoom} return <Block label={"Max Zoom"} fieldSpec={latest.layer.maxzoom}
error={this.props.error} error={this.props.error}
@@ -1,17 +1,16 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import FieldNumber from './FieldNumber' import FieldNumber from './FieldNumber'
export default class BlockMinZoom extends React.Component { type BlockMinZoomProps = {
static propTypes = { value?: number
value: PropTypes.number, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[]
error: PropTypes.object, };
}
export default class BlockMinZoom extends React.Component<BlockMinZoomProps> {
render() { render() {
return <Block label={"Min Zoom"} fieldSpec={latest.layer.minzoom} return <Block label={"Min Zoom"} fieldSpec={latest.layer.minzoom}
error={this.props.error} error={this.props.error}
@@ -1,19 +1,18 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import FieldAutocomplete from './FieldAutocomplete' import FieldAutocomplete from './FieldAutocomplete'
export default class BlockSource extends React.Component { type BlockSourceProps = {
static propTypes = { value?: string
value: PropTypes.string, wdKey?: string
wdKey: PropTypes.string, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, sourceIds?: unknown[]
sourceIds: PropTypes.array, error?: unknown[]
error: PropTypes.object, };
}
export default class BlockSource extends React.Component<BlockSourceProps> {
static defaultProps = { static defaultProps = {
onChange: () => {}, onChange: () => {},
sourceIds: [], sourceIds: [],
@@ -28,8 +27,8 @@ export default class BlockSource extends React.Component {
> >
<FieldAutocomplete <FieldAutocomplete
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange!}
options={this.props.sourceIds.map(src => [src, src])} options={this.props.sourceIds!.map(src => [src, src])}
/> />
</Block> </Block>
} }
@@ -1,18 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import FieldAutocomplete from './FieldAutocomplete' import FieldAutocomplete from './FieldAutocomplete'
export default class BlockSourceLayer extends React.Component { type BlockSourceLayerProps = {
static propTypes = { value?: string
value: PropTypes.string, onChange?(...args: unknown[]): unknown
onChange: PropTypes.func, sourceLayerIds?: unknown[]
sourceLayerIds: PropTypes.array, isFixed?: boolean
isFixed: PropTypes.bool, };
}
export default class BlockSourceLayer extends React.Component<BlockSourceLayerProps> {
static defaultProps = { static defaultProps = {
onChange: () => {}, onChange: () => {},
sourceLayerIds: [], sourceLayerIds: [],
@@ -26,8 +25,8 @@ export default class BlockSourceLayer extends React.Component {
<FieldAutocomplete <FieldAutocomplete
keepMenuWithinWindowBounds={!!this.props.isFixed} keepMenuWithinWindowBounds={!!this.props.isFixed}
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange!}
options={this.props.sourceLayerIds.map(l => [l, l])} options={this.props.sourceLayerIds!.map(l => [l, l])}
/> />
</Block> </Block>
} }
-27
View File
@@ -1,27 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import FieldAutocomplete from './FieldAutocomplete'
export default class FieldSymbol extends React.Component {
static propTypes = {
value: PropTypes.string,
icons: PropTypes.array,
style: PropTypes.object,
onChange: PropTypes.func.isRequired,
}
static defaultProps = {
icons: []
}
render() {
return <FieldAutocomplete
value={this.props.value}
options={this.props.icons.map(f => [f, f])}
onChange={this.props.onChange}
wrapperStyle={this.props.style}
/>
}
}
+25
View File
@@ -0,0 +1,25 @@
import React from 'react'
import FieldAutocomplete from './FieldAutocomplete'
type FieldSymbolProps = {
value?: string
icons?: unknown[]
onChange(...args: unknown[]): unknown
};
export default class FieldSymbol extends React.Component<FieldSymbolProps> {
static defaultProps = {
icons: []
}
render() {
return <FieldAutocomplete
value={this.props.value}
options={this.props.icons!.map(f => [f, f])}
onChange={this.props.onChange}
/>
}
}
@@ -1,20 +1,19 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import Block from './Block' import Block from './Block'
import FieldSelect from './FieldSelect' import FieldSelect from './FieldSelect'
import FieldString from './FieldString' import FieldString from './FieldString'
export default class BlockType extends React.Component { type BlockTypeProps = {
static propTypes = { value: string
value: PropTypes.string.isRequired, wdKey?: string
wdKey: PropTypes.string, onChange(...args: unknown[]): unknown
onChange: PropTypes.func.isRequired, error?: unknown[]
error: PropTypes.object, disabled?: boolean
disabled: PropTypes.bool, };
}
export default class BlockType extends React.Component<BlockTypeProps> {
static defaultProps = { static defaultProps = {
disabled: false, disabled: false,
} }
@@ -1,37 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputButton from './InputButton' import InputButton from './InputButton'
import {MdFunctions, MdInsertChart} from 'react-icons/md' import {MdFunctions, MdInsertChart} from 'react-icons/md'
import {mdiFunctionVariant} from '@mdi/js'; import {mdiFunctionVariant} from '@mdi/js';
type FunctionInputButtonsProps = {
fieldSpec?: any
onZoomClick?(...args: unknown[]): unknown
onDataClick?(...args: unknown[]): unknown
onExpressionClick?(...args: unknown[]): unknown
};
/** export default class FunctionInputButtons extends React.Component<FunctionInputButtonsProps> {
* So here we can't just check is `Array.isArray(value)` because certain
* properties accept arrays as values, for example `text-font`. So we must try
* and create an expression.
*/
function isExpression(value, fieldSpec={}) {
if (!Array.isArray(value)) {
return false;
}
try {
expression.createExpression(value, fieldSpec);
return true;
}
catch (err) {
return false;
}
}
export default class FunctionInputButtons extends React.Component {
static propTypes = {
fieldSpec: PropTypes.object,
onZoomClick: PropTypes.func,
onDataClick: PropTypes.func,
onExpressionClick: PropTypes.func,
}
render() { render() {
let makeZoomInputButton, makeDataInputButton, expressionInputButton; let makeZoomInputButton, makeDataInputButton, expressionInputButton;
@@ -1,25 +1,24 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import SpecField from './SpecField' import SpecField, {SpecFieldProps} from './SpecField'
import FunctionButtons from './_FunctionButtons' import FunctionButtons from './_FunctionButtons'
import Block from './Block'
import labelFromFieldName from './_labelFromFieldName' import labelFromFieldName from './_labelFromFieldName'
export default class SpecProperty extends React.Component { type SpecPropertyProps = SpecFieldProps & {
static propTypes = { onZoomClick(...args: unknown[]): unknown
onZoomClick: PropTypes.func.isRequired, onDataClick(...args: unknown[]): unknown
onDataClick: PropTypes.func.isRequired, fieldName?: string
fieldName: PropTypes.string, fieldType?: string
fieldType: PropTypes.string, fieldSpec?: any
fieldSpec: PropTypes.object, value?: any
value: PropTypes.any, errors?: unknown[]
errors: PropTypes.object, onExpressionClick?(...args: unknown[]): unknown
onExpressionClick: PropTypes.func, };
}
export default class SpecProperty extends React.Component<SpecPropertyProps> {
static defaultProps = { static defaultProps = {
errors: {}, errors: {},
} }
@@ -31,17 +30,16 @@ export default class SpecProperty extends React.Component {
fieldSpec={this.props.fieldSpec} fieldSpec={this.props.fieldSpec}
onZoomClick={this.props.onZoomClick} onZoomClick={this.props.onZoomClick}
onDataClick={this.props.onDataClick} onDataClick={this.props.onDataClick}
value={this.props.value}
onExpressionClick={this.props.onExpressionClick} onExpressionClick={this.props.onExpressionClick}
/> />
const error = errors[fieldType+"."+fieldName]; const error = errors![fieldType+"."+fieldName as any] as any;
return <SpecField return <SpecField
{...this.props} {...this.props}
error={error} error={error}
fieldSpec={this.props.fieldSpec} fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)} label={labelFromFieldName(this.props.fieldName || '')}
action={functionBtn} action={functionBtn}
/> />
} }
@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js'; import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
@@ -7,7 +6,6 @@ import InputButton from './InputButton'
import InputSpec from './InputSpec' import InputSpec from './InputSpec'
import InputNumber from './InputNumber' import InputNumber from './InputNumber'
import InputSelect from './InputSelect' import InputSelect from './InputSelect'
import FieldDocLabel from './FieldDocLabel'
import Block from './Block' import Block from './Block'
import DeleteStopButton from './_DeleteStopButton' import DeleteStopButton from './_DeleteStopButton'
@@ -22,12 +20,12 @@ import sortNumerically from '../libs/sort-numerically'
* *
* When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus. * When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus.
*/ */
function setStopRefs(props, state) { function setStopRefs(props: ZoomPropertyProps, state: ZoomPropertyState) {
// This is initialsed below only if required to improved performance. // This is initialsed below only if required to improved performance.
let newRefs; let newRefs: {[key: number]: string} = {};
if(props.value && props.value.stops) { if(props.value && (props.value as ZoomWithStops).stops) {
props.value.stops.forEach((val, idx) => { (props.value as ZoomWithStops).stops.forEach((_val, idx: number) => {
if(!state.refs.hasOwnProperty(idx)) { if(!state.refs.hasOwnProperty(idx)) {
if(!newRefs) { if(!newRefs) {
newRefs = {...state}; newRefs = {...state};
@@ -40,32 +38,39 @@ function setStopRefs(props, state) {
return newRefs; return newRefs;
} }
type ZoomWithStops = {
stops: [number | undefined, number][]
base?: number
}
export default class ZoomProperty extends React.Component {
static propTypes = { type ZoomPropertyProps = {
onChange: PropTypes.func, onChange?(...args: unknown[]): unknown
onDeleteStop: PropTypes.func, onChangeToDataFunction?(...args: unknown[]): unknown
onAddStop: PropTypes.func, onDeleteStop?(...args: unknown[]): unknown
onExpressionClick: PropTypes.func, onAddStop?(...args: unknown[]): unknown
fieldType: PropTypes.string, onExpressionClick?(...args: unknown[]): unknown
fieldName: PropTypes.string, fieldType?: string
fieldSpec: PropTypes.object, fieldName: string
errors: PropTypes.object, fieldSpec?: {
value: PropTypes.oneOfType([ "property-type"?: string
PropTypes.object, "function-type"?: string
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
} }
errors?: object
value?: ZoomWithStops
};
type ZoomPropertyState = {
refs: {[key: number]: string}
}
export default class ZoomProperty extends React.Component<ZoomPropertyProps, ZoomPropertyState> {
static defaultProps = { static defaultProps = {
errors: {}, errors: {},
} }
state = { state = {
refs: {} refs: {} as {[key: number]: string}
} }
componentDidMount() { componentDidMount() {
@@ -78,7 +83,7 @@ export default class ZoomProperty extends React.Component {
} }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props: ZoomPropertyProps, state: ZoomPropertyState) {
const newRefs = setStopRefs(props, state); const newRefs = setStopRefs(props, state);
if(newRefs) { if(newRefs) {
return { return {
@@ -89,7 +94,7 @@ export default class ZoomProperty extends React.Component {
} }
// Order the stops altering the refs to reflect their new position. // Order the stops altering the refs to reflect their new position.
orderStopsByZoom(stops) { orderStopsByZoom(stops: ZoomWithStops["stops"]) {
const mappedWithRef = stops const mappedWithRef = stops
.map((stop, idx) => { .map((stop, idx) => {
return { return {
@@ -98,10 +103,10 @@ export default class ZoomProperty extends React.Component {
} }
}) })
// Sort by zoom // Sort by zoom
.sort((a, b) => sortNumerically(a.data[0], b.data[0])); .sort((a, b) => sortNumerically(a.data[0]!, b.data[0]!));
// Fetch the new position of the stops // Fetch the new position of the stops
const newRefs = {}; const newRefs: {[key:number]: string} = {};
mappedWithRef mappedWithRef
.forEach((stop, idx) =>{ .forEach((stop, idx) =>{
newRefs[idx] = stop.ref; newRefs[idx] = stop.ref;
@@ -114,20 +119,20 @@ export default class ZoomProperty extends React.Component {
return mappedWithRef.map((item) => item.data); return mappedWithRef.map((item) => item.data);
} }
changeZoomStop(changeIdx, stopData, value) { changeZoomStop(changeIdx: number, stopData: number | undefined, value: number) {
const stops = this.props.value.stops.slice(0); const stops = (this.props.value as ZoomWithStops).stops.slice(0);
stops[changeIdx] = [stopData, value]; stops[changeIdx] = [stopData, value];
const orderedStops = this.orderStopsByZoom(stops); const orderedStops = this.orderStopsByZoom(stops);
const changedValue = { const changedValue = {
...this.props.value, ...this.props.value as ZoomWithStops,
stops: orderedStops stops: orderedStops
} }
this.props.onChange(this.props.fieldName, changedValue) this.props.onChange!(this.props.fieldName, changedValue)
} }
changeBase(newValue) { changeBase(newValue: number | undefined) {
const changedValue = { const changedValue = {
...this.props.value, ...this.props.value,
base: newValue base: newValue
@@ -136,33 +141,21 @@ export default class ZoomProperty extends React.Component {
if (changedValue.base === undefined) { if (changedValue.base === undefined) {
delete changedValue["base"]; delete changedValue["base"];
} }
this.props.onChange(this.props.fieldName, changedValue) this.props.onChange!(this.props.fieldName, changedValue)
} }
changeDataType = (type) => { changeDataType = (type: string) => {
if (type !== "interpolate") { if (type !== "interpolate" && this.props.onChangeToDataFunction) {
this.props.onChangeToDataFunction(type); this.props.onChangeToDataFunction(type);
} }
} }
render() { render() {
const {fieldName, fieldType, errors} = this.props; const zoomFields = this.props.value?.stops.map((stop, idx) => {
const zoomFields = this.props.value.stops.map((stop, idx) => {
const zoomLevel = stop[0] const zoomLevel = stop[0]
const key = this.state.refs[idx]; const key = this.state.refs[idx];
const value = stop[1] const value = stop[1]
const deleteStopBtn= <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} /> const deleteStopBtn= <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
const foundErrors = Object.entries(errors).filter(([key, error]) => {
return key.startsWith(errorKeyStart);
});
const message = foundErrors.map(([key, error]) => {
return error.message;
}).join("");
const error = message ? {message} : undefined;
return <tr return <tr
key={key} key={key}
@@ -180,9 +173,9 @@ export default class ZoomProperty extends React.Component {
<InputSpec <InputSpec
aria-label="Output value" aria-label="Output value"
fieldName={this.props.fieldName} fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec} fieldSpec={this.props.fieldSpec as any}
value={value} value={value}
onChange={(_, newValue) => this.changeZoomStop(idx, zoomLevel, newValue)} onChange={(_, newValue) => this.changeZoomStop(idx, zoomLevel, newValue as number)}
/> />
</td> </td>
<td> <td>
@@ -204,7 +197,7 @@ export default class ZoomProperty extends React.Component {
value={"interpolate"} value={"interpolate"}
onChange={propVal => this.changeDataType(propVal)} onChange={propVal => this.changeDataType(propVal)}
title={"Select a type of data scale (default is 'categorical')."} title={"Select a type of data scale (default is 'categorical')."}
options={this.getDataFunctionTypes(this.props.fieldSpec)} options={this.getDataFunctionTypes(this.props.fieldSpec!)}
/> />
</div> </div>
</Block> </Block>
@@ -215,8 +208,8 @@ export default class ZoomProperty extends React.Component {
<InputSpec <InputSpec
fieldName={"base"} fieldName={"base"}
fieldSpec={latest.function.base} fieldSpec={latest.function.base}
value={this.props.value.base} value={this.props.value?.base}
onChange={(_, newValue) => this.changeBase(newValue)} onChange={(_, newValue) => this.changeBase(newValue as number | undefined)}
/> />
</div> </div>
</Block> </Block>
@@ -226,7 +219,7 @@ export default class ZoomProperty extends React.Component {
<thead> <thead>
<tr> <tr>
<th>Zoom</th> <th>Zoom</th>
<th rowSpan="2">Output value</th> <th rowSpan={2}>Output value</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -237,7 +230,7 @@ export default class ZoomProperty extends React.Component {
<div className="maputnik-toolbox"> <div className="maputnik-toolbox">
<InputButton <InputButton
className="maputnik-add-stop" className="maputnik-add-stop"
onClick={this.props.onAddStop.bind(this)} onClick={this.props.onAddStop?.bind(this)}
> >
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24"> <svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiTableRowPlusAfter} /> <path fill="currentColor" d={mdiTableRowPlusAfter} />
@@ -245,7 +238,7 @@ export default class ZoomProperty extends React.Component {
</InputButton> </InputButton>
<InputButton <InputButton
className="maputnik-add-stop" className="maputnik-add-stop"
onClick={this.props.onExpressionClick.bind(this)} onClick={this.props.onExpressionClick?.bind(this)}
> >
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24"> <svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiFunctionVariant} /> <path fill="currentColor" d={mdiFunctionVariant} />
@@ -257,7 +250,10 @@ export default class ZoomProperty extends React.Component {
</div> </div>
} }
getDataFunctionTypes(fieldSpec) { getDataFunctionTypes(fieldSpec: {
"property-type"?: string
"function-type"?: string
}) {
if (fieldSpec['property-type'] === 'data-driven') { if (fieldSpec['property-type'] === 'data-driven') {
return ["interpolate", "categorical", "interval", "exponential", "identity"]; return ["interpolate", "categorical", "interval", "exponential", "identity"];
} }
@@ -265,5 +261,4 @@ export default class ZoomProperty extends React.Component {
return ["interpolate"]; return ["interpolate"];
} }
} }
} }
@@ -1,6 +1,6 @@
import capitalize from 'lodash.capitalize' import capitalize from 'lodash.capitalize'
export default function labelFromFieldName(fieldName) { export default function labelFromFieldName(fieldName: string) {
let label; let label;
const parts = fieldName.split('-'); const parts = fieldName.split('-');
if (parts.length > 1) { if (parts.length > 1) {
+6 -6
View File
@@ -1,8 +1,8 @@
import throttle from 'lodash.throttle' import throttle from 'lodash.throttle'
export default { // Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page. const reducedMotionEnabled = throttle(() => {
reducedMotionEnabled: throttle(() => { return window.matchMedia("(prefers-reduced-motion: reduce)").matches
return window.matchMedia("(prefers-reduced-motion: reduce)").matches }, 3000)
}, 3000)
} export { reducedMotionEnabled }
-3
View File
@@ -1,3 +0,0 @@
export function formatLayerId (id) {
return id === "" ? "[empty_string]" : `'${id}'`;
}
+3
View File
@@ -0,0 +1,3 @@
export function formatLayerId (id: string | undefined) {
return id === "" ? "[empty_string]" : `'${id}'`;
}
@@ -1,7 +1,7 @@
/** /**
* If we don't have a default value just make one up * If we don't have a default value just make one up
*/ */
export function findDefaultFromSpec (spec) { export function findDefaultFromSpec(spec: { type: 'string' | 'color' | 'boolean' | 'array', default?: any }) {
if (spec.hasOwnProperty('default')) { if (spec.hasOwnProperty('default')) {
return spec.default; return spec.default;
} }