Compare commits

...

4 Commits

Author SHA1 Message Date
HarelM 90f50d98e7 Fix more types, migrate two more files 2023-12-24 00:42:02 +02:00
HarelM 08f7a427f6 More componenet migration 2023-12-23 15:40:27 +02:00
HarelM 90b692158a Another one bites the dust 2023-12-23 14:58:33 +02:00
HarelM 5a62f4ab21 Improve types, migrate another file 2023-12-22 23:59:51 +02:00
35 changed files with 322 additions and 253 deletions
+10
View File
@@ -75,6 +75,7 @@
"@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-menubutton": "^6.2.13",
"@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-collapse": "^5.0.4",
@@ -4889,6 +4890,15 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-aria-menubutton": {
"version": "6.2.13",
"resolved": "https://registry.npmjs.org/@types/react-aria-menubutton/-/react-aria-menubutton-6.2.13.tgz",
"integrity": "sha512-olSjeIzNzn0KrbShOmBwchHS++khDXBYFTO2U802o8LDHANLms7zUsJhdecfqFpwdFMHxFiMMlCn2nJNCEHWlQ==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-aria-modal": { "node_modules/@types/react-aria-modal": {
"version": "4.0.9", "version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/react-aria-modal/-/react-aria-modal-4.0.9.tgz", "resolved": "https://registry.npmjs.org/@types/react-aria-modal/-/react-aria-modal-4.0.9.tgz",
+1
View File
@@ -104,6 +104,7 @@
"@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-menubutton": "^6.2.13",
"@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-collapse": "^5.0.4",
+1 -1
View File
@@ -12,7 +12,7 @@ type BlockProps = {
onChange?(...args: unknown[]): unknown onChange?(...args: unknown[]): unknown
fieldSpec?: object fieldSpec?: object
wideMode?: boolean wideMode?: boolean
error?: unknown[] error?: {message: string}
}; };
type BlockState = { type BlockState = {
+3 -1
View File
@@ -5,7 +5,8 @@ import InputString from './InputString'
type FieldCommentProps = { type FieldCommentProps = {
value?: string value?: string
onChange(...args: unknown[]): unknown onChange(value: string | undefined): unknown
error: {message: string}
}; };
export default class FieldComment extends React.Component<FieldCommentProps> { export default class FieldComment extends React.Component<FieldCommentProps> {
@@ -18,6 +19,7 @@ export default class FieldComment extends React.Component<FieldCommentProps> {
label={"Comments"} label={"Comments"}
fieldSpec={fieldSpec} fieldSpec={fieldSpec}
data-wd-key="layer-comment" data-wd-key="layer-comment"
error={this.props.error}
> >
<InputString <InputString
multi={true} multi={true}
+1 -1
View File
@@ -107,7 +107,7 @@ type FieldFunctionProps = {
fieldName: string fieldName: string
fieldType: string fieldType: string
fieldSpec: any fieldSpec: any
errors?: unknown[] errors?: {[key: string]: {message: string}}
value?: any value?: any
}; };
+2 -2
View File
@@ -7,8 +7,8 @@ import InputString from './InputString'
type FieldIdProps = { type FieldIdProps = {
value: string value: string
wdKey: string wdKey: string
onChange(...args: unknown[]): unknown onChange(value: string | undefined): unknown
error?: unknown[] error?: {message: string}
}; };
export default class FieldId extends React.Component<FieldIdProps> { export default class FieldId extends React.Component<FieldIdProps> {
+2 -2
View File
@@ -6,8 +6,8 @@ import InputNumber from './InputNumber'
type FieldMaxZoomProps = { type FieldMaxZoomProps = {
value?: number value?: number
onChange(...args: unknown[]): unknown onChange(value: number | undefined): unknown
error?: unknown[] error?: {message: string}
}; };
export default class FieldMaxZoom extends React.Component<FieldMaxZoomProps> { export default class FieldMaxZoom extends React.Component<FieldMaxZoomProps> {
+1 -1
View File
@@ -7,7 +7,7 @@ import InputNumber from './InputNumber'
type FieldMinZoomProps = { type FieldMinZoomProps = {
value?: number value?: number
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
error?: unknown[] error?: {message: string}
}; };
export default class FieldMinZoom extends React.Component<FieldMinZoomProps> { export default class FieldMinZoom extends React.Component<FieldMinZoomProps> {
+2 -2
View File
@@ -7,9 +7,9 @@ import InputAutocomplete from './InputAutocomplete'
type FieldSourceProps = { type FieldSourceProps = {
value?: string value?: string
wdKey?: string wdKey?: string
onChange?(...args: unknown[]): unknown onChange?(value: string| undefined): unknown
sourceIds?: unknown[] sourceIds?: unknown[]
error?: unknown[] error?: {message: string}
}; };
export default class FieldSource extends React.Component<FieldSourceProps> { export default class FieldSource extends React.Component<FieldSourceProps> {
+5 -1
View File
@@ -9,6 +9,7 @@ type FieldSourceLayerProps = {
onChange?(...args: unknown[]): unknown onChange?(...args: unknown[]): unknown
sourceLayerIds?: unknown[] sourceLayerIds?: unknown[]
isFixed?: boolean isFixed?: boolean
error?: {message: string}
}; };
export default class FieldSourceLayer extends React.Component<FieldSourceLayerProps> { export default class FieldSourceLayer extends React.Component<FieldSourceLayerProps> {
@@ -19,8 +20,11 @@ export default class FieldSourceLayer extends React.Component<FieldSourceLayerPr
} }
render() { render() {
return <Block label={"Source Layer"} fieldSpec={latest.layer['source-layer']} return <Block
label={"Source Layer"}
fieldSpec={latest.layer['source-layer']}
data-wd-key="layer-source-layer" data-wd-key="layer-source-layer"
error={this.props.error}
> >
<InputAutocomplete <InputAutocomplete
keepMenuWithinWindowBounds={!!this.props.isFixed} keepMenuWithinWindowBounds={!!this.props.isFixed}
+2 -2
View File
@@ -8,8 +8,8 @@ import InputString from './InputString'
type FieldTypeProps = { type FieldTypeProps = {
value: string value: string
wdKey?: string wdKey?: string
onChange(...args: unknown[]): unknown onChange(value: string): unknown
error?: unknown[] | undefined error?: {message: string}
disabled?: boolean disabled?: boolean
}; };
@@ -1,10 +1,9 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import {combiningFilterOps} from '../libs/filterops' import {combiningFilterOps} from '../libs/filterops'
import {mdiTableRowPlusAfter} from '@mdi/js'; import {mdiTableRowPlusAfter} from '@mdi/js';
import {isEqual} from 'lodash'; import {isEqual} from 'lodash';
import {latest, migrate, convertFilter} from '@maplibre/maplibre-gl-style-spec' import {latest, migrate, convertFilter, ExpressionSpecification, LegacyFilterSpecification, StyleSpecification} from '@maplibre/maplibre-gl-style-spec'
import InputSelect from './InputSelect' import InputSelect from './InputSelect'
import Block from './Block' import Block from './Block'
import SingleFilterEditor from './SingleFilterEditor' import SingleFilterEditor from './SingleFilterEditor'
@@ -15,7 +14,7 @@ import ExpressionProperty from './_ExpressionProperty';
import {mdiFunctionVariant} from '@mdi/js'; import {mdiFunctionVariant} from '@mdi/js';
function combiningFilter (props) { function combiningFilter(props: FilterEditorProps): LegacyFilterSpecification | ExpressionSpecification {
let filter = props.filter || ['all']; let filter = props.filter || ['all'];
if (!Array.isArray(filter)) { if (!Array.isArray(filter)) {
@@ -30,14 +29,14 @@ function combiningFilter (props) {
filters = [filter.slice(0)]; filters = [filter.slice(0)];
} }
return [combiningOp, ...filters]; return [combiningOp, ...filters] as LegacyFilterSpecification | ExpressionSpecification;
} }
function migrateFilter (filter) { function migrateFilter(filter: LegacyFilterSpecification | ExpressionSpecification) {
return migrate(createStyleFromFilter(filter)).layers[0].filter; return (migrate(createStyleFromFilter(filter)).layers[0] as any).filter;
} }
function createStyleFromFilter (filter) { function createStyleFromFilter(filter: LegacyFilterSpecification | ExpressionSpecification): StyleSpecification & {id: string} {
return { return {
"id": "tmp", "id": "tmp",
"version": 8, "version": 8,
@@ -69,7 +68,7 @@ const FILTER_OPS = [
]; ];
// If we convert a filter that is an expression to an expression it'll remain the same in value // If we convert a filter that is an expression to an expression it'll remain the same in value
function checkIfSimpleFilter (filter) { function checkIfSimpleFilter (filter: LegacyFilterSpecification | ExpressionSpecification) {
if (filter.length === 1 && FILTER_OPS.includes(filter[0])) { if (filter.length === 1 && FILTER_OPS.includes(filter[0])) {
return true; return true;
} }
@@ -77,33 +76,38 @@ function checkIfSimpleFilter (filter) {
return !isEqual(expression, filter); return !isEqual(expression, filter);
} }
function hasCombiningFilter(filter) { function hasCombiningFilter(filter: LegacyFilterSpecification | ExpressionSpecification) {
return combiningFilterOps.indexOf(filter[0]) >= 0 return combiningFilterOps.indexOf(filter[0]) >= 0
} }
function hasNestedCombiningFilter(filter) { function hasNestedCombiningFilter(filter: LegacyFilterSpecification | ExpressionSpecification) {
if(hasCombiningFilter(filter)) { if(hasCombiningFilter(filter)) {
const combinedFilters = filter.slice(1) return filter.slice(1).map(f => hasCombiningFilter(f as any)).filter(f => f == true).length > 0
return filter.slice(1).map(f => hasCombiningFilter(f)).filter(f => f == true).length > 0
} }
return false return false
} }
export default class FilterEditor extends React.Component { type FilterEditorProps = {
static propTypes = {
/** Properties of the vector layer and the available fields */ /** Properties of the vector layer and the available fields */
properties: PropTypes.object, properties?: {[key:string]: any}
filter: PropTypes.array, filter?: any[]
errors: PropTypes.object, errors?: {[key:string]: any}
onChange: PropTypes.func.isRequired, onChange(value: LegacyFilterSpecification | ExpressionSpecification): unknown
} };
type FilterEditorState = {
showDoc: boolean
displaySimpleFilter: boolean
valueIsSimpleFilter?: boolean
};
export default class FilterEditor extends React.Component<FilterEditorProps, FilterEditorState> {
static defaultProps = { static defaultProps = {
filter: ["all"], filter: ["all"],
} }
constructor (props) { constructor (props: FilterEditorProps) {
super(); super(props);
this.state = { this.state = {
showDoc: false, showDoc: false,
displaySimpleFilter: checkIfSimpleFilter(combiningFilter(props)), displaySimpleFilter: checkIfSimpleFilter(combiningFilter(props)),
@@ -111,25 +115,25 @@ export default class FilterEditor extends React.Component {
} }
// Convert filter to combining filter // Convert filter to combining filter
onFilterPartChanged(filterIdx, newPart) { onFilterPartChanged(filterIdx: number, newPart: any[]) {
const newFilter = combiningFilter(this.props).slice(0) const newFilter = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification
newFilter[filterIdx] = newPart newFilter[filterIdx] = newPart
this.props.onChange(newFilter) this.props.onChange(newFilter)
} }
deleteFilterItem(filterIdx) { deleteFilterItem(filterIdx: number) {
const newFilter = combiningFilter(this.props).slice(0) const newFilter = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification
newFilter.splice(filterIdx + 1, 1) newFilter.splice(filterIdx + 1, 1)
this.props.onChange(newFilter) this.props.onChange(newFilter)
} }
addFilterItem = () => { addFilterItem = () => {
const newFilterItem = combiningFilter(this.props).slice(0) const newFilterItem = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification
newFilterItem.push(['==', 'name', '']) (newFilterItem as any[]).push(['==', 'name', ''])
this.props.onChange(newFilterItem) this.props.onChange(newFilterItem)
} }
onToggleDoc = (val) => { onToggleDoc = (val: boolean) => {
this.setState({ this.setState({
showDoc: val showDoc: val
}); });
@@ -149,8 +153,7 @@ export default class FilterEditor extends React.Component {
}) })
} }
static getDerivedStateFromProps (props, currentState) { static getDerivedStateFromProps(props: FilterEditorProps, currentState: FilterEditorState) {
const {filter} = props;
const displaySimpleFilter = checkIfSimpleFilter(combiningFilter(props)); const displaySimpleFilter = checkIfSimpleFilter(combiningFilter(props));
// Upgrade but never downgrade // Upgrade but never downgrade
@@ -178,7 +181,7 @@ export default class FilterEditor extends React.Component {
const fieldSpec={ const fieldSpec={
doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter." doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."
}; };
const defaultFilter = ["all"]; const defaultFilter = ["all"] as LegacyFilterSpecification | ExpressionSpecification;
const isNestedCombiningFilter = displaySimpleFilter && hasNestedCombiningFilter(combiningFilter(this.props)); const isNestedCombiningFilter = displaySimpleFilter && hasNestedCombiningFilter(combiningFilter(this.props));
@@ -201,7 +204,7 @@ export default class FilterEditor extends React.Component {
else if (displaySimpleFilter) { else if (displaySimpleFilter) {
const filter = combiningFilter(this.props); const filter = combiningFilter(this.props);
let combiningOp = filter[0]; let combiningOp = filter[0];
let filters = filter.slice(1) let filters = filter.slice(1) as (LegacyFilterSpecification | ExpressionSpecification)[];
const actions = ( const actions = (
<div> <div>
@@ -218,7 +221,7 @@ export default class FilterEditor extends React.Component {
); );
const editorBlocks = filters.map((f, idx) => { const editorBlocks = filters.map((f, idx) => {
const error = errors[`filter[${idx+1}]`]; const error = errors![`filter[${idx+1}]`];
return ( return (
<div key={`block-${idx}`}> <div key={`block-${idx}`}>
@@ -247,7 +250,7 @@ export default class FilterEditor extends React.Component {
> >
<InputSelect <InputSelect
value={combiningOp} value={combiningOp}
onChange={this.onFilterPartChanged.bind(this, 0)} onChange={(v: [string, any]) => this.onFilterPartChanged(0, v)}
options={[["all", "every filter matches"], ["none", "no filter matches"], ["any", "any filter matches"]]} options={[["all", "every filter matches"], ["none", "no filter matches"], ["any", "any filter matches"]]}
/> />
</Block> </Block>
+1
View File
@@ -10,6 +10,7 @@ import IconMissing from './IconMissing'
type IconLayerProps = { type IconLayerProps = {
type: string type: string
style?: object style?: object
className?: string
}; };
export default class IconLayer extends React.Component<IconLayerProps> { export default class IconLayer extends React.Component<IconLayerProps> {
+1 -1
View File
@@ -8,7 +8,7 @@ const MAX_HEIGHT = 140;
export type InputAutocompleteProps = { export type InputAutocompleteProps = {
value?: string value?: string
options: any[] options: any[]
onChange(...args: unknown[]): unknown onChange(value: string | undefined): unknown
keepMenuWithinWindowBounds?: boolean keepMenuWithinWindowBounds?: boolean
'aria-label'?: string 'aria-label'?: string
}; };
+1 -1
View File
@@ -5,7 +5,7 @@ export type InputSelectProps = {
"data-wd-key"?: string "data-wd-key"?: string
options: [string, any][] | string[] options: [string, any][] | string[]
style?: object style?: object
onChange(value: string): unknown onChange(value: string | [string, any]): unknown
title?: string title?: string
'aria-label'?: string 'aria-label'?: string
}; };
@@ -1,6 +1,8 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton' import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
import {Accordion} from 'react-accessible-accordion';
import {MdMoreVert} from 'react-icons/md'
import FieldJson from './FieldJson' import FieldJson from './FieldJson'
import FilterEditor from './FilterEditor' import FilterEditor from './FilterEditor'
@@ -13,20 +15,18 @@ import FieldMaxZoom from './FieldMaxZoom'
import FieldComment from './FieldComment' import FieldComment from './FieldComment'
import FieldSource from './FieldSource' import FieldSource from './FieldSource'
import FieldSourceLayer from './FieldSourceLayer' import FieldSourceLayer from './FieldSourceLayer'
import {Accordion} from 'react-accessible-accordion';
import {MdMoreVert} from 'react-icons/md'
import { changeType, changeProperty } from '../libs/layer' import { changeType, changeProperty } from '../libs/layer'
import layout from '../config/layout.json' import layout from '../config/layout.json'
import {formatLayerId} from '../util/format'; import {formatLayerId} from '../util/format';
import { SourceSpecification } from '@maplibre/maplibre-gl-style-spec';
import { BackgroundLayerSpecification, LayerSpecification } from 'maplibre-gl';
function getLayoutForType (type) { function getLayoutForType(type: LayerSpecification["type"]) {
return layout[type] ? layout[type] : layout.invalid; return layout[type] ? layout[type] : layout.invalid;
} }
function layoutGroups(layerType) { function layoutGroups(layerType: LayerSpecification["type"]): {title: string, type: string, fields?: string[]}[] {
const layerGroup = { const layerGroup = {
title: 'Layer', title: 'Layer',
type: 'layer' type: 'layer'
@@ -44,25 +44,29 @@ function layoutGroups(layerType) {
.concat([editorGroup]) .concat([editorGroup])
} }
/** Layer editor supporting multiple types of layers. */ type LayerEditorProps = {
export default class LayerEditor extends React.Component { layer: LayerSpecification
static propTypes = { sources?: SourceSpecification
layer: PropTypes.object.isRequired, vectorLayers: {[key: string]: any}
sources: PropTypes.object, spec: object
vectorLayers: PropTypes.object, onLayerChanged(...args: unknown[]): unknown
spec: PropTypes.object.isRequired, onLayerIdChange(...args: unknown[]): unknown
onLayerChanged: PropTypes.func, onMoveLayer(...args: unknown[]): unknown
onLayerIdChange: PropTypes.func, onLayerDestroy(...args: unknown[]): unknown
onMoveLayer: PropTypes.func, onLayerCopy(...args: unknown[]): unknown
onLayerDestroy: PropTypes.func, onLayerVisibilityToggle(...args: unknown[]): unknown
onLayerCopy: PropTypes.func, isFirstLayer?: boolean
onLayerVisibilityToggle: PropTypes.func, isLastLayer?: boolean
isFirstLayer: PropTypes.bool, layerIndex: number
isLastLayer: PropTypes.bool, errors?: any[]
layerIndex: PropTypes.number, };
errors: PropTypes.array,
}
type LayerEditorState = {
editorGroups: {[keys:string]: boolean}
};
/** Layer editor supporting multiple types of layers. */
export default class LayerEditor extends React.Component<LayerEditorProps, LayerEditorState> {
static defaultProps = { static defaultProps = {
onLayerChanged: () => {}, onLayerChanged: () => {},
onLayerIdChange: () => {}, onLayerIdChange: () => {},
@@ -73,11 +77,11 @@ export default class LayerEditor extends React.Component {
reactIconBase: PropTypes.object reactIconBase: PropTypes.object
} }
constructor(props) { constructor(props: LayerEditorProps) {
super(props) super(props)
//TODO: Clean this up and refactor into function //TODO: Clean this up and refactor into function
const editorGroups = {} const editorGroups: {[keys:string]: boolean} = {}
layoutGroups(this.props.layer.type).forEach(group => { layoutGroups(this.props.layer.type).forEach(group => {
editorGroups[group.title] = true editorGroups[group.title] = true
}) })
@@ -85,7 +89,7 @@ export default class LayerEditor extends React.Component {
this.state = { editorGroups } this.state = { editorGroups }
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props: LayerEditorProps, state: LayerEditorState) {
const additionalGroups = { ...state.editorGroups } const additionalGroups = { ...state.editorGroups }
getLayoutForType(props.layer.type).groups.forEach(group => { getLayoutForType(props.layer.type).groups.forEach(group => {
@@ -108,14 +112,14 @@ export default class LayerEditor extends React.Component {
} }
} }
changeProperty(group, property, newValue) { changeProperty(group: keyof LayerSpecification | null, property: string, newValue: any) {
this.props.onLayerChanged( this.props.onLayerChanged(
this.props.layerIndex, this.props.layerIndex,
changeProperty(this.props.layer, group, property, newValue) changeProperty(this.props.layer, group, property, newValue)
) )
} }
onGroupToggle(groupTitle, active) { onGroupToggle(groupTitle: string, active: boolean) {
const changedActiveGroups = { const changedActiveGroups = {
...this.state.editorGroups, ...this.state.editorGroups,
[groupTitle]: active, [groupTitle]: active,
@@ -125,15 +129,15 @@ export default class LayerEditor extends React.Component {
}) })
} }
renderGroupType(type, fields) { renderGroupType(type: string, fields?: string[]): JSX.Element {
let comment = "" let comment = ""
if(this.props.layer.metadata) { if(this.props.layer.metadata) {
comment = this.props.layer.metadata['maputnik:comment'] comment = (this.props.layer.metadata as any)['maputnik:comment']
} }
const {errors, layerIndex} = this.props; const {errors, layerIndex} = this.props;
const errorData = {}; const errorData: {[key in LayerSpecification as string]: {message: string}} = {};
errors.forEach(error => { errors!.forEach(error => {
if ( if (
error.parsed && error.parsed &&
error.parsed.type === "layer" && error.parsed.type === "layer" &&
@@ -146,8 +150,8 @@ export default class LayerEditor extends React.Component {
}) })
let sourceLayerIds; let sourceLayerIds;
if(this.props.sources.hasOwnProperty(this.props.layer.source)) { if(this.props.sources?.hasOwnProperty((this.props.layer as Exclude<LayerSpecification, BackgroundLayerSpecification>).source)) {
sourceLayerIds = this.props.sources[this.props.layer.source].layers; sourceLayerIds = (this.props.sources as any)[(this.props.layer as any).source].layers;
} }
switch(type) { switch(type) {
@@ -169,7 +173,7 @@ export default class LayerEditor extends React.Component {
/> />
{this.props.layer.type !== 'background' && <FieldSource {this.props.layer.type !== 'background' && <FieldSource
error={errorData.source} error={errorData.source}
sourceIds={Object.keys(this.props.sources)} sourceIds={Object.keys(this.props.sources!)}
value={this.props.layer.source} value={this.props.layer.source}
onChange={v => this.changeProperty(null, 'source', v)} onChange={v => this.changeProperty(null, 'source', v)}
/> />
@@ -178,7 +182,7 @@ export default class LayerEditor extends React.Component {
<FieldSourceLayer <FieldSourceLayer
error={errorData['source-layer']} error={errorData['source-layer']}
sourceLayerIds={sourceLayerIds} sourceLayerIds={sourceLayerIds}
value={this.props.layer['source-layer']} value={(this.props.layer as any)['source-layer']}
onChange={v => this.changeProperty(null, 'source-layer', v)} onChange={v => this.changeProperty(null, 'source-layer', v)}
/> />
} }
@@ -202,8 +206,8 @@ export default class LayerEditor extends React.Component {
<div className="maputnik-filter-editor-wrapper"> <div className="maputnik-filter-editor-wrapper">
<FilterEditor <FilterEditor
errors={errorData} errors={errorData}
filter={this.props.layer.filter} filter={(this.props.layer as any).filter}
properties={this.props.vectorLayers[this.props.layer['source-layer']]} properties={this.props.vectorLayers[(this.props.layer as any)['source-layer']]}
onChange={f => this.changeProperty(null, 'filter', f)} onChange={f => this.changeProperty(null, 'filter', f)}
/> />
</div> </div>
@@ -212,7 +216,7 @@ export default class LayerEditor extends React.Component {
return <PropertyGroup return <PropertyGroup
errors={errorData} errors={errorData}
layer={this.props.layer} layer={this.props.layer}
groupFields={fields} groupFields={fields!}
spec={this.props.spec} spec={this.props.spec}
onChange={this.changeProperty.bind(this)} onChange={this.changeProperty.bind(this)}
/> />
@@ -226,10 +230,11 @@ export default class LayerEditor extends React.Component {
); );
}} }}
/> />
default: return <></>
} }
} }
moveLayer(offset) { moveLayer(offset: number) {
this.props.onMoveLayer({ this.props.onMoveLayer({
oldIndex: this.props.layerIndex, oldIndex: this.props.layerIndex,
newIndex: this.props.layerIndex+offset newIndex: this.props.layerIndex+offset
@@ -237,7 +242,7 @@ export default class LayerEditor extends React.Component {
} }
render() { render() {
const groupIds = []; const groupIds: string[] = [];
const layerType = this.props.layer.type const layerType = this.props.layer.type
const groups = layoutGroups(layerType).filter(group => { const groups = layoutGroups(layerType).filter(group => {
return !(layerType === 'background' && group.type === 'source') return !(layerType === 'background' && group.type === 'source')
@@ -258,7 +263,7 @@ export default class LayerEditor extends React.Component {
const layout = this.props.layer.layout || {} const layout = this.props.layer.layout || {}
const items = { const items: {[key: string]: {text: string, handler: () => void, disabled?: boolean}} = {
delete: { delete: {
text: "Delete", text: "Delete",
handler: () => this.props.onLayerDestroy(this.props.layerIndex) handler: () => this.props.onLayerDestroy(this.props.layerIndex)
@@ -285,8 +290,8 @@ export default class LayerEditor extends React.Component {
} }
} }
function handleSelection(id, event) { function handleSelection(id: string, event: React.SyntheticEvent) {
event.stopPropagation; event.stopPropagation();
items[id].handler(); items[id].handler();
} }
@@ -310,7 +315,7 @@ export default class LayerEditor extends React.Component {
</Button> </Button>
<Menu> <Menu>
<ul className="more-menu__menu"> <ul className="more-menu__menu">
{Object.keys(items).map((id, idx) => { {Object.keys(items).map((id) => {
const item = items[id]; const item = items[id];
return <li key={id}> return <li key={id}>
<MenuItem value={id} className='more-menu__menu__item'> <MenuItem value={id} className='more-menu__menu__item'>
+1 -1
View File
@@ -18,7 +18,7 @@ type LayerEditorGroupProps = {
title: string title: string
isActive: boolean isActive: boolean
children: React.ReactElement children: React.ReactElement
onActiveToggle(...args: unknown[]): unknown onActiveToggle(active: boolean): unknown
}; };
@@ -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 lodash from 'lodash'; import lodash from 'lodash';
@@ -8,20 +7,13 @@ import LayerListItem from './LayerListItem'
import ModalAdd from './ModalAdd' import ModalAdd from './ModalAdd'
import {SortableContainer} from 'react-sortable-hoc'; import {SortableContainer} from 'react-sortable-hoc';
import { LayerSpecification } from '@maplibre/maplibre-gl-style-spec';
const layerListPropTypes = { function layerPrefix(name: string) {
layers: PropTypes.array.isRequired,
selectedLayerIndex: PropTypes.number.isRequired,
onLayersChange: PropTypes.func.isRequired,
onLayerSelect: PropTypes.func,
sources: PropTypes.object.isRequired,
}
function layerPrefix(name) {
return name.replace(' ', '-').replace('_', '-').split('-')[0] return name.replace(' ', '-').replace('_', '-').split('-')[0]
} }
function findClosestCommonPrefix(layers, idx) { function findClosestCommonPrefix(layers: LayerSpecification[], idx: number) {
const currentLayerPrefix = layerPrefix(layers[idx].id) const currentLayerPrefix = layerPrefix(layers[idx].id)
let closestIdx = idx let closestIdx = idx
for (let i = idx; i > 0; i--) { for (let i = idx; i > 0; i--) {
@@ -37,14 +29,34 @@ function findClosestCommonPrefix(layers, idx) {
let UID = 0; let UID = 0;
type LayerListContainerProps = {
layers: LayerSpecification[]
selectedLayerIndex: number
onLayersChange(...args: unknown[]): unknown
onLayerSelect(...args: unknown[]): unknown
onLayerDestroy?(...args: unknown[]): unknown
onLayerCopy(...args: unknown[]): unknown
onLayerVisibilityToggle(...args: unknown[]): unknown
sources: object
errors: any[]
};
type LayerListContainerState = {
collapsedGroups: {[ket: string]: boolean}
areAllGroupsExpanded: boolean
keys: {[key: string]: number}
isOpen: {[key: string]: boolean}
};
// List of collapsible layer editors // List of collapsible layer editors
class LayerListContainer extends React.Component { class LayerListContainer extends React.Component<LayerListContainerProps, LayerListContainerState> {
static propTypes = {...layerListPropTypes}
static defaultProps = { static defaultProps = {
onLayerSelect: () => {}, onLayerSelect: () => {},
} }
selectedItemRef: React.RefObject<any>;
scrollContainerRef: React.RefObject<HTMLElement>;
constructor(props) { constructor(props: LayerListContainerProps) {
super(props); super(props);
this.selectedItemRef = React.createRef(); this.selectedItemRef = React.createRef();
this.scrollContainerRef = React.createRef(); this.scrollContainerRef = React.createRef();
@@ -60,7 +72,7 @@ class LayerListContainer extends React.Component {
} }
} }
toggleModal(modalName) { toggleModal(modalName: string) {
this.setState({ this.setState({
keys: { keys: {
...this.state.keys, ...this.state.keys,
@@ -76,7 +88,7 @@ class LayerListContainer extends React.Component {
toggleLayers = () => { toggleLayers = () => {
let idx = 0 let idx = 0
let newGroups=[] let newGroups: {[key:string]: boolean} = {}
this.groupedLayers().forEach(layers => { this.groupedLayers().forEach(layers => {
const groupPrefix = layerPrefix(layers[0].id) const groupPrefix = layerPrefix(layers[0].id)
@@ -87,7 +99,7 @@ class LayerListContainer extends React.Component {
newGroups[lookupKey] = this.state.areAllGroupsExpanded newGroups[lookupKey] = this.state.areAllGroupsExpanded
} }
layers.forEach((layer) => { layers.forEach((_layer) => {
idx += 1 idx += 1
}) })
}); });
@@ -98,7 +110,7 @@ class LayerListContainer extends React.Component {
}) })
} }
groupedLayers() { groupedLayers(): (LayerSpecification & {key: string})[][] {
const groups = [] const groups = []
const layerIdCount = new Map(); const layerIdCount = new Map();
@@ -122,7 +134,7 @@ class LayerListContainer extends React.Component {
return groups return groups
} }
toggleLayerGroup(groupPrefix, idx) { toggleLayerGroup(groupPrefix: string, idx: number) {
const lookupKey = [groupPrefix, idx].join('-') const lookupKey = [groupPrefix, idx].join('-')
const newGroups = { ...this.state.collapsedGroups } const newGroups = { ...this.state.collapsedGroups }
if(lookupKey in this.state.collapsedGroups) { if(lookupKey in this.state.collapsedGroups) {
@@ -135,12 +147,12 @@ class LayerListContainer extends React.Component {
}) })
} }
isCollapsed(groupPrefix, idx) { isCollapsed(groupPrefix: string, idx: number) {
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')] const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')]
return collapsed === undefined ? true : collapsed return collapsed === undefined ? true : collapsed
} }
shouldComponentUpdate (nextProps, nextState) { shouldComponentUpdate (nextProps: LayerListContainerProps, nextState: LayerListContainerState) {
// Always update on state change // Always update on state change
if (this.state !== nextState) { if (this.state !== nextState) {
return true; return true;
@@ -148,8 +160,8 @@ class LayerListContainer extends React.Component {
// This component tree only requires id and visibility from the layers // This component tree only requires id and visibility from the layers
// objects // objects
function getRequiredProps (layer) { function getRequiredProps(layer: LayerSpecification) {
const out = { const out: {id: string, layout?: { visibility: any}} = {
id: layer.id, id: layer.id,
}; };
@@ -165,10 +177,10 @@ class LayerListContainer extends React.Component {
this.props.layers.map(getRequiredProps), this.props.layers.map(getRequiredProps),
); );
function withoutLayers (props) { function withoutLayers(props: LayerListContainerProps) {
const out = { const out = {
...props ...props
}; } as LayerListContainerProps & { layers?: any };
delete out['layers']; delete out['layers'];
return out; return out;
} }
@@ -184,7 +196,7 @@ class LayerListContainer extends React.Component {
return propsChanged; return propsChanged;
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps: LayerListContainerProps) {
if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) { if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) {
const selectedItemNode = this.selectedItemRef.current; const selectedItemNode = this.selectedItemRef.current;
if (selectedItemNode && selectedItemNode.node) { if (selectedItemNode && selectedItemNode.node) {
@@ -207,7 +219,7 @@ class LayerListContainer extends React.Component {
render() { render() {
const listItems = [] const listItems: JSX.Element[] = []
let idx = 0 let idx = 0
const layersByGroup = this.groupedLayers(); const layersByGroup = this.groupedLayers();
layersByGroup.forEach(layers => { layersByGroup.forEach(layers => {
@@ -235,7 +247,7 @@ class LayerListContainer extends React.Component {
); );
}); });
const additionalProps = {}; const additionalProps: {ref?: React.RefObject<any>} = {};
if (idx === this.props.selectedLayerIndex) { if (idx === this.props.selectedLayerIndex) {
additionalProps.ref = this.selectedItemRef; additionalProps.ref = this.selectedItemRef;
} }
@@ -255,7 +267,7 @@ class LayerListContainer extends React.Component {
visibility={(layer.layout || {}).visibility} visibility={(layer.layout || {}).visibility}
isSelected={idx === this.props.selectedLayerIndex} isSelected={idx === this.props.selectedLayerIndex}
onLayerSelect={this.props.onLayerSelect} onLayerSelect={this.props.onLayerSelect}
onLayerDestroy={this.props.onLayerDestroy.bind(this)} onLayerDestroy={this.props.onLayerDestroy?.bind(this)}
onLayerCopy={this.props.onLayerCopy.bind(this)} onLayerCopy={this.props.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)} onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
{...additionalProps} {...additionalProps}
@@ -316,11 +328,13 @@ class LayerListContainer extends React.Component {
} }
} }
const LayerListContainerSortable = SortableContainer((props) => <LayerListContainer {...props} />) const LayerListContainerSortable = SortableContainer((props: LayerListContainerProps) => <LayerListContainer {...props} />)
export default class LayerList extends React.Component { type LayerListProps = LayerListContainerProps & {
static propTypes = {...layerListPropTypes} onMoveLayer(...args: unknown[]): unknown
};
export default class LayerList extends React.Component<LayerListProps> {
render() { render() {
return <LayerListContainerSortable return <LayerListContainerSortable
{...this.props} {...this.props}
@@ -8,7 +8,12 @@ import IconLayer from './IconLayer'
import {SortableElement, SortableHandle} from 'react-sortable-hoc' import {SortableElement, SortableHandle} from 'react-sortable-hoc'
const DraggableLabel = SortableHandle((props) => { type DraggableLabelProps = {
layerId: string
layerType: string
};
const DraggableLabel = SortableHandle((props: DraggableLabelProps) => {
return <div className="maputnik-layer-list-item-handle"> return <div className="maputnik-layer-list-item-handle">
<IconLayer <IconLayer
className="layer-handle__icon" className="layer-handle__icon"
@@ -20,15 +25,15 @@ const DraggableLabel = SortableHandle((props) => {
</div> </div>
}); });
class IconAction extends React.Component { type IconActionProps = {
static propTypes = { action: string
action: PropTypes.string.isRequired, onClick(...args: unknown[]): unknown
onClick: PropTypes.func.isRequired, wdKey?: string
wdKey: PropTypes.string, classBlockName?: string
classBlockName: PropTypes.string, classBlockModifier?: string
classBlockModifier: PropTypes.string, };
}
class IconAction extends React.Component<IconActionProps> {
renderIcon() { renderIcon() {
switch(this.props.action) { switch(this.props.action) {
case 'duplicate': return <MdContentCopy /> case 'duplicate': return <MdContentCopy />
@@ -51,7 +56,7 @@ class IconAction extends React.Component {
} }
return <button return <button
tabIndex="-1" tabIndex={-1}
title={this.props.action} title={this.props.action}
className={`maputnik-layer-list-icon-action ${classAdditions}`} className={`maputnik-layer-list-icon-action ${classAdditions}`}
data-wd-key={this.props.wdKey} data-wd-key={this.props.wdKey}
@@ -63,21 +68,21 @@ class IconAction extends React.Component {
} }
} }
class LayerListItem extends React.Component { type LayerListItemProps = {
static propTypes = { id?: string
layerIndex: PropTypes.number.isRequired, layerIndex: number
layerId: PropTypes.string.isRequired, layerId: string
layerType: PropTypes.string.isRequired, layerType: string
isSelected: PropTypes.bool, isSelected?: boolean
visibility: PropTypes.string, visibility?: string
className: PropTypes.string, className?: string
onLayerSelect(...args: unknown[]): unknown
onLayerSelect: PropTypes.func.isRequired, onLayerCopy?(...args: unknown[]): unknown
onLayerCopy: PropTypes.func, onLayerDestroy?(...args: unknown[]): unknown
onLayerDestroy: PropTypes.func, onLayerVisibilityToggle?(...args: unknown[]): unknown
onLayerVisibilityToggle: PropTypes.func, };
}
class LayerListItem extends React.Component<LayerListItemProps> {
static defaultProps = { static defaultProps = {
isSelected: false, isSelected: false,
visibility: 'visible', visibility: 'visible',
@@ -102,12 +107,12 @@ class LayerListItem extends React.Component {
return <li return <li
id={this.props.id} id={this.props.id}
key={this.props.layerId} key={this.props.layerId}
onClick={e => this.props.onLayerSelect(this.props.layerIndex)} onClick={_e => this.props.onLayerSelect(this.props.layerIndex)}
data-wd-key={"layer-list-item:"+this.props.layerId} data-wd-key={"layer-list-item:"+this.props.layerId}
className={classnames({ className={classnames({
"maputnik-layer-list-item": true, "maputnik-layer-list-item": true,
"maputnik-layer-list-item-selected": this.props.isSelected, "maputnik-layer-list-item-selected": this.props.isSelected,
[this.props.className]: true, [this.props.className!]: true,
})}> })}>
<DraggableLabel {...this.props} /> <DraggableLabel {...this.props} />
<span style={{flexGrow: 1}} /> <span style={{flexGrow: 1}} />
@@ -115,25 +120,25 @@ class LayerListItem extends React.Component {
wdKey={"layer-list-item:"+this.props.layerId+":delete"} wdKey={"layer-list-item:"+this.props.layerId+":delete"}
action={'delete'} action={'delete'}
classBlockName="delete" classBlockName="delete"
onClick={e => this.props.onLayerDestroy(this.props.layerIndex)} onClick={_e => this.props.onLayerDestroy!(this.props.layerIndex)}
/> />
<IconAction <IconAction
wdKey={"layer-list-item:"+this.props.layerId+":copy"} wdKey={"layer-list-item:"+this.props.layerId+":copy"}
action={'duplicate'} action={'duplicate'}
classBlockName="duplicate" classBlockName="duplicate"
onClick={e => this.props.onLayerCopy(this.props.layerIndex)} onClick={_e => this.props.onLayerCopy!(this.props.layerIndex)}
/> />
<IconAction <IconAction
wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"} wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
action={visibilityAction} action={visibilityAction}
classBlockName="visibility" classBlockName="visibility"
classBlockModifier={visibilityAction} classBlockModifier={visibilityAction}
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerIndex)} onClick={_e => this.props.onLayerVisibilityToggle!(this.props.layerIndex)}
/> />
</li> </li>
} }
} }
const LayerListItemSortable = SortableElement((props) => <LayerListItem {...props} />); const LayerListItemSortable = SortableElement((props: LayerListItemProps) => <LayerListItem {...props} />);
export default LayerListItemSortable; export default LayerListItemSortable;
@@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import MapLibreGl from 'maplibre-gl' import MapLibreGl, {LayerSpecification, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
// @ts-ignore
import MapboxInspect from 'mapbox-gl-inspect' import MapboxInspect from 'mapbox-gl-inspect'
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup' // @ts-ignore
import MapMaplibreGlFeaturePropertyPopup from './MapMaplibreGlFeaturePropertyPopup'
import tokens from '../config/tokens.json'
import colors from 'mapbox-gl-inspect/lib/colors' import colors from 'mapbox-gl-inspect/lib/colors'
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'
import MapMaplibreGlFeaturePropertyPopup, { InspectFeature } from './MapMaplibreGlFeaturePropertyPopup'
import Color from 'color' import Color from 'color'
import ZoomControl from '../libs/zoomcontrol' import ZoomControl from '../libs/zoomcontrol'
import { colorHighlightedLayer } from '../libs/highlight' import { HighlightedLayer, colorHighlightedLayer } from '../libs/highlight'
import 'maplibre-gl/dist/maplibre-gl.css' import 'maplibre-gl/dist/maplibre-gl.css'
import '../maplibregl.css' import '../maplibregl.css'
import '../libs/maplibre-rtl' import '../libs/maplibre-rtl'
@@ -17,26 +17,26 @@ import '../libs/maplibre-rtl'
const IS_SUPPORTED = MapLibreGl.supported(); const IS_SUPPORTED = MapLibreGl.supported();
function renderPopup(popup, mountNode) { function renderPopup(popup: JSX.Element, mountNode: ReactDOM.Container) {
ReactDOM.render(popup, mountNode); ReactDOM.render(popup, mountNode);
return mountNode; return mountNode;
} }
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) { function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[], highlightedLayer?: HighlightedLayer) {
const backgroundLayer = { const backgroundLayer = {
"id": "background", "id": "background",
"type": "background", "type": "background",
"paint": { "paint": {
"background-color": '#1c1f24', "background-color": '#1c1f24',
} }
} } as LayerSpecification
const layer = colorHighlightedLayer(highlightedLayer) const layer = colorHighlightedLayer(highlightedLayer)
if(layer) { if(layer) {
coloredLayers.push(layer) coloredLayers.push(layer)
} }
const sources = {} const sources: {[key:string]: SourceSpecification} = {}
Object.keys(originalMapStyle.sources).forEach(sourceId => { Object.keys(originalMapStyle.sources).forEach(sourceId => {
const source = originalMapStyle.sources[sourceId] const source = originalMapStyle.sources[sourceId]
if(source.type !== 'raster' && source.type !== 'raster-dem') { if(source.type !== 'raster' && source.type !== 'raster-dem') {
@@ -47,32 +47,43 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
const inspectStyle = { const inspectStyle = {
...originalMapStyle, ...originalMapStyle,
sources: sources, sources: sources,
layers: [backgroundLayer].concat(coloredLayers) layers: [backgroundLayer].concat(coloredLayers as LayerSpecification[])
} }
return inspectStyle return inspectStyle
} }
export default class MapMaplibreGl extends React.Component { type MapMaplibreGlProps = {
static propTypes = { onDataChange?(...args: unknown[]): unknown
onDataChange: PropTypes.func, onLayerSelect(...args: unknown[]): unknown
onLayerSelect: PropTypes.func.isRequired, mapStyle: StyleSpecification
mapStyle: PropTypes.object.isRequired, inspectModeEnabled: boolean
inspectModeEnabled: PropTypes.bool.isRequired, highlightedLayer?: HighlightedLayer
highlightedLayer: PropTypes.object, options?: MapOptions & {
options: PropTypes.object, showTileBoundaries?: boolean
replaceAccessTokens: PropTypes.func.isRequired, showCollisionBoxes?: boolean
onChange: PropTypes.func.isRequired, showOverdrawInspector?: boolean
} }
replaceAccessTokens(mapStyle: StyleSpecification): StyleSpecification
onChange(...args: unknown[]): unknown
};
type MapMaplibreGlState = {
map: Map | null
inspect: MapboxInspect | null
zoom?: number
};
export default class MapMaplibreGl extends React.Component<MapMaplibreGlProps, MapMaplibreGlState> {
static defaultProps = { static defaultProps = {
onMapLoaded: () => {}, onMapLoaded: () => {},
onDataChange: () => {}, onDataChange: () => {},
onLayerSelect: () => {}, onLayerSelect: () => {},
onChange: () => {}, onChange: () => {},
options: {}, options: {} as MapOptions,
} }
container: HTMLDivElement | null = null
constructor(props) { constructor(props: MapMaplibreGlProps) {
super(props) super(props)
this.state = { this.state = {
map: null, map: null,
@@ -80,7 +91,7 @@ export default class MapMaplibreGl extends React.Component {
} }
} }
updateMapFromProps(props) { updateMapFromProps(props: MapMaplibreGlProps) {
if(!IS_SUPPORTED) return; if(!IS_SUPPORTED) return;
if(!this.state.map) return if(!this.state.map) return
@@ -93,7 +104,7 @@ export default class MapMaplibreGl extends React.Component {
) )
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: MapMaplibreGlProps, nextState: MapMaplibreGlState) {
let should = false; let should = false;
try { try {
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState); should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
@@ -103,7 +114,7 @@ export default class MapMaplibreGl extends React.Component {
return should; return should;
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate() {
if(!IS_SUPPORTED) return; if(!IS_SUPPORTED) return;
const map = this.state.map; const map = this.state.map;
@@ -128,9 +139,9 @@ export default class MapMaplibreGl extends React.Component {
} }
} }
map.showTileBoundaries = this.props.options.showTileBoundaries; map.showTileBoundaries = this.props.options?.showTileBoundaries!;
map.showCollisionBoxes = this.props.options.showCollisionBoxes; map.showCollisionBoxes = this.props.options?.showCollisionBoxes!;
map.showOverdrawInspector = this.props.options.showOverdrawInspector; map.showOverdrawInspector = this.props.options?.showOverdrawInspector!;
} }
} }
@@ -139,7 +150,7 @@ export default class MapMaplibreGl extends React.Component {
const mapOpts = { const mapOpts = {
...this.props.options, ...this.props.options,
container: this.container, container: this.container!,
style: this.props.mapStyle, style: this.props.mapStyle,
hash: true, hash: true,
maxZoom: 24 maxZoom: 24
@@ -154,9 +165,9 @@ export default class MapMaplibreGl extends React.Component {
} }
mapViewChange(); mapViewChange();
map.showTileBoundaries = mapOpts.showTileBoundaries; map.showTileBoundaries = mapOpts.showTileBoundaries!;
map.showCollisionBoxes = mapOpts.showCollisionBoxes; map.showCollisionBoxes = mapOpts.showCollisionBoxes!;
map.showOverdrawInspector = mapOpts.showOverdrawInspector; map.showOverdrawInspector = mapOpts.showOverdrawInspector!;
const zoomControl = new ZoomControl; const zoomControl = new ZoomControl;
map.addControl(zoomControl, 'top-right'); map.addControl(zoomControl, 'top-right');
@@ -175,11 +186,11 @@ export default class MapMaplibreGl extends React.Component {
showInspectMapPopupOnHover: true, showInspectMapPopupOnHover: true,
showInspectButton: false, showInspectButton: false,
blockHoverPopupOnClick: true, blockHoverPopupOnClick: true,
assignLayerColor: (layerId, alpha) => { assignLayerColor: (layerId: string, alpha: number) => {
return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string() return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string()
}, },
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer), buildInspectStyle: (originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[]) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
renderPopup: features => { renderPopup: (features: InspectFeature[]) => {
if(this.props.inspectModeEnabled) { if(this.props.inspectModeEnabled) {
return renderPopup(<MapMaplibreGlFeaturePropertyPopup features={features} />, tmpNode); return renderPopup(<MapMaplibreGlFeaturePropertyPopup features={features} />, tmpNode);
} else { } else {
@@ -199,7 +210,7 @@ export default class MapMaplibreGl extends React.Component {
map.on("data", e => { map.on("data", e => {
if(e.dataType !== 'tile') return if(e.dataType !== 'tile') return
this.props.onDataChange({ this.props.onDataChange!({
map: this.state.map map: this.state.map
}) })
}) })
@@ -208,7 +219,7 @@ export default class MapMaplibreGl extends React.Component {
console.log("ERROR", e); console.log("ERROR", e);
}) })
map.on("zoom", e => { map.on("zoom", _e => {
this.setState({ this.setState({
zoom: map.getZoom() zoom: map.getZoom()
}); });
@@ -218,7 +229,7 @@ export default class MapMaplibreGl extends React.Component {
map.on("zoomend", mapViewChange); map.on("zoomend", mapViewChange);
} }
onLayerSelectById = (id) => { onLayerSelectById = (id: string) => {
const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id); const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id);
this.props.onLayerSelect(index); this.props.onLayerSelect(index);
} }
@@ -1,9 +1,18 @@
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'
function displayValue(value) { export type InspectFeature = {
id: string
properties: {[key:string]: any}
layer: {[key:string]: any}
geometry: GeoJSON.Geometry
sourceLayer: string
inspectModeCounter?: number
counter?: number
}
function displayValue(value: string | number | Date | object) {
if (typeof value === 'undefined' || value === null) return value; if (typeof value === 'undefined' || value === null) return value;
if (value instanceof Date) return value.toLocaleString(); if (value instanceof Date) return value.toLocaleString();
if (typeof value === 'object' || if (typeof value === 'object' ||
@@ -12,7 +21,7 @@ function displayValue(value) {
return value; return value;
} }
function renderProperties(feature) { function renderProperties(feature: InspectFeature) {
return Object.keys(feature.properties).map(propertyName => { return Object.keys(feature.properties).map(propertyName => {
const property = feature.properties[propertyName] const property = feature.properties[propertyName]
return <Block key={propertyName} label={propertyName}> return <Block key={propertyName} label={propertyName}>
@@ -21,13 +30,13 @@ function renderProperties(feature) {
}) })
} }
function renderFeatureId(feature) { function renderFeatureId(feature: InspectFeature) {
return <Block key={"feature-id"} label={"feature_id"}> return <Block key={"feature-id"} label={"feature_id"}>
<FieldString value={displayValue(feature.id)} style={{backgroundColor: 'transparent'}} /> <FieldString value={displayValue(feature.id)} style={{backgroundColor: 'transparent'}} />
</Block> </Block>
} }
function renderFeature(feature, idx) { function renderFeature(feature: InspectFeature, idx: number) {
return <div key={`${feature.sourceLayer}-${idx}`}> return <div key={`${feature.sourceLayer}-${idx}`}>
<div className="maputnik-popup-layer-id">{feature.layer['source']}: {feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div> <div className="maputnik-popup-layer-id">{feature.layer['source']}: {feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
<Block key={"property-type"} label={"$type"}> <Block key={"property-type"} label={"$type"}>
@@ -38,8 +47,8 @@ function renderFeature(feature, idx) {
</div> </div>
} }
function removeDuplicatedFeatures(features) { function removeDuplicatedFeatures(features: InspectFeature[]) {
let uniqueFeatures = []; let uniqueFeatures: InspectFeature[] = [];
features.forEach(feature => { features.forEach(feature => {
const featureIndex = uniqueFeatures.findIndex(feature2 => { const featureIndex = uniqueFeatures.findIndex(feature2 => {
@@ -50,8 +59,8 @@ function removeDuplicatedFeatures(features) {
if(featureIndex === -1) { if(featureIndex === -1) {
uniqueFeatures.push(feature) uniqueFeatures.push(feature)
} else { } else {
if(uniqueFeatures[featureIndex].hasOwnProperty('inspectModeCounter')) { if('inspectModeCounter' in uniqueFeatures[featureIndex]) {
uniqueFeatures[featureIndex].inspectModeCounter++ uniqueFeatures[featureIndex].inspectModeCounter!++
} else { } else {
uniqueFeatures[featureIndex].inspectModeCounter = 2 uniqueFeatures[featureIndex].inspectModeCounter = 2
} }
@@ -61,11 +70,11 @@ function removeDuplicatedFeatures(features) {
return uniqueFeatures return uniqueFeatures
} }
class FeaturePropertyPopup extends React.Component { type FeaturePropertyPopupProps = {
static propTypes = { features: InspectFeature[]
features: PropTypes.array };
}
class FeaturePropertyPopup extends React.Component<FeaturePropertyPopupProps> {
render() { render() {
const features = removeDuplicatedFeatures(this.props.features) const features = removeDuplicatedFeatures(this.props.features)
return <div className="maputnik-feature-property-popup"> return <div className="maputnik-feature-property-popup">
+10 -9
View File
@@ -1,18 +1,19 @@
import React from 'react' import React from 'react'
import IconLayer from './IconLayer' import IconLayer from './IconLayer'
import type {InspectFeature} from './MapMaplibreGlFeaturePropertyPopup';
function groupFeaturesBySourceLayer(features: any[]) { function groupFeaturesBySourceLayer(features: InspectFeature[]) {
const sources = {} as any const sources: {[key: string]: InspectFeature[]} = {}
let returnedFeatures = {} as any; let returnedFeatures: {[key: string]: number} = {}
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: any) => f.layer.id === feature.layer.id) const featureObject = sources[feature.layer['source-layer']].find((f: InspectFeature) => f.layer.id === feature.layer.id)
featureObject.counter = returnedFeatures[feature.layer.id] featureObject!.counter = returnedFeatures[feature.layer.id]
} else { } else {
sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || [] sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
sources[feature.layer['source-layer']].push(feature) sources[feature.layer['source-layer']].push(feature)
@@ -25,13 +26,13 @@ function groupFeaturesBySourceLayer(features: any[]) {
} }
type FeatureLayerPopupProps = { type FeatureLayerPopupProps = {
onLayerSelect(...args: unknown[]): unknown onLayerSelect(layerId: string): unknown
features: any[] features: InspectFeature[]
zoom?: number zoom?: number
}; };
class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> { class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
_getFeatureColor(feature: any, _zoom?: number) { _getFeatureColor(feature: InspectFeature, _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;
@@ -75,7 +76,7 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
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: any, idx: number) => { const layers = sources[vectorLayerId].map((feature: InspectFeature, idx: number) => {
const featureColor = this._getFeatureColor(feature, this.props.zoom); const featureColor = this._getFeatureColor(feature, this.props.zoom);
return <div return <div
+1 -1
View File
@@ -39,7 +39,7 @@ type PropertyGroupProps = {
groupFields: string[] groupFields: string[]
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
spec: any spec: any
errors?: unknown[] errors?: {[key: string]: {message: string}}
}; };
export default class PropertyGroup extends React.Component<PropertyGroupProps> { export default class PropertyGroup extends React.Component<PropertyGroupProps> {
+1 -1
View File
@@ -36,7 +36,7 @@ function parseFilter(v: string | boolean | number) {
type SingleFilterEditorProps = { type SingleFilterEditorProps = {
filter: any[] filter: any[]
onChange(...args: unknown[]): unknown onChange(filter: any[]): unknown
properties?: {[key: string]: string} properties?: {[key: string]: string}
}; };
+1 -1
View File
@@ -288,7 +288,7 @@ export default class DataProperty extends React.Component<DataPropertyProps, Dat
<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: string) => 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)}
/> />
+3 -2
View File
@@ -14,7 +14,7 @@ type ExpressionPropertyProps = {
fieldType?: string fieldType?: string
fieldSpec?: object fieldSpec?: object
value?: any value?: any
errors?: {[key: string]: any} errors?: {[key: string]: {message: string}}
onChange?(...args: unknown[]): unknown onChange?(...args: unknown[]): unknown
onUndo?(...args: unknown[]): unknown onUndo?(...args: unknown[]): unknown
canUndo?(...args: unknown[]): unknown canUndo?(...args: unknown[]): unknown
@@ -109,7 +109,8 @@ export default class ExpressionProperty extends React.Component<ExpressionProper
} }
return <Block return <Block
error={foundErrors} // this feels like an incorrect type...? `foundErrors` is an array of objects, not a single object
error={foundErrors as any}
fieldSpec={this.props.fieldSpec} fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)} label={labelFromFieldName(this.props.fieldName)}
action={deleteStopBtn} action={deleteStopBtn}
+1 -1
View File
@@ -8,7 +8,7 @@ type BlockIdProps = {
value: string value: string
wdKey: string wdKey: string
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
error?: unknown[] error?: {message: string}
}; };
export default class BlockId extends React.Component<BlockIdProps> { export default class BlockId extends React.Component<BlockIdProps> {
+1 -1
View File
@@ -7,7 +7,7 @@ import FieldNumber from './FieldNumber'
type BlockMaxZoomProps = { type BlockMaxZoomProps = {
value?: number value?: number
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
error?: unknown[] error?: {message: string}
}; };
export default class BlockMaxZoom extends React.Component<BlockMaxZoomProps> { export default class BlockMaxZoom extends React.Component<BlockMaxZoomProps> {
+1 -1
View File
@@ -7,7 +7,7 @@ import FieldNumber from './FieldNumber'
type BlockMinZoomProps = { type BlockMinZoomProps = {
value?: number value?: number
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
error?: unknown[] error?: {message: string}
}; };
export default class BlockMinZoom extends React.Component<BlockMinZoomProps> { export default class BlockMinZoom extends React.Component<BlockMinZoomProps> {
+1 -1
View File
@@ -9,7 +9,7 @@ type BlockSourceProps = {
wdKey?: string wdKey?: string
onChange?(...args: unknown[]): unknown onChange?(...args: unknown[]): unknown
sourceIds?: unknown[] sourceIds?: unknown[]
error?: unknown[] error?: {message: string}
}; };
export default class BlockSource extends React.Component<BlockSourceProps> { export default class BlockSource extends React.Component<BlockSourceProps> {
+1 -1
View File
@@ -9,7 +9,7 @@ type BlockTypeProps = {
value: string value: string
wdKey?: string wdKey?: string
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
error?: unknown[] error?: {message: string}
disabled?: boolean disabled?: boolean
}; };
+1 -1
View File
@@ -13,7 +13,7 @@ type SpecPropertyProps = SpecFieldProps & {
fieldType?: string fieldType?: string
fieldSpec?: any fieldSpec?: any
value?: any value?: any
errors?: unknown[] errors?: {[key: string]: {message: string}}
onExpressionClick?(...args: unknown[]): unknown onExpressionClick?(...args: unknown[]): unknown
}; };
+1 -1
View File
@@ -195,7 +195,7 @@ export default class ZoomProperty extends React.Component<ZoomPropertyProps, Zoo
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<InputSelect <InputSelect
value={"interpolate"} value={"interpolate"}
onChange={propVal => this.changeDataType(propVal)} onChange={(propVal: string) => 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!)}
/> />
+8 -6
View File
@@ -4,10 +4,9 @@ import stylegen from 'mapbox-gl-inspect/lib/stylegen'
import colors from 'mapbox-gl-inspect/lib/colors' import colors from 'mapbox-gl-inspect/lib/colors'
import {FilterSpecification,LayerSpecification } from '@maplibre/maplibre-gl-style-spec' import {FilterSpecification,LayerSpecification } from '@maplibre/maplibre-gl-style-spec'
export function colorHighlightedLayer(layer: LayerSpecification) { export type HighlightedLayer = LayerSpecification & {filter?: FilterSpecification};
if(!layer || layer.type === 'background' || layer.type === 'raster') return null
function changeLayer(l: LayerSpecification & {filter?: FilterSpecification}) { function changeLayer(l: HighlightedLayer, layer: LayerSpecification) {
if(l.type === 'circle') { if(l.type === 'circle') {
l.paint!['circle-radius'] = 3 l.paint!['circle-radius'] = 3
} else if(l.type === 'line') { } else if(l.type === 'line') {
@@ -23,19 +22,22 @@ export function colorHighlightedLayer(layer: LayerSpecification) {
return l return l
} }
export function colorHighlightedLayer(layer?: LayerSpecification): HighlightedLayer | null {
if(!layer || layer.type === 'background' || layer.type === 'raster') return null
const sourceLayerId = layer['source-layer'] || '' const sourceLayerId = layer['source-layer'] || ''
const color = colors.brightColor(sourceLayerId, 1); const color = colors.brightColor(sourceLayerId, 1);
if(layer.type === "fill" || layer.type === 'fill-extrusion') { if(layer.type === "fill" || layer.type === 'fill-extrusion') {
return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer['source-layer'])) return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer['source-layer']), layer)
} }
if(layer.type === "symbol" || layer.type === 'circle') { if(layer.type === "symbol" || layer.type === 'circle') {
return changeLayer(stylegen.circleLayer(color, layer.source, layer['source-layer'])) return changeLayer(stylegen.circleLayer(color, layer.source, layer['source-layer']), layer)
} }
if(layer.type === 'line') { if(layer.type === 'line') {
return changeLayer(stylegen.lineLayer(color, layer.source, layer['source-layer'])) return changeLayer(stylegen.lineLayer(color, layer.source, layer['source-layer']), layer)
} }
return null return null
+1 -1
View File
@@ -27,7 +27,7 @@ export function changeType(layer: LayerSpecification, newType: string) {
/** A {@property} in either the paint our layout {@group} has changed /** A {@property} in either the paint our layout {@group} has changed
* to a {@newValue}. * to a {@newValue}.
*/ */
export function changeProperty(layer: LayerSpecification, group: keyof LayerSpecification, property: string, newValue: any) { export function changeProperty(layer: LayerSpecification, group: keyof LayerSpecification | null, property: string, newValue: any) {
// Remove the property if undefined // Remove the property if undefined
if(newValue === undefined) { if(newValue === undefined) {
if(group) { if(group) {