mirror of
https://github.com/maputnik/editor.git
synced 2025-12-06 06:10:00 +00:00
Migration of jsx files to tsx 3 (#851)
This is in continue to: - #850 - #848 The last files should be converted as part of this PR, there are only a handful left.
This commit is contained in:
54
package-lock.json
generated
54
package-lock.json
generated
@@ -71,10 +71,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/file-saver": "^2.0.7",
|
||||||
|
"@types/json-to-ast": "^2.1.4",
|
||||||
"@types/lodash.capitalize": "^4.2.9",
|
"@types/lodash.capitalize": "^4.2.9",
|
||||||
|
"@types/lodash.clamp": "^4.0.9",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.9",
|
||||||
|
"@types/lodash.get": "^4.4.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-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",
|
||||||
@@ -82,6 +87,7 @@
|
|||||||
"@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",
|
||||||
"@types/react-icon-base": "^2.1.6",
|
"@types/react-icon-base": "^2.1.6",
|
||||||
|
"@types/string-hash": "^1.1.3",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
@@ -4746,6 +4752,12 @@
|
|||||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/json-to-ast": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/json-to-ast/-/json-to-ast-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-131wOmuwDg8ypYCSQ437bGdP+K2lJ8GJUu+ng4iQQxAc3irRnb7mGHbexsPChBcKWLctTR9V5LJdX5A8WWk44A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.14.202",
|
"version": "4.14.202",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
|
||||||
@@ -4761,6 +4773,33 @@
|
|||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash.clamp": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.clamp/-/lodash.clamp-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-t+hBIPHXyBVYkl0KEAEchOJwBrG8czt3E7r5fdpwMRrn3g+hkRzw6cjzWl+nJg3Z2QqRaQLt+W2n4ikwGr1u2g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash.clonedeep": {
|
||||||
|
"version": "4.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz",
|
||||||
|
"integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/lodash.get": {
|
||||||
|
"version": "4.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.9.tgz",
|
||||||
|
"integrity": "sha512-J5dvW98sxmGnamqf+/aLP87PYXyrha9xIgc2ZlHl6OHMFR2Ejdxep50QfU0abO1+CH6+ugx+8wEUN1toImAinA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/lodash.isequal": {
|
"node_modules/@types/lodash.isequal": {
|
||||||
"version": "4.5.8",
|
"version": "4.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz",
|
||||||
@@ -4889,6 +4928,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",
|
||||||
@@ -5013,6 +5061,12 @@
|
|||||||
"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==",
|
"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/string-hash": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/string-hash/-/string-hash-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-p6skq756fJWiA59g2Uss+cMl6tpoDGuCBuxG0SI1t0NwJmYOU66LAMS6QiCgu7cUh3/hYCaMl5phcCW1JP5wOA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/tern": {
|
"node_modules/@types/tern": {
|
||||||
"version": "0.23.9",
|
"version": "0.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||||
|
|||||||
@@ -100,10 +100,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/file-saver": "^2.0.7",
|
||||||
|
"@types/json-to-ast": "^2.1.4",
|
||||||
"@types/lodash.capitalize": "^4.2.9",
|
"@types/lodash.capitalize": "^4.2.9",
|
||||||
|
"@types/lodash.clamp": "^4.0.9",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.9",
|
||||||
|
"@types/lodash.get": "^4.4.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-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",
|
||||||
@@ -111,6 +116,7 @@
|
|||||||
"@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",
|
||||||
"@types/react-icon-base": "^2.1.6",
|
"@types/react-icon-base": "^2.1.6",
|
||||||
|
"@types/string-hash": "^1.1.3",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-ignore - this can be easily replaced with arrow functions
|
||||||
import autoBind from 'react-autobind';
|
import autoBind from 'react-autobind';
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
@@ -6,14 +7,15 @@ import buffer from 'buffer'
|
|||||||
import get from 'lodash.get'
|
import get from 'lodash.get'
|
||||||
import {unset} from 'lodash'
|
import {unset} from 'lodash'
|
||||||
import {arrayMoveMutable} from 'array-move'
|
import {arrayMoveMutable} from 'array-move'
|
||||||
import url from 'url'
|
|
||||||
import hash from "string-hash";
|
import hash from "string-hash";
|
||||||
|
import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl'
|
||||||
|
import {latest, validate} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
|
||||||
import MapMaplibreGl from './MapMaplibreGl'
|
import MapMaplibreGl from './MapMaplibreGl'
|
||||||
import MapOpenLayers from './MapOpenLayers'
|
import MapOpenLayers from './MapOpenLayers'
|
||||||
import LayerList from './LayerList'
|
import LayerList from './LayerList'
|
||||||
import LayerEditor from './LayerEditor'
|
import LayerEditor from './LayerEditor'
|
||||||
import AppToolbar from './AppToolbar'
|
import AppToolbar, { MapState } from './AppToolbar'
|
||||||
import AppLayout from './AppLayout'
|
import AppLayout from './AppLayout'
|
||||||
import MessagePanel from './AppMessagePanel'
|
import MessagePanel from './AppMessagePanel'
|
||||||
|
|
||||||
@@ -25,8 +27,7 @@ import ModalShortcuts from './ModalShortcuts'
|
|||||||
import ModalSurvey from './ModalSurvey'
|
import ModalSurvey from './ModalSurvey'
|
||||||
import ModalDebug from './ModalDebug'
|
import ModalDebug from './ModalDebug'
|
||||||
|
|
||||||
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
import {downloadGlyphsMetadata, downloadSpriteMetadata} from '../libs/metadata'
|
||||||
import {latest, validate} from '@maplibre/maplibre-gl-style-spec'
|
|
||||||
import style from '../libs/style'
|
import style from '../libs/style'
|
||||||
import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
|
import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
|
||||||
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
||||||
@@ -37,12 +38,13 @@ import LayerWatcher from '../libs/layerwatcher'
|
|||||||
import tokens from '../config/tokens.json'
|
import tokens from '../config/tokens.json'
|
||||||
import isEqual from 'lodash.isequal'
|
import isEqual from 'lodash.isequal'
|
||||||
import Debug from '../libs/debug'
|
import Debug from '../libs/debug'
|
||||||
import {formatLayerId} from '../util/format';
|
import { SortEnd } from 'react-sortable-hoc';
|
||||||
|
import { MapOptions } from 'maplibre-gl';
|
||||||
|
|
||||||
// Buffer must be defined globally for @maplibre/maplibre-gl-style-spec validate() function to succeed.
|
// Buffer must be defined globally for @maplibre/maplibre-gl-style-spec validate() function to succeed.
|
||||||
window.Buffer = buffer.Buffer;
|
window.Buffer = buffer.Buffer;
|
||||||
|
|
||||||
function setFetchAccessToken(url, mapStyle) {
|
function setFetchAccessToken(url: string, mapStyle: StyleSpecification) {
|
||||||
const matchesTilehosting = url.match(/\.tilehosting\.com/);
|
const matchesTilehosting = url.match(/\.tilehosting\.com/);
|
||||||
const matchesMaptiler = url.match(/\.maptiler\.com/);
|
const matchesMaptiler = url.match(/\.maptiler\.com/);
|
||||||
const matchesThunderforest = url.match(/\.thunderforest\.com/);
|
const matchesThunderforest = url.match(/\.thunderforest\.com/);
|
||||||
@@ -63,7 +65,7 @@ function setFetchAccessToken(url, mapStyle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRootSpec(spec, fieldName, newValues) {
|
function updateRootSpec(spec: any, fieldName: string, newValues: any) {
|
||||||
return {
|
return {
|
||||||
...spec,
|
...spec,
|
||||||
$root: {
|
$root: {
|
||||||
@@ -76,15 +78,75 @@ function updateRootSpec(spec, fieldName, newValues) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class App extends React.Component {
|
type OnStyleChangedOpts = {
|
||||||
constructor(props) {
|
save?: boolean
|
||||||
|
addRevision?: boolean
|
||||||
|
initialLoad?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type MappedErrors = {
|
||||||
|
message: string
|
||||||
|
parsed?: {
|
||||||
|
type: string
|
||||||
|
data: {
|
||||||
|
index: number
|
||||||
|
key: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppState = {
|
||||||
|
errors: MappedErrors[],
|
||||||
|
infos: string[],
|
||||||
|
mapStyle: StyleSpecification & {id: string},
|
||||||
|
dirtyMapStyle?: StyleSpecification,
|
||||||
|
selectedLayerIndex: number,
|
||||||
|
selectedLayerOriginalId?: string,
|
||||||
|
sources: {[key: string]: SourceSpecification},
|
||||||
|
vectorLayers: {},
|
||||||
|
spec: any,
|
||||||
|
mapView: {
|
||||||
|
zoom: number,
|
||||||
|
center: {
|
||||||
|
lng: number,
|
||||||
|
lat: number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maplibreGlDebugOptions: Partial<MapOptions> & {
|
||||||
|
showTileBoundaries: boolean,
|
||||||
|
showCollisionBoxes: boolean,
|
||||||
|
showOverdrawInspector: boolean,
|
||||||
|
},
|
||||||
|
openlayersDebugOptions: {
|
||||||
|
debugToolbox: boolean,
|
||||||
|
},
|
||||||
|
mapState: MapState
|
||||||
|
isOpen: {
|
||||||
|
settings: boolean
|
||||||
|
sources: boolean
|
||||||
|
open: boolean
|
||||||
|
shortcuts: boolean
|
||||||
|
export: boolean
|
||||||
|
survey: boolean
|
||||||
|
debug: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class App extends React.Component<any, AppState> {
|
||||||
|
revisionStore: RevisionStore;
|
||||||
|
styleStore: StyleStore | ApiStyleStore;
|
||||||
|
layerWatcher: LayerWatcher;
|
||||||
|
shortcutEl: ModalShortcuts | null = null;
|
||||||
|
|
||||||
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
|
|
||||||
this.revisionStore = new RevisionStore()
|
this.revisionStore = new RevisionStore()
|
||||||
const params = new URLSearchParams(window.location.search.substring(1))
|
const params = new URLSearchParams(window.location.search.substring(1))
|
||||||
let port = params.get("localport")
|
let port = params.get("localport")
|
||||||
if (port == null && (window.location.port != 80 && window.location.port != 443)) {
|
if (port == null && (window.location.port !== "80" && window.location.port !== "443")) {
|
||||||
port = window.location.port
|
port = window.location.port
|
||||||
}
|
}
|
||||||
this.styleStore = new ApiStyleStore({
|
this.styleStore = new ApiStyleStore({
|
||||||
@@ -136,7 +198,7 @@ export default class App extends React.Component {
|
|||||||
{
|
{
|
||||||
key: "m",
|
key: "m",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
document.querySelector(".maplibregl-canvas").focus();
|
(document.querySelector(".maplibregl-canvas") as HTMLCanvasElement).focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -149,7 +211,7 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
document.body.addEventListener("keyup", (e) => {
|
document.body.addEventListener("keyup", (e) => {
|
||||||
if(e.key === "Escape") {
|
if(e.key === "Escape") {
|
||||||
e.target.blur();
|
(e.target as HTMLElement).blur();
|
||||||
document.body.focus();
|
document.body.focus();
|
||||||
}
|
}
|
||||||
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
|
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
|
||||||
@@ -159,7 +221,7 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
if(shortcut) {
|
if(shortcut) {
|
||||||
this.setModal("shortcuts", false);
|
this.setModal("shortcuts", false);
|
||||||
shortcut.handler(e);
|
shortcut.handler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -192,8 +254,6 @@ export default class App extends React.Component {
|
|||||||
Debug.set("maputnik", "styleStore", this.styleStore);
|
Debug.set("maputnik", "styleStore", this.styleStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryObj = url.parse(window.location.href, true).query;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
errors: [],
|
errors: [],
|
||||||
infos: [],
|
infos: [],
|
||||||
@@ -235,25 +295,25 @@ export default class App extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyPress = (e) => {
|
handleKeyPress = (e: KeyboardEvent) => {
|
||||||
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
|
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
|
||||||
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
|
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onRedo(e);
|
this.onRedo();
|
||||||
}
|
}
|
||||||
else if(e.metaKey && e.keyCode === 90) {
|
else if(e.metaKey && e.keyCode === 90) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onUndo(e);
|
this.onUndo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(e.ctrlKey && e.keyCode === 90) {
|
if(e.ctrlKey && e.keyCode === 90) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onUndo(e);
|
this.onUndo();
|
||||||
}
|
}
|
||||||
else if(e.ctrlKey && e.keyCode === 89) {
|
else if(e.ctrlKey && e.keyCode === 89) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onRedo(e);
|
this.onRedo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,12 +326,12 @@ export default class App extends React.Component {
|
|||||||
window.removeEventListener("keydown", this.handleKeyPress);
|
window.removeEventListener("keydown", this.handleKeyPress);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveStyle(snapshotStyle) {
|
saveStyle(snapshotStyle: StyleSpecification & {id: string}) {
|
||||||
this.styleStore.save(snapshotStyle)
|
this.styleStore.save(snapshotStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFonts(urlTemplate) {
|
updateFonts(urlTemplate: string) {
|
||||||
const metadata = this.state.mapStyle.metadata || {}
|
const metadata: {[key: string]: string} = this.state.mapStyle.metadata || {} as any
|
||||||
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
|
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
|
||||||
|
|
||||||
let glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate;
|
let glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate;
|
||||||
@@ -280,13 +340,13 @@ export default class App extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIcons(baseUrl) {
|
updateIcons(baseUrl: string) {
|
||||||
downloadSpriteMetadata(baseUrl, icons => {
|
downloadSpriteMetadata(baseUrl, icons => {
|
||||||
this.setState({ spec: updateRootSpec(this.state.spec, 'sprite', icons)})
|
this.setState({ spec: updateRootSpec(this.state.spec, 'sprite', icons)})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeMetadataProperty = (property, value) => {
|
onChangeMetadataProperty = (property: string, value: any) => {
|
||||||
// If we're changing renderer reset the map state.
|
// If we're changing renderer reset the map state.
|
||||||
if (
|
if (
|
||||||
property === 'maputnik:renderer' &&
|
property === 'maputnik:renderer' &&
|
||||||
@@ -300,14 +360,14 @@ export default class App extends React.Component {
|
|||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.state.mapStyle,
|
...this.state.mapStyle,
|
||||||
metadata: {
|
metadata: {
|
||||||
...this.state.mapStyle.metadata,
|
...(this.state.mapStyle as any).metadata,
|
||||||
[property]: value
|
[property]: value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.onStyleChanged(changedStyle)
|
this.onStyleChanged(changedStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onStyleChanged = (newStyle, opts={}) => {
|
onStyleChanged = (newStyle: StyleSpecification & {id: string}, opts: OnStyleChangedOpts={}) => {
|
||||||
opts = {
|
opts = {
|
||||||
save: true,
|
save: true,
|
||||||
addRevision: true,
|
addRevision: true,
|
||||||
@@ -319,16 +379,16 @@ export default class App extends React.Component {
|
|||||||
this.getInitialStateFromUrl(newStyle);
|
this.getInitialStateFromUrl(newStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = validate(newStyle, latest) || [];
|
// This "any" can be removed in latest version of maplibre where maplibre re-exported types from style-spec
|
||||||
|
const errors = validate(newStyle as any, latest) || [];
|
||||||
|
|
||||||
// The validate function doesn't give us errors for duplicate error with
|
// The validate function doesn't give us errors for duplicate error with
|
||||||
// empty string for layer.id, manually deal with that here.
|
// empty string for layer.id, manually deal with that here.
|
||||||
const layerErrors = [];
|
const layerErrors: (Error | ValidationError)[] = [];
|
||||||
if (newStyle && newStyle.layers) {
|
if (newStyle && newStyle.layers) {
|
||||||
const foundLayers = new Map();
|
const foundLayers = new global.Map();
|
||||||
newStyle.layers.forEach((layer, index) => {
|
newStyle.layers.forEach((layer, index) => {
|
||||||
if (layer.id === "" && foundLayers.has(layer.id)) {
|
if (layer.id === "" && foundLayers.has(layer.id)) {
|
||||||
const message = `Duplicate layer: ${formatLayerId(layer.id)}`;
|
|
||||||
const error = new Error(
|
const error = new Error(
|
||||||
`layers[${index}]: duplicate layer id [empty_string], previously used`
|
`layers[${index}]: duplicate layer id [empty_string], previously used`
|
||||||
);
|
);
|
||||||
@@ -342,7 +402,7 @@ export default class App extends React.Component {
|
|||||||
// Special case: Duplicate layer id
|
// Special case: Duplicate layer id
|
||||||
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
|
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
|
||||||
if (dupMatch) {
|
if (dupMatch) {
|
||||||
const [matchStr, index, message] = dupMatch;
|
const [_matchStr, index, message] = dupMatch;
|
||||||
return {
|
return {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
parsed: {
|
parsed: {
|
||||||
@@ -359,7 +419,7 @@ export default class App extends React.Component {
|
|||||||
// Special case: Invalid source
|
// Special case: Invalid source
|
||||||
const invalidSourceMatch = error.message.match(/layers\[(\d+)\]: (source "(?:.*)" not found)/);
|
const invalidSourceMatch = error.message.match(/layers\[(\d+)\]: (source "(?:.*)" not found)/);
|
||||||
if (invalidSourceMatch) {
|
if (invalidSourceMatch) {
|
||||||
const [matchStr, index, message] = invalidSourceMatch;
|
const [_matchStr, index, message] = invalidSourceMatch;
|
||||||
return {
|
return {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
parsed: {
|
parsed: {
|
||||||
@@ -375,7 +435,7 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
||||||
if (layerMatch) {
|
if (layerMatch) {
|
||||||
const [matchStr, index, group, property, message] = layerMatch;
|
const [_matchStr, index, group, property, message] = layerMatch;
|
||||||
const key = (group && property) ? [group, property].join(".") : property;
|
const key = (group && property) ? [group, property].join(".") : property;
|
||||||
return {
|
return {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
@@ -396,7 +456,7 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let dirtyMapStyle = undefined;
|
let dirtyMapStyle: StyleSpecification | undefined = undefined;
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
dirtyMapStyle = cloneDeep(newStyle);
|
dirtyMapStyle = cloneDeep(newStyle);
|
||||||
|
|
||||||
@@ -406,7 +466,7 @@ export default class App extends React.Component {
|
|||||||
try {
|
try {
|
||||||
const objPath = message.split(":")[0];
|
const objPath = message.split(":")[0];
|
||||||
// Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
|
// Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
|
||||||
const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^\[]+/)[0];
|
const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^\[]+/)![0];
|
||||||
unset(dirtyMapStyle, unsetPath);
|
unset(dirtyMapStyle, unsetPath);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -417,17 +477,17 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
|
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
|
||||||
this.updateFonts(newStyle.glyphs)
|
this.updateFonts(newStyle.glyphs as string)
|
||||||
}
|
}
|
||||||
if(newStyle.sprite !== this.state.mapStyle.sprite) {
|
if(newStyle.sprite !== this.state.mapStyle.sprite) {
|
||||||
this.updateIcons(newStyle.sprite)
|
this.updateIcons(newStyle.sprite as string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.addRevision) {
|
if (opts.addRevision) {
|
||||||
this.revisionStore.addRevision(newStyle);
|
this.revisionStore.addRevision(newStyle);
|
||||||
}
|
}
|
||||||
if (opts.save) {
|
if (opts.save) {
|
||||||
this.saveStyle(newStyle);
|
this.saveStyle(newStyle as StyleSpecification & {id: string});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -460,7 +520,7 @@ export default class App extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMoveLayer = (move) => {
|
onMoveLayer = (move: SortEnd) => {
|
||||||
let { oldIndex, newIndex } = move;
|
let { oldIndex, newIndex } = move;
|
||||||
let layers = this.state.mapStyle.layers;
|
let layers = this.state.mapStyle.layers;
|
||||||
oldIndex = clamp(oldIndex, 0, layers.length-1);
|
oldIndex = clamp(oldIndex, 0, layers.length-1);
|
||||||
@@ -478,7 +538,7 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(layers);
|
this.onLayersChange(layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayersChange = (changedLayers) => {
|
onLayersChange = (changedLayers: LayerSpecification[]) => {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.state.mapStyle,
|
...this.state.mapStyle,
|
||||||
layers: changedLayers
|
layers: changedLayers
|
||||||
@@ -486,14 +546,14 @@ export default class App extends React.Component {
|
|||||||
this.onStyleChanged(changedStyle)
|
this.onStyleChanged(changedStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerDestroy = (index) => {
|
onLayerDestroy = (index: number) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
let layers = this.state.mapStyle.layers;
|
||||||
const remainingLayers = layers.slice(0);
|
const remainingLayers = layers.slice(0);
|
||||||
remainingLayers.splice(index, 1);
|
remainingLayers.splice(index, 1);
|
||||||
this.onLayersChange(remainingLayers);
|
this.onLayersChange(remainingLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerCopy = (index) => {
|
onLayerCopy = (index: number) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
let layers = this.state.mapStyle.layers;
|
||||||
const changedLayers = layers.slice(0)
|
const changedLayers = layers.slice(0)
|
||||||
|
|
||||||
@@ -503,7 +563,7 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerVisibilityToggle = (index) => {
|
onLayerVisibilityToggle = (index: number) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
let layers = this.state.mapStyle.layers;
|
||||||
const changedLayers = layers.slice(0)
|
const changedLayers = layers.slice(0)
|
||||||
|
|
||||||
@@ -517,7 +577,7 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onLayerIdChange = (index, oldId, newId) => {
|
onLayerIdChange = (index: number, _oldId: string, newId: string) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||||
changedLayers[index] = {
|
changedLayers[index] = {
|
||||||
...changedLayers[index],
|
...changedLayers[index],
|
||||||
@@ -527,26 +587,26 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerChanged = (index, layer) => {
|
onLayerChanged = (index: number, layer: LayerSpecification) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||||
changedLayers[index] = layer
|
changedLayers[index] = layer
|
||||||
|
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
setMapState = (newState) => {
|
setMapState = (newState: MapState) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
mapState: newState
|
mapState: newState
|
||||||
}, this.setStateInUrl);
|
}, this.setStateInUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultValues = (styleObj) => {
|
setDefaultValues = (styleObj: StyleSpecification & {id: string}) => {
|
||||||
const metadata = styleObj.metadata || {}
|
const metadata: {[key: string]: string} = styleObj.metadata || {} as any
|
||||||
if(metadata['maputnik:renderer'] === undefined) {
|
if(metadata['maputnik:renderer'] === undefined) {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...styleObj,
|
...styleObj,
|
||||||
metadata: {
|
metadata: {
|
||||||
...styleObj.metadata,
|
...styleObj.metadata as any,
|
||||||
'maputnik:renderer': 'mlgljs'
|
'maputnik:renderer': 'mlgljs'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -556,13 +616,13 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openStyle = (styleObj) => {
|
openStyle = (styleObj: StyleSpecification & {id: string}) => {
|
||||||
styleObj = this.setDefaultValues(styleObj)
|
styleObj = this.setDefaultValues(styleObj)
|
||||||
this.onStyleChanged(styleObj)
|
this.onStyleChanged(styleObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchSources() {
|
fetchSources() {
|
||||||
const sourceList = {};
|
const sourceList: {[key: string]: any} = {};
|
||||||
|
|
||||||
for(let [key, val] of Object.entries(this.state.mapStyle.sources)) {
|
for(let [key, val] of Object.entries(this.state.mapStyle.sources)) {
|
||||||
if(
|
if(
|
||||||
@@ -578,12 +638,12 @@ export default class App extends React.Component {
|
|||||||
let url = val.url;
|
let url = val.url;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
url = setFetchAccessToken(url, this.state.mapStyle)
|
url = setFetchAccessToken(url!, this.state.mapStyle)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn("Failed to setFetchAccessToken: ", err);
|
console.warn("Failed to setFetchAccessToken: ", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url, {
|
fetch(url!, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -599,7 +659,7 @@ export default class App extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for(let layer of json.vector_layers) {
|
for(let layer of json.vector_layers) {
|
||||||
sources[key].layers.push(layer.id)
|
(sources[key] as any).layers.push(layer.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("Updating source: "+key);
|
console.debug("Updating source: "+key);
|
||||||
@@ -625,11 +685,17 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getRenderer () {
|
_getRenderer () {
|
||||||
const metadata = this.state.mapStyle.metadata || {};
|
const metadata: {[key:string]: string} = this.state.mapStyle.metadata || {} as any;
|
||||||
return metadata['maputnik:renderer'] || 'mlgljs';
|
return metadata['maputnik:renderer'] || 'mlgljs';
|
||||||
}
|
}
|
||||||
|
|
||||||
onMapChange = (mapView) => {
|
onMapChange = (mapView: {
|
||||||
|
zoom: number,
|
||||||
|
center: {
|
||||||
|
lng: number,
|
||||||
|
lat: number,
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
mapView,
|
mapView,
|
||||||
});
|
});
|
||||||
@@ -637,16 +703,15 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
mapRenderer() {
|
mapRenderer() {
|
||||||
const {mapStyle, dirtyMapStyle} = this.state;
|
const {mapStyle, dirtyMapStyle} = this.state;
|
||||||
const metadata = this.state.mapStyle.metadata || {};
|
|
||||||
|
|
||||||
const mapProps = {
|
const mapProps = {
|
||||||
mapStyle: (dirtyMapStyle || mapStyle),
|
mapStyle: (dirtyMapStyle || mapStyle),
|
||||||
replaceAccessTokens: (mapStyle) => {
|
replaceAccessTokens: (mapStyle: StyleSpecification) => {
|
||||||
return style.replaceAccessTokens(mapStyle, {
|
return style.replaceAccessTokens(mapStyle, {
|
||||||
allowFallback: true
|
allowFallback: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDataChange: (e) => {
|
onDataChange: (e: {map: Map}) => {
|
||||||
this.layerWatcher.analyzeMap(e.map)
|
this.layerWatcher.analyzeMap(e.map)
|
||||||
this.fetchSources();
|
this.fetchSources();
|
||||||
},
|
},
|
||||||
@@ -677,7 +742,7 @@ export default class App extends React.Component {
|
|||||||
if(this.state.mapState.match(/^filter-/)) {
|
if(this.state.mapState.match(/^filter-/)) {
|
||||||
filterName = this.state.mapState.replace(/^filter-/, "");
|
filterName = this.state.mapState.replace(/^filter-/, "");
|
||||||
}
|
}
|
||||||
const elementStyle = {};
|
const elementStyle: {filter?: string} = {};
|
||||||
if (filterName) {
|
if (filterName) {
|
||||||
elementStyle.filter = `url('#${filterName}')`;
|
elementStyle.filter = `url('#${filterName}')`;
|
||||||
}
|
}
|
||||||
@@ -715,12 +780,12 @@ export default class App extends React.Component {
|
|||||||
history.replaceState({selectedLayerIndex}, "Maputnik", url.href);
|
history.replaceState({selectedLayerIndex}, "Maputnik", url.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInitialStateFromUrl = (mapStyle) => {
|
getInitialStateFromUrl = (mapStyle: StyleSpecification) => {
|
||||||
const url = new URL(location.href);
|
const url = new URL(location.href);
|
||||||
const modalParam = url.searchParams.get("modal");
|
const modalParam = url.searchParams.get("modal");
|
||||||
if (modalParam && modalParam !== "") {
|
if (modalParam && modalParam !== "") {
|
||||||
const modals = modalParam.split(",");
|
const modals = modalParam.split(",");
|
||||||
const modalObj = {};
|
const modalObj: {[key: string]: boolean} = {};
|
||||||
modals.forEach(modalName => {
|
modals.forEach(modalName => {
|
||||||
modalObj[modalName] = true;
|
modalObj[modalName] = true;
|
||||||
});
|
});
|
||||||
@@ -735,7 +800,7 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
const view = url.searchParams.get("view");
|
const view = url.searchParams.get("view");
|
||||||
if (view && view !== "") {
|
if (view && view !== "") {
|
||||||
this.setMapState(view);
|
this.setMapState(view as MapState);
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = url.searchParams.get("layer");
|
const path = url.searchParams.get("layer");
|
||||||
@@ -767,14 +832,14 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerSelect = (index) => {
|
onLayerSelect = (index: number) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedLayerIndex: index,
|
selectedLayerIndex: index,
|
||||||
selectedLayerOriginalId: this.state.mapStyle.layers[index].id,
|
selectedLayerOriginalId: this.state.mapStyle.layers[index].id,
|
||||||
}, this.setStateInUrl);
|
}, this.setStateInUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
setModal(modalName, value) {
|
setModal(modalName: keyof AppState["isOpen"], value: boolean) {
|
||||||
if(modalName === 'survey' && value === false) {
|
if(modalName === 'survey' && value === false) {
|
||||||
localStorage.setItem('survey', '');
|
localStorage.setItem('survey', '');
|
||||||
}
|
}
|
||||||
@@ -787,11 +852,11 @@ export default class App extends React.Component {
|
|||||||
}, this.setStateInUrl)
|
}, this.setStateInUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModal(modalName) {
|
toggleModal(modalName: keyof AppState["isOpen"]) {
|
||||||
this.setModal(modalName, !this.state.isOpen[modalName]);
|
this.setModal(modalName, !this.state.isOpen[modalName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeOpenlayersDebug = (key, value) => {
|
onChangeOpenlayersDebug = (key: keyof AppState["openlayersDebugOptions"], value: boolean) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
openlayersDebugOptions: {
|
openlayersDebugOptions: {
|
||||||
...this.state.openlayersDebugOptions,
|
...this.state.openlayersDebugOptions,
|
||||||
@@ -800,7 +865,7 @@ export default class App extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeMaplibreGlDebug = (key, value) => {
|
onChangeMaplibreGlDebug = (key: keyof AppState["maplibreGlDebugOptions"], value: any) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
maplibreGlDebugOptions: {
|
maplibreGlDebugOptions: {
|
||||||
...this.state.maplibreGlDebugOptions,
|
...this.state.maplibreGlDebugOptions,
|
||||||
@@ -811,8 +876,7 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const layers = this.state.mapStyle.layers || []
|
const layers = this.state.mapStyle.layers || []
|
||||||
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
|
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : undefined
|
||||||
const metadata = this.state.mapStyle.metadata || {}
|
|
||||||
|
|
||||||
const toolbar = <AppToolbar
|
const toolbar = <AppToolbar
|
||||||
renderer={this._getRenderer()}
|
renderer={this._getRenderer()}
|
||||||
@@ -855,7 +919,7 @@ export default class App extends React.Component {
|
|||||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
||||||
onLayerIdChange={this.onLayerIdChange}
|
onLayerIdChange={this.onLayerIdChange}
|
||||||
errors={this.state.errors}
|
errors={this.state.errors}
|
||||||
/> : null
|
/> : undefined
|
||||||
|
|
||||||
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
|
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
|
||||||
currentLayer={selectedLayer}
|
currentLayer={selectedLayer}
|
||||||
@@ -864,7 +928,7 @@ export default class App extends React.Component {
|
|||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
errors={this.state.errors}
|
errors={this.state.errors}
|
||||||
infos={this.state.infos}
|
infos={this.state.infos}
|
||||||
/> : null
|
/> : undefined
|
||||||
|
|
||||||
|
|
||||||
const modals = <div>
|
const modals = <div>
|
||||||
@@ -889,7 +953,6 @@ export default class App extends React.Component {
|
|||||||
onChangeMetadataProperty={this.onChangeMetadataProperty}
|
onChangeMetadataProperty={this.onChangeMetadataProperty}
|
||||||
isOpen={this.state.isOpen.settings}
|
isOpen={this.state.isOpen.settings}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'settings')}
|
onOpenToggle={this.toggleModal.bind(this, 'settings')}
|
||||||
openlayersDebugOptions={this.state.openlayersDebugOptions}
|
|
||||||
/>
|
/>
|
||||||
<ModalExport
|
<ModalExport
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {formatLayerId} from '../util/format';
|
import {formatLayerId} from '../util/format';
|
||||||
import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
|
import {LayerSpecification, StyleSpecification} from 'maplibre-gl';
|
||||||
|
|
||||||
type AppMessagePanelProps = {
|
type AppMessagePanelProps = {
|
||||||
errors?: unknown[]
|
errors?: unknown[]
|
||||||
infos?: unknown[]
|
infos?: unknown[]
|
||||||
mapStyle?: StyleSpecification
|
mapStyle?: StyleSpecification
|
||||||
onLayerSelect?(...args: unknown[]): unknown
|
onLayerSelect?(...args: unknown[]): unknown
|
||||||
currentLayer?: object
|
currentLayer?: LayerSpecification
|
||||||
selectedLayerIndex?: number
|
selectedLayerIndex?: number
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ class ToolbarAction extends React.Component<ToolbarActionProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MapState = "map" | "inspect" | "filter-achromatopsia" | "filter-deuteranopia" | "filter-protanopia" | "filter-tritanopia";
|
||||||
|
|
||||||
type AppToolbarProps = {
|
type AppToolbarProps = {
|
||||||
mapStyle: object
|
mapStyle: object
|
||||||
inspectModeEnabled: boolean
|
inspectModeEnabled: boolean
|
||||||
@@ -108,8 +110,8 @@ type AppToolbarProps = {
|
|||||||
sources: object
|
sources: object
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
onToggleModal(...args: unknown[]): unknown
|
onToggleModal(...args: unknown[]): unknown
|
||||||
onSetMapState(...args: unknown[]): unknown
|
onSetMapState(mapState: MapState): unknown
|
||||||
mapState?: string
|
mapState?: MapState
|
||||||
renderer?: string
|
renderer?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ export default class AppToolbar extends React.Component<AppToolbarProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelection(val: string | undefined) {
|
handleSelection(val: MapState) {
|
||||||
this.props.onSetMapState(val);
|
this.props.onSetMapState(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +247,7 @@ export default class AppToolbar extends React.Component<AppToolbarProps> {
|
|||||||
<select
|
<select
|
||||||
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 as MapState)}
|
||||||
value={currentView?.id}
|
value={currentView?.id}
|
||||||
>
|
>
|
||||||
{views.filter(v => v.group === "general").map((item) => {
|
{views.filter(v => v.group === "general").map((item) => {
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {combiningFilterOps} from '../libs/filterops'
|
|
||||||
import {mdiTableRowPlusAfter} from '@mdi/js';
|
import {mdiTableRowPlusAfter} from '@mdi/js';
|
||||||
import {isEqual} from 'lodash';
|
import {isEqual} from 'lodash';
|
||||||
|
import {ExpressionSpecification, LegacyFilterSpecification, StyleSpecification} from 'maplibre-gl'
|
||||||
import {latest, migrate, convertFilter} from '@maplibre/maplibre-gl-style-spec'
|
import {latest, migrate, convertFilter} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
import {mdiFunctionVariant} from '@mdi/js';
|
||||||
|
|
||||||
|
import {combiningFilterOps} from '../libs/filterops'
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from './InputSelect'
|
||||||
import Block from './Block'
|
import Block from './Block'
|
||||||
import SingleFilterEditor from './SingleFilterEditor'
|
import SingleFilterEditor from './SingleFilterEditor'
|
||||||
@@ -12,10 +13,9 @@ import FilterEditorBlock from './FilterEditorBlock'
|
|||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import Doc from './Doc'
|
import Doc from './Doc'
|
||||||
import ExpressionProperty from './_ExpressionProperty';
|
import ExpressionProperty from './_ExpressionProperty';
|
||||||
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 +30,15 @@ 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;
|
// This "any" can be removed in latest version of maplibre where maplibre re-exported types from style-spec
|
||||||
|
return (migrate(createStyleFromFilter(filter) as any).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 +70,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 +78,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?: {[key:string]: any}
|
||||||
properties: PropTypes.object,
|
filter?: any[]
|
||||||
filter: PropTypes.array,
|
errors?: {[key:string]: any}
|
||||||
errors: PropTypes.object,
|
onChange(value: LegacyFilterSpecification | ExpressionSpecification): unknown
|
||||||
onChange: PropTypes.func.isRequired,
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
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 +117,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 +155,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 +183,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 +206,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 +223,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 +252,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>
|
||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ class DeleteValueInputButton extends React.Component<DeleteValueInputButtonProps
|
|||||||
>
|
>
|
||||||
<FieldDocLabel
|
<FieldDocLabel
|
||||||
label={<MdDelete />}
|
label={<MdDelete />}
|
||||||
fieldSpec={{doc:" Remove array item."}}
|
|
||||||
/>
|
/>
|
||||||
</InputButton>
|
</InputButton>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,9 @@
|
|||||||
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 {BackgroundLayerSpecification, LayerSpecification, SourceSpecification} from 'maplibre-gl';
|
||||||
|
|
||||||
import FieldJson from './FieldJson'
|
import FieldJson from './FieldJson'
|
||||||
import FilterEditor from './FilterEditor'
|
import FilterEditor from './FilterEditor'
|
||||||
@@ -13,20 +16,16 @@ 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';
|
||||||
|
|
||||||
|
|
||||||
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 +43,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: {[key: string]: 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 +76,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 +88,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 +111,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 +128,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 +149,9 @@ export default class LayerEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let sourceLayerIds;
|
let sourceLayerIds;
|
||||||
if(this.props.sources.hasOwnProperty(this.props.layer.source)) {
|
const layer = this.props.layer as Exclude<LayerSpecification, BackgroundLayerSpecification>;
|
||||||
sourceLayerIds = this.props.sources[this.props.layer.source].layers;
|
if(this.props.sources.hasOwnProperty(layer.source)) {
|
||||||
|
sourceLayerIds = (this.props.sources[layer.source] as any).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'>
|
||||||
@@ -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';
|
||||||
|
|
||||||
@@ -7,21 +6,14 @@ import LayerListGroup from './LayerListGroup'
|
|||||||
import LayerListItem from './LayerListItem'
|
import LayerListItem from './LayerListItem'
|
||||||
import ModalAdd from './ModalAdd'
|
import ModalAdd from './ModalAdd'
|
||||||
|
|
||||||
import {SortableContainer} from 'react-sortable-hoc';
|
import {SortEndHandler, SortableContainer} from 'react-sortable-hoc';
|
||||||
|
import type {LayerSpecification} from 'maplibre-gl';
|
||||||
|
|
||||||
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(layers: LayerSpecification[]): 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,
|
||||||
@@ -74,9 +86,9 @@ 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: SortEndHandler
|
||||||
|
};
|
||||||
|
|
||||||
|
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, LngLat, 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?(event: {map: Map | null}): 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?: Partial<MapOptions> & {
|
||||||
options: PropTypes.object,
|
showTileBoundaries?: boolean
|
||||||
replaceAccessTokens: PropTypes.func.isRequired,
|
showCollisionBoxes?: boolean
|
||||||
onChange: PropTypes.func.isRequired,
|
showOverdrawInspector?: boolean
|
||||||
}
|
}
|
||||||
|
replaceAccessTokens(mapStyle: StyleSpecification): StyleSpecification
|
||||||
|
onChange(value: {center: LngLat, zoom: number}): 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">
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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 { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
|
import type {StyleSpecification} from 'maplibre-gl';
|
||||||
|
|
||||||
|
|
||||||
function renderCoords (coords: string[]) {
|
function renderCoords (coords: string[]) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type ModalProps = {
|
|||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
title: string
|
title: string
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(value: boolean): unknown
|
||||||
underlayClickExits?: boolean
|
underlayClickExits?: boolean
|
||||||
underlayProps?: any
|
underlayProps?: any
|
||||||
className?: string
|
className?: string
|
||||||
|
|||||||
@@ -6,18 +6,19 @@ 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'
|
||||||
|
import type {LayerSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
type ModalAddProps = {
|
type ModalAddProps = {
|
||||||
layers: unknown[]
|
layers: LayerSpecification[]
|
||||||
onLayersChange(...args: unknown[]): unknown
|
onLayersChange(layers: LayerSpecification[]): unknown
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(open: boolean): unknown
|
||||||
// A dict of source id's and the available source layers
|
// A dict of source id's and the available source layers
|
||||||
sources: any
|
sources: any
|
||||||
};
|
};
|
||||||
|
|
||||||
type ModalAddState = {
|
type ModalAddState = {
|
||||||
type: string
|
type: LayerSpecification["type"]
|
||||||
id: string
|
id: string
|
||||||
source?: string
|
source?: string
|
||||||
'source-layer'?: string
|
'source-layer'?: string
|
||||||
@@ -38,7 +39,7 @@ export default class ModalAdd extends React.Component<ModalAddProps, ModalAddSta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changedLayers.push(layer)
|
changedLayers.push(layer as LayerSpecification)
|
||||||
|
|
||||||
this.props.onLayersChange(changedLayers)
|
this.props.onLayersChange(changedLayers)
|
||||||
this.props.onOpenToggle(false)
|
this.props.onOpenToggle(false)
|
||||||
@@ -145,7 +146,7 @@ export default class ModalAdd extends React.Component<ModalAddProps, ModalAddSta
|
|||||||
<FieldType
|
<FieldType
|
||||||
value={this.state.type}
|
value={this.state.type}
|
||||||
wdKey="add-layer.layer-type"
|
wdKey="add-layer.layer-type"
|
||||||
onChange={(v: string) => this.setState({ type: v })}
|
onChange={(v: LayerSpecification["type"]) => this.setState({ type: v })}
|
||||||
/>
|
/>
|
||||||
{this.state.type !== 'background' &&
|
{this.state.type !== 'background' &&
|
||||||
<FieldSource
|
<FieldSource
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ import Modal from './Modal'
|
|||||||
type ModalDebugProps = {
|
type ModalDebugProps = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
renderer: string
|
renderer: string
|
||||||
onChangeMaboxGlDebug(...args: unknown[]): unknown
|
onChangeMaplibreGlDebug(key: string, checked: boolean): unknown
|
||||||
onChangeOpenlayersDebug(...args: unknown[]): unknown
|
onChangeOpenlayersDebug(key: string, checked: boolean): unknown
|
||||||
onOpenToggle(...args: unknown[]): unknown
|
onOpenToggle(value: boolean): unknown
|
||||||
maplibreGlDebugOptions?: object
|
maplibreGlDebugOptions?: object
|
||||||
openlayersDebugOptions?: object
|
openlayersDebugOptions?: object
|
||||||
mapView: {
|
mapView: {
|
||||||
zoom: number
|
zoom: number
|
||||||
center: {
|
center: {
|
||||||
lng: string
|
lng: number
|
||||||
lat: string
|
lat: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -26,8 +26,8 @@ export default class ModalDebug extends React.Component<ModalDebugProps> {
|
|||||||
const {mapView} = this.props;
|
const {mapView} = this.props;
|
||||||
|
|
||||||
const osmZoom = Math.round(mapView.zoom)+1;
|
const osmZoom = Math.round(mapView.zoom)+1;
|
||||||
const osmLon = Number.parseFloat(mapView.center.lng).toFixed(5);
|
const osmLon = +(mapView.center.lng).toFixed(5);
|
||||||
const osmLat = Number.parseFloat(mapView.center.lat).toFixed(5);
|
const osmLat = +(mapView.center.lat).toFixed(5);
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal:debug"
|
data-wd-key="modal:debug"
|
||||||
@@ -42,7 +42,7 @@ export default class ModalDebug extends React.Component<ModalDebugProps> {
|
|||||||
{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} onChange={(e) => this.props.onChangeMaboxGlDebug(key, e.target.checked)} /> {key}
|
<input type="checkbox" checked={val} onChange={(e) => this.props.onChangeMaplibreGlDebug(key, e.target.checked)} /> {key}
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import React from 'react'
|
|||||||
import Slugify from 'slugify'
|
import Slugify from 'slugify'
|
||||||
import {saveAs} from 'file-saver'
|
import {saveAs} from 'file-saver'
|
||||||
import {version} from 'maplibre-gl/package.json'
|
import {version} from 'maplibre-gl/package.json'
|
||||||
import {StyleSpecification, format} from '@maplibre/maplibre-gl-style-spec'
|
import {format} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
import type {StyleSpecification} from 'maplibre-gl'
|
||||||
import {MdFileDownload} from 'react-icons/md'
|
import {MdFileDownload} from 'react-icons/md'
|
||||||
|
|
||||||
import FieldString from './FieldString'
|
import FieldString from './FieldString'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {LightSpecification, StyleSpecification, TransitionSpecification, latest} from '@maplibre/maplibre-gl-style-spec'
|
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
import type {LightSpecification, StyleSpecification, TransitionSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
import FieldArray from './FieldArray'
|
import FieldArray from './FieldArray'
|
||||||
import FieldNumber from './FieldNumber'
|
import FieldNumber from './FieldNumber'
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification, latest} from '@maplibre/maplibre-gl-style-spec'
|
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
||||||
|
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import InputButton from './InputButton'
|
import InputButton from './InputButton'
|
||||||
import FieldString from './FieldString'
|
import FieldString from './FieldString'
|
||||||
@@ -10,7 +13,6 @@ import style from '../libs/style'
|
|||||||
import { deleteSource, addSource, changeSource } from '../libs/source'
|
import { deleteSource, addSource, changeSource } from '../libs/source'
|
||||||
import publicSources from '../config/tilesets.json'
|
import publicSources from '../config/tilesets.json'
|
||||||
|
|
||||||
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
|
||||||
|
|
||||||
type PublicSourceProps = {
|
type PublicSourceProps = {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import FieldFunction from './FieldFunction'
|
import FieldFunction from './FieldFunction'
|
||||||
import { LayerSpecification } from '@maplibre/maplibre-gl-style-spec'
|
import type {LayerSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
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
|
||||||
@@ -39,7 +40,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> {
|
||||||
|
|||||||
@@ -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}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const typeMap = {
|
|||||||
number: () => Block,
|
number: () => Block,
|
||||||
string: () => Block,
|
string: () => Block,
|
||||||
formatted: () => Block,
|
formatted: () => Block,
|
||||||
|
padding: () => Block,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SpecFieldProps = InputFieldSpecProps & {
|
export type SpecFieldProps = InputFieldSpecProps & {
|
||||||
|
|||||||
@@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ function setStopRefs(props: ZoomPropertyProps, state: ZoomPropertyState) {
|
|||||||
newRefs = {...state};
|
newRefs = {...state};
|
||||||
}
|
}
|
||||||
newRefs[idx] = docUid("stop-");
|
newRefs[idx] = docUid("stop-");
|
||||||
|
} else {
|
||||||
|
newRefs[idx] = state.refs[idx];
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRefs;
|
return newRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +157,6 @@ export default class ZoomProperty extends React.Component<ZoomPropertyProps, Zoo
|
|||||||
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)} />
|
||||||
|
|
||||||
return <tr
|
return <tr
|
||||||
key={key}
|
key={key}
|
||||||
>
|
>
|
||||||
@@ -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!)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import style from './style.js'
|
import style from './style.js'
|
||||||
import {StyleSpecification, format} from '@maplibre/maplibre-gl-style-spec'
|
import {format} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
import type {StyleSpecification} from 'maplibre-gl'
|
||||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||||
|
|
||||||
export type ApiStyleStoreOptions = {
|
export type ApiStyleStoreOptions = {
|
||||||
port?: string
|
port: string | null
|
||||||
host?: string
|
host: string | null
|
||||||
onLocalStyleChange?: (style: any) => void
|
onLocalStyleChange?: (style: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {StyleSpecification, diff} from '@maplibre/maplibre-gl-style-spec'
|
import {diff} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
import type {StyleSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
function diffMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
function diffMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
||||||
const changes = diff(beforeStyle, afterStyle)
|
const changes = diff(beforeStyle, afterStyle)
|
||||||
|
|||||||
@@ -2,40 +2,42 @@
|
|||||||
import stylegen from 'mapbox-gl-inspect/lib/stylegen'
|
import stylegen from 'mapbox-gl-inspect/lib/stylegen'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
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 type {FilterSpecification,LayerSpecification } from 'maplibre-gl'
|
||||||
|
|
||||||
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') {
|
||||||
l.paint!['line-width'] = 2
|
l.paint!['line-width'] = 2
|
||||||
}
|
|
||||||
|
|
||||||
if("filter" in layer) {
|
|
||||||
l.filter = layer.filter
|
|
||||||
} else {
|
|
||||||
delete l['filter']
|
|
||||||
}
|
|
||||||
l.id = l.id + '_highlight'
|
|
||||||
return l
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if("filter" in layer) {
|
||||||
|
l.filter = layer.filter
|
||||||
|
} else {
|
||||||
|
delete l['filter']
|
||||||
|
}
|
||||||
|
l.id = l.id + '_highlight'
|
||||||
|
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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type {StyleSpecification} from "@maplibre/maplibre-gl-style-spec";
|
import type {StyleSpecification} from "maplibre-gl";
|
||||||
|
|
||||||
export class RevisionStore {
|
export class RevisionStore {
|
||||||
revisions: StyleSpecification[];
|
revisions: (StyleSpecification & {id: string})[];
|
||||||
currentIdx: number;
|
currentIdx: number;
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export class RevisionStore {
|
|||||||
return this.revisions[this.currentIdx]
|
return this.revisions[this.currentIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
addRevision(revision: StyleSpecification) {
|
addRevision(revision: StyleSpecification & {id: string}) {
|
||||||
//TODO: compare new revision style id with old ones
|
//TODO: compare new revision style id with old ones
|
||||||
//and ensure that it is always the same id
|
//and ensure that it is always the same id
|
||||||
this.revisions.push(revision)
|
this.revisions.push(revision)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type {StyleSpecification, SourceSpecification} from "@maplibre/maplibre-gl-style-spec";
|
import type {StyleSpecification, SourceSpecification} from "maplibre-gl";
|
||||||
|
|
||||||
export function deleteSource(mapStyle: StyleSpecification, sourceId: string) {
|
export function deleteSource(mapStyle: StyleSpecification, sourceId: string) {
|
||||||
const remainingSources = { ...mapStyle.sources}
|
const remainingSources = { ...mapStyle.sources}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {derefLayers, StyleSpecification, LayerSpecification} from '@maplibre/maplibre-gl-style-spec'
|
import {derefLayers} from '@maplibre/maplibre-gl-style-spec'
|
||||||
|
import type {StyleSpecification, LayerSpecification} from 'maplibre-gl'
|
||||||
import tokens from '../config/tokens.json'
|
import tokens from '../config/tokens.json'
|
||||||
|
|
||||||
// Empty style is always used if no style could be restored or fetched
|
// Empty style is always used if no style could be restored or fetched
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import style from './style'
|
import style from './style'
|
||||||
import {loadStyleUrl} from './urlopen'
|
import {loadStyleUrl} from './urlopen'
|
||||||
import publicSources from '../config/styles.json'
|
import publicSources from '../config/styles.json'
|
||||||
import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
|
import type {StyleSpecification} from 'maplibre-gl'
|
||||||
|
|
||||||
const storagePrefix = "maputnik"
|
const storagePrefix = "maputnik"
|
||||||
const stylePrefix = 'style'
|
const stylePrefix = 'style'
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
|
// @ts-ignore - this is a fork of jsonlint
|
||||||
import jsonlint from 'jsonlint';
|
import jsonlint from 'jsonlint';
|
||||||
import CodeMirror from 'codemirror';
|
import CodeMirror, { MarkerRange } from 'codemirror';
|
||||||
import jsonToAst from 'json-to-ast';
|
import jsonToAst from 'json-to-ast';
|
||||||
import {expression, validate} from '@maplibre/maplibre-gl-style-spec';
|
import {expression, validate} from '@maplibre/maplibre-gl-style-spec';
|
||||||
|
|
||||||
|
type MarkerRangeWithMessage = MarkerRange & {message: string};
|
||||||
|
|
||||||
CodeMirror.defineMode("mgl", function(config, parserConfig) {
|
|
||||||
|
CodeMirror.defineMode("mgl", (config, parserConfig) => {
|
||||||
// Just using the javascript mode with json enabled. Our logic is in the linter below.
|
// Just using the javascript mode with json enabled. Our logic is in the linter below.
|
||||||
return CodeMirror.modes.javascript(
|
return CodeMirror.modes.javascript(
|
||||||
{...config, json: true},
|
{...config, json: true} as any,
|
||||||
parserConfig
|
parserConfig
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
CodeMirror.registerHelper("lint", "json", function(text) {
|
CodeMirror.registerHelper("lint", "json", (text: string) => {
|
||||||
const found = [];
|
const found: MarkerRangeWithMessage[] = [];
|
||||||
|
|
||||||
// NOTE: This was modified from the original to remove the global, also the
|
// NOTE: This was modified from the original to remove the global, also the
|
||||||
// old jsonlint API was 'jsonlint.parseError' its now
|
// old jsonlint API was 'jsonlint.parseError' its now
|
||||||
// 'jsonlint.parser.parseError'
|
// 'jsonlint.parser.parseError'
|
||||||
jsonlint.parser.parseError = function(str, hash) {
|
(jsonlint as any).parser.parseError = (str: string, hash: any) => {
|
||||||
const loc = hash.loc;
|
const loc = hash.loc;
|
||||||
found.push({
|
found.push({
|
||||||
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
||||||
@@ -36,12 +39,12 @@ CodeMirror.registerHelper("lint", "json", function(text) {
|
|||||||
return found;
|
return found;
|
||||||
});
|
});
|
||||||
|
|
||||||
CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) => {
|
||||||
const found = [];
|
const found: MarkerRangeWithMessage[] = [];
|
||||||
const {parser} = jsonlint;
|
const {parser} = jsonlint as any;
|
||||||
const {context} = opts;
|
const {context} = opts;
|
||||||
|
|
||||||
parser.parseError = function(str, hash) {
|
parser.parseError = (str: string, hash: any) => {
|
||||||
const loc = hash.loc;
|
const loc = hash.loc;
|
||||||
found.push({
|
found.push({
|
||||||
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
||||||
@@ -62,7 +65,7 @@ CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
|||||||
const ast = jsonToAst(text);
|
const ast = jsonToAst(text);
|
||||||
const input = JSON.parse(text);
|
const input = JSON.parse(text);
|
||||||
|
|
||||||
function getArrayPositionalFromAst (node, path) {
|
function getArrayPositionalFromAst(node: any, path: string[]) {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -79,7 +82,7 @@ CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
|||||||
newNode = node.children[path[0]];
|
newNode = node.children[path[0]];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
newNode = node.children.find(childNode => {
|
newNode = node.children.find((childNode: any) => {
|
||||||
return (
|
return (
|
||||||
childNode.key &&
|
childNode.key &&
|
||||||
childNode.key.type === "Identifier" &&
|
childNode.key.type === "Identifier" &&
|
||||||
@@ -94,7 +97,7 @@ CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let out;
|
let out: ReturnType<typeof expression.createExpression> | null = null;
|
||||||
if (context === "layer") {
|
if (context === "layer") {
|
||||||
// Just an empty style so we can validate a layer.
|
// Just an empty style so we can validate a layer.
|
||||||
const errors = validate({
|
const errors = validate({
|
||||||
@@ -121,6 +124,7 @@ CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
|||||||
// Remove the 'layers[0].' as we're validating the layer only here
|
// Remove the 'layers[0].' as we're validating the layer only here
|
||||||
const errMessageParts = err.message.replace(/^layers\[0\]./, "").split(":");
|
const errMessageParts = err.message.replace(/^layers\[0\]./, "").split(":");
|
||||||
return {
|
return {
|
||||||
|
name: '',
|
||||||
key: errMessageParts[0],
|
key: errMessageParts[0],
|
||||||
message: errMessageParts[1],
|
message: errMessageParts[1],
|
||||||
};
|
};
|
||||||
@@ -135,7 +139,7 @@ CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
|||||||
throw new Error(`Invalid context ${context}`);
|
throw new Error(`Invalid context ${context}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out.result === "error") {
|
if (out?.result === "error") {
|
||||||
const errors = out.value;
|
const errors = out.value;
|
||||||
errors.forEach(error => {
|
errors.forEach(error => {
|
||||||
const {key, message} = error;
|
const {key, message} = error;
|
||||||
Reference in New Issue
Block a user