mirror of
https://github.com/maputnik/editor.git
synced 2026-07-26 07:47:25 +00:00
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:
Generated
+17
@@ -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",
|
||||||
|
|||||||
@@ -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 {
|
|||||||
—
|
—
|
||||||
<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,
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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."
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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)];
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
}
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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">}
|
||||||
|
|||||||
@@ -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"
|
||||||
+12
-15
@@ -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>
|
||||||
}
|
}
|
||||||
+58
-40
@@ -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
|
||||||
@@ -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>
|
||||||
}
|
}
|
||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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) {
|
||||||
@@ -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 }
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export function formatLayerId (id) {
|
|
||||||
return id === "" ? "[empty_string]" : `'${id}'`;
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user