mirror of
https://github.com/maputnik/editor.git
synced 2026-01-04 20:40:01 +00:00
Compare commits
54 Commits
v1.7.0-bet
...
v1.7.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
184bfeeaf8 | ||
|
|
e45f8d960d | ||
|
|
1fede3af3a | ||
|
|
5ad74048bd | ||
|
|
a0a91474de | ||
|
|
c3670701e5 | ||
|
|
86923330d9 | ||
|
|
4517148e5a | ||
|
|
0433d66f45 | ||
|
|
0c592bacab | ||
|
|
d98637cb12 | ||
|
|
1070209cb5 | ||
|
|
b6189f77c4 | ||
|
|
25322a3952 | ||
|
|
5943c6f282 | ||
|
|
090a26bb40 | ||
|
|
af03b010a4 | ||
|
|
578a920b6d | ||
|
|
0858a16ffc | ||
|
|
7cfe0563bc | ||
|
|
ee72389534 | ||
|
|
8f722c59de | ||
|
|
94d2e958eb | ||
|
|
d931c7cb38 | ||
|
|
6da83c4670 | ||
|
|
d26af16003 | ||
|
|
d75b86c927 | ||
|
|
a0cd087ccc | ||
|
|
313b639a5f | ||
|
|
93c45d5340 | ||
|
|
3be6cb5926 | ||
|
|
9d151fdc1f | ||
|
|
44d1a7a6b0 | ||
|
|
0e5676eae0 | ||
|
|
b8739915b2 | ||
|
|
a1dedd1aa6 | ||
|
|
33b4a40c35 | ||
|
|
a624909819 | ||
|
|
d5d387f349 | ||
|
|
c58ae0f895 | ||
|
|
c9e360d675 | ||
|
|
75ece350bd | ||
|
|
45680151ef | ||
|
|
87bae82b17 | ||
|
|
fcad636f85 | ||
|
|
bac8495b3c | ||
|
|
df98cb9c7b | ||
|
|
34c3015b42 | ||
|
|
ca7bf9f4a7 | ||
|
|
61ba399e1c | ||
|
|
b5c09a4f17 | ||
|
|
6f83839a4c | ||
|
|
74b47e7e74 | ||
|
|
6b45dc8b4d |
@@ -18,6 +18,7 @@ A free and open visual editor for the [Mapbox GL styles](https://www.mapbox.com/
|
|||||||
targeted at developers and map designers.
|
targeted at developers and map designers.
|
||||||
|
|
||||||
- :link: Design your maps online at **<https://maputnik.github.io/editor/>** (all in local storage)
|
- :link: Design your maps online at **<https://maputnik.github.io/editor/>** (all in local storage)
|
||||||
|
- :link: Try out the v1.7.0-beta release at: https://maputnik.github.io/releases/v1.7.0-beta/
|
||||||
- :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development
|
- :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development
|
||||||
|
|
||||||
Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independence is an OSS map designer.
|
Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independence is an OSS map designer.
|
||||||
|
|||||||
95
package-lock.json
generated
95
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "maputnik",
|
"name": "maputnik",
|
||||||
"version": "1.7.0-beta",
|
"version": "1.7.0-beta4",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1514,9 +1514,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@mdi/react": {
|
"@mdi/react": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.4.0.tgz",
|
||||||
"integrity": "sha512-RmdB3gsAW4iXOTTHaEaGQ//2w0sxGWiZEoIDteXcf1qTkDkaA+LBu6ub4nNi4VcmSKjcceGHnYHqHENh8fky7A=="
|
"integrity": "sha512-OUH9RhfDJPhybQL3owwrSDIXz2yVKXg5lYeOZjyRCiT9wqywNK0FeYyDByOwNIZnnIQoQYmuSrMv+pOX0Uqkmw=="
|
||||||
},
|
},
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
@@ -2490,6 +2490,11 @@
|
|||||||
"is-string": "^1.0.5"
|
"is-string": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"array-move": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-move/-/array-move-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-qQpEHBnVT6HAFgEVUwRdHVd8TYJThrZIT5wSXpEUTPwBaYhPLclw12mEpyUvRWVdl1VwPOqnIy6LqTFN3cSeUQ=="
|
||||||
|
},
|
||||||
"array-union": {
|
"array-union": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
||||||
@@ -5783,9 +5788,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"fastq": {
|
"fastq": {
|
||||||
"version": "1.6.1",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz",
|
||||||
"integrity": "sha512-mpIH5sKYueh3YyeJwqtVo8sORi0CgtmkVbK6kZStpQlZBYQuTzG2CZ7idSiJuA7bY0SFCWUc5WIs+oYumGCQNw==",
|
"integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
@@ -6426,9 +6431,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gl-matrix": {
|
"gl-matrix": {
|
||||||
"version": "3.2.1",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.3.0.tgz",
|
||||||
"integrity": "sha512-YYVO8jUSf6+SakL4AJmx9Jc7zAZhkJQ+WhdtX3VQe5PJdCOX6/ybY4x1vk+h94ePnjRn6uml68+QxTAJneUpvA=="
|
"integrity": "sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA=="
|
||||||
},
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.1.6",
|
"version": "7.1.6",
|
||||||
@@ -6535,18 +6540,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gonzales-pe": {
|
"gonzales-pe": {
|
||||||
"version": "4.2.4",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz",
|
||||||
"integrity": "sha512-v0Ts/8IsSbh9n1OJRnSfa7Nlxi4AkXIsWB6vPept8FDbL4bXn3FNuxjYtO/nmBGu7GDkL9MFeGebeSu6l55EPQ==",
|
"integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "1.1.x"
|
"minimist": "^1.2.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.1.3",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8841,9 +8846,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mapbox-gl": {
|
"mapbox-gl": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.9.1.tgz",
|
||||||
"integrity": "sha512-PKpoiB2pPUMrqFfBJpt/oA8On3zcp0adEoDS2YIC2RA6o4EZ9Sq2NPZocb64y7ra3mLUvEb7ps1pLVlPMh6y7w==",
|
"integrity": "sha512-jpBcqh+4qpOkj8RdxRdvwKPA8gzNYyMQ8HOcXgZYuEM5nKevRDjD3cEs+rUxi1JuYj4t8bIk68Lfh7aQQC1MjQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@mapbox/geojson-rewind": "^0.4.0",
|
"@mapbox/geojson-rewind": "^0.4.0",
|
||||||
"@mapbox/geojson-types": "^1.0.2",
|
"@mapbox/geojson-types": "^1.0.2",
|
||||||
@@ -9180,9 +9185,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
"integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==",
|
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mocha": {
|
"mocha": {
|
||||||
@@ -9990,9 +9995,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ol": {
|
"ol": {
|
||||||
"version": "6.2.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ol/-/ol-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ol/-/ol-6.3.1.tgz",
|
||||||
"integrity": "sha512-CT2szew/COd7Zf9Bls+pdzewBYZNgyfxFivJ3L4Jv9Th7JdWjcQAT+pqMPH25L9SbVT+T17RCMq2H2m9uBCl1A==",
|
"integrity": "sha512-cSSYizzUJQ7AhFSrLPAKopjDXPCHtJsXSmjf3xutPG+iyBeD9IOepQGSgpOSZavPI1gsYh9wowiH+NZwVJ/NYQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"elm-pep": "^1.0.4",
|
"elm-pep": "^1.0.4",
|
||||||
"pbf": "3.2.1",
|
"pbf": "3.2.1",
|
||||||
@@ -10723,12 +10728,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss-sass": {
|
"postcss-sass": {
|
||||||
"version": "0.4.2",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz",
|
||||||
"integrity": "sha512-hcRgnd91OQ6Ot9R90PE/khUDCJHG8Uxxd3F7Y0+9VHjBiJgNv7sK5FxyHMCBtoLmmkzVbSj3M3OlqUfLJpq0CQ==",
|
"integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"gonzales-pe": "^4.2.4",
|
"gonzales-pe": "^4.3.0",
|
||||||
"postcss": "^7.0.21"
|
"postcss": "^7.0.21"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -13246,12 +13251,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"stylelint": {
|
"stylelint": {
|
||||||
"version": "13.2.1",
|
"version": "13.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.3.0.tgz",
|
||||||
"integrity": "sha512-461ZV4KpUe7pEHHgMOsH4kkjF7qsjkCIMJYOf7QQC4cvgPUJ0z4Nj+ah5fvKl1rzqBqc5EZa6P0nna4CGoJX+A==",
|
"integrity": "sha512-ehNzQu9JAbxuiNhUhmoyPgMjIdz7Fg1AxC5urPVhKotto/faF5GxwljSoLvQa6pB6yd+BVuofApWjWT/6/rBMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"autoprefixer": "^9.7.4",
|
"autoprefixer": "^9.7.5",
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"cosmiconfig": "^6.0.0",
|
"cosmiconfig": "^6.0.0",
|
||||||
@@ -13271,7 +13276,7 @@
|
|||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"log-symbols": "^3.0.0",
|
"log-symbols": "^3.0.0",
|
||||||
"mathml-tag-names": "^2.1.3",
|
"mathml-tag-names": "^2.1.3",
|
||||||
"meow": "^6.0.1",
|
"meow": "^6.1.0",
|
||||||
"micromatch": "^4.0.2",
|
"micromatch": "^4.0.2",
|
||||||
"normalize-selector": "^0.2.0",
|
"normalize-selector": "^0.2.0",
|
||||||
"postcss": "^7.0.27",
|
"postcss": "^7.0.27",
|
||||||
@@ -13282,7 +13287,7 @@
|
|||||||
"postcss-media-query-parser": "^0.2.3",
|
"postcss-media-query-parser": "^0.2.3",
|
||||||
"postcss-reporter": "^6.0.1",
|
"postcss-reporter": "^6.0.1",
|
||||||
"postcss-resolve-nested-selector": "^0.1.1",
|
"postcss-resolve-nested-selector": "^0.1.1",
|
||||||
"postcss-safe-parser": "^4.0.1",
|
"postcss-safe-parser": "^4.0.2",
|
||||||
"postcss-sass": "^0.4.2",
|
"postcss-sass": "^0.4.2",
|
||||||
"postcss-scss": "^2.0.0",
|
"postcss-scss": "^2.0.0",
|
||||||
"postcss-selector-parser": "^6.0.2",
|
"postcss-selector-parser": "^6.0.2",
|
||||||
@@ -13318,9 +13323,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"camelcase-keys": {
|
"camelcase-keys": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
|
||||||
"integrity": "sha512-BPCNVH56RVIxQQIXskp5tLQXUNGQ6sXr7iCv1FHDt81xBOQ/1r6H8SPxf19InVP6DexWar4s87q9thfuk8X9HA==",
|
"integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"camelcase": "^5.3.1",
|
"camelcase": "^5.3.1",
|
||||||
@@ -14526,9 +14531,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
|
||||||
"integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==",
|
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"v8-compile-cache": {
|
"v8-compile-cache": {
|
||||||
@@ -14606,9 +14611,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"vfile-message": {
|
"vfile-message": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
|
||||||
"integrity": "sha512-qQg/2z8qnnBHL0psXyF72kCjb9YioIynvyltuNKFaUhRtqTIcIMP3xnBaPzirVZNuBrUe1qwFciSx2yApa4byw==",
|
"integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/unist": "^2.0.0",
|
"@types/unist": "^2.0.0",
|
||||||
|
|||||||
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "maputnik",
|
"name": "maputnik",
|
||||||
"version": "1.7.0-beta",
|
"version": "1.7.0-beta4",
|
||||||
"description": "A MapboxGL visual style editor",
|
"description": "A MapboxGL visual style editor",
|
||||||
"main": "''",
|
"main": "''",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
"@babel/runtime": "^7.8.4",
|
"@babel/runtime": "^7.8.4",
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
|
||||||
"@mapbox/mapbox-gl-style-spec": "^13.12.0",
|
"@mapbox/mapbox-gl-style-spec": "^13.12.0",
|
||||||
"@mdi/react": "^1.3.0",
|
"@mdi/react": "^1.4.0",
|
||||||
|
"array-move": "^2.2.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"codemirror": "^5.52.0",
|
"codemirror": "^5.52.0",
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
@@ -40,10 +41,10 @@
|
|||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mapbox-gl": "^1.9.0",
|
"mapbox-gl": "^1.9.1",
|
||||||
"mapbox-gl-inspect": "^1.3.1",
|
"mapbox-gl-inspect": "^1.3.1",
|
||||||
"maputnik-design": "github:maputnik/design#f7a2b4d",
|
"maputnik-design": "github:maputnik/design#f7a2b4d",
|
||||||
"ol": "^6.2.1",
|
"ol": "^6.3.1",
|
||||||
"ol-mapbox-style": "^6.0.1",
|
"ol-mapbox-style": "^6.0.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
@@ -102,6 +103,11 @@
|
|||||||
"experimentalObjectRestSpread": true,
|
"experimentalObjectRestSpread": true,
|
||||||
"jsx": true
|
"jsx": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -135,19 +141,19 @@
|
|||||||
"is-docker": "^2.0.0",
|
"is-docker": "^2.0.0",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"istanbul-lib-coverage": "^3.0.0",
|
"istanbul-lib-coverage": "^3.0.0",
|
||||||
"mkdirp": "^1.0.3",
|
"mkdirp": "^1.0.4",
|
||||||
"mocha": "^7.0.1",
|
"mocha": "^7.0.1",
|
||||||
"node-sass": "^4.13.1",
|
"node-sass": "^4.13.1",
|
||||||
"react-hot-loader": "^4.12.19",
|
"react-hot-loader": "^4.12.19",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"selenium-standalone": "^6.17.0",
|
"selenium-standalone": "^6.17.0",
|
||||||
"style-loader": "^1.1.3",
|
"style-loader": "^1.1.3",
|
||||||
"stylelint": "^13.2.0",
|
"stylelint": "^13.3.0",
|
||||||
"stylelint-config-recommended-scss": "^4.2.0",
|
"stylelint-config-recommended-scss": "^4.2.0",
|
||||||
"stylelint-scss": "^3.14.2",
|
"stylelint-scss": "^3.14.2",
|
||||||
"svg-inline-loader": "^0.8.2",
|
"svg-inline-loader": "^0.8.2",
|
||||||
"transform-loader": "^0.2.4",
|
"transform-loader": "^0.2.4",
|
||||||
"uuid": "^7.0.2",
|
"uuid": "^7.0.3",
|
||||||
"webdriverio": "^6.0.5",
|
"webdriverio": "^6.0.5",
|
||||||
"webpack": "^4.41.6",
|
"webpack": "^4.41.6",
|
||||||
"webpack-bundle-analyzer": "^3.6.0",
|
"webpack-bundle-analyzer": "^3.6.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import cloneDeep from 'lodash.clonedeep'
|
|||||||
import clamp from 'lodash.clamp'
|
import clamp from 'lodash.clamp'
|
||||||
import get from 'lodash.get'
|
import get from 'lodash.get'
|
||||||
import {unset} from 'lodash'
|
import {unset} from 'lodash'
|
||||||
import {arrayMove} from 'react-sortable-hoc'
|
import arrayMove from 'array-move'
|
||||||
import url from 'url'
|
import url from 'url'
|
||||||
|
|
||||||
import MapboxGlMap from './map/MapboxGlMap'
|
import MapboxGlMap from './map/MapboxGlMap'
|
||||||
@@ -36,6 +36,7 @@ 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 queryUtil from '../libs/query-util'
|
import queryUtil from '../libs/query-util'
|
||||||
|
import {formatLayerId} from './util/format';
|
||||||
|
|
||||||
import MapboxGl from 'mapbox-gl'
|
import MapboxGl from 'mapbox-gl'
|
||||||
|
|
||||||
@@ -325,7 +326,59 @@ export default class App extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const errors = validate(newStyle, latest) || [];
|
const errors = validate(newStyle, latest) || [];
|
||||||
const mappedErrors = errors.map(error => {
|
|
||||||
|
// The validate function doesn't give us errors for duplicate error with
|
||||||
|
// empty string for layer.id, manually deal with that here.
|
||||||
|
const layerErrors = [];
|
||||||
|
if (newStyle && newStyle.layers) {
|
||||||
|
const foundLayers = new Map();
|
||||||
|
newStyle.layers.forEach((layer, index) => {
|
||||||
|
if (layer.id === "" && foundLayers.has(layer.id)) {
|
||||||
|
const message = `Duplicate layer: ${formatLayerId(layer.id)}`;
|
||||||
|
const error = new Error(
|
||||||
|
`layers[${index}]: duplicate layer id [empty_string], previously used`
|
||||||
|
);
|
||||||
|
layerErrors.push(error);
|
||||||
|
}
|
||||||
|
foundLayers.set(layer.id, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedErrors = layerErrors.concat(errors).map(error => {
|
||||||
|
// Special case: Duplicate layer id
|
||||||
|
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
|
||||||
|
if (dupMatch) {
|
||||||
|
const [matchStr, index, message] = dupMatch;
|
||||||
|
return {
|
||||||
|
message: error.message,
|
||||||
|
parsed: {
|
||||||
|
type: "layer",
|
||||||
|
data: {
|
||||||
|
index: parseInt(index, 10),
|
||||||
|
key: "id",
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: Invalid source
|
||||||
|
const invalidSourceMatch = error.message.match(/layers\[(\d+)\]: (source "(?:.*)" not found)/);
|
||||||
|
if (invalidSourceMatch) {
|
||||||
|
const [matchStr, index, message] = invalidSourceMatch;
|
||||||
|
return {
|
||||||
|
message: error.message,
|
||||||
|
parsed: {
|
||||||
|
type: "layer",
|
||||||
|
data: {
|
||||||
|
index: parseInt(index, 10),
|
||||||
|
key: "source",
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -335,7 +388,7 @@ export default class App extends React.Component {
|
|||||||
parsed: {
|
parsed: {
|
||||||
type: "layer",
|
type: "layer",
|
||||||
data: {
|
data: {
|
||||||
index,
|
index: parseInt(index, 10),
|
||||||
key,
|
key,
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
@@ -347,7 +400,7 @@ export default class App extends React.Component {
|
|||||||
message: error.message,
|
message: error.message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
let dirtyMapStyle = undefined;
|
let dirtyMapStyle = undefined;
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
@@ -437,56 +490,50 @@ export default class App extends React.Component {
|
|||||||
this.onStyleChanged(changedStyle)
|
this.onStyleChanged(changedStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerDestroy = (layerId) => {
|
onLayerDestroy = (index) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
let layers = this.state.mapStyle.layers;
|
||||||
const remainingLayers = layers.slice(0);
|
const remainingLayers = layers.slice(0);
|
||||||
const idx = style.indexOfLayer(remainingLayers, layerId)
|
remainingLayers.splice(index, 1);
|
||||||
remainingLayers.splice(idx, 1);
|
|
||||||
this.onLayersChange(remainingLayers);
|
this.onLayersChange(remainingLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerCopy = (layerId) => {
|
onLayerCopy = (index) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
let layers = this.state.mapStyle.layers;
|
||||||
const changedLayers = layers.slice(0)
|
const changedLayers = layers.slice(0)
|
||||||
const idx = style.indexOfLayer(changedLayers, layerId)
|
|
||||||
|
|
||||||
const clonedLayer = cloneDeep(changedLayers[idx])
|
const clonedLayer = cloneDeep(changedLayers[index])
|
||||||
clonedLayer.id = clonedLayer.id + "-copy"
|
clonedLayer.id = clonedLayer.id + "-copy"
|
||||||
changedLayers.splice(idx, 0, clonedLayer)
|
changedLayers.splice(index, 0, clonedLayer)
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerVisibilityToggle = (layerId) => {
|
onLayerVisibilityToggle = (index) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
let layers = this.state.mapStyle.layers;
|
||||||
const changedLayers = layers.slice(0)
|
const changedLayers = layers.slice(0)
|
||||||
const idx = style.indexOfLayer(changedLayers, layerId)
|
|
||||||
|
|
||||||
const layer = { ...changedLayers[idx] }
|
const layer = { ...changedLayers[index] }
|
||||||
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
|
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
|
||||||
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
|
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
|
||||||
|
|
||||||
layer.layout = changedLayout
|
layer.layout = changedLayout
|
||||||
changedLayers[idx] = layer
|
changedLayers[index] = layer
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onLayerIdChange = (oldId, newId) => {
|
onLayerIdChange = (index, oldId, newId) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||||
const idx = style.indexOfLayer(changedLayers, oldId)
|
changedLayers[index] = {
|
||||||
|
...changedLayers[index],
|
||||||
changedLayers[idx] = {
|
|
||||||
...changedLayers[idx],
|
|
||||||
id: newId
|
id: newId
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerChanged = (layer) => {
|
onLayerChanged = (index, layer) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||||
const idx = style.indexOfLayer(changedLayers, layer.id)
|
changedLayers[index] = layer
|
||||||
changedLayers[idx] = layer
|
|
||||||
|
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
@@ -645,9 +692,8 @@ export default class App extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerSelect = (layerId) => {
|
onLayerSelect = (index) => {
|
||||||
const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
|
this.setState({ selectedLayerIndex: index })
|
||||||
this.setState({ selectedLayerIndex: idx })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setModal(modalName, value) {
|
setModal(modalName, value) {
|
||||||
@@ -735,6 +781,7 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
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}
|
||||||
|
selectedLayerIndex={this.state.selectedLayerIndex}
|
||||||
onLayerSelect={this.onLayerSelect}
|
onLayerSelect={this.onLayerSelect}
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
errors={this.state.errors}
|
errors={this.state.errors}
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ class AppLayout extends React.Component {
|
|||||||
return <div className="maputnik-layout">
|
return <div className="maputnik-layout">
|
||||||
{this.props.toolbar}
|
{this.props.toolbar}
|
||||||
<div className="maputnik-layout-list">
|
<div className="maputnik-layout-list">
|
||||||
<ScrollContainer>
|
{this.props.layerList}
|
||||||
{this.props.layerList}
|
|
||||||
</ScrollContainer>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-layout-drawer">
|
<div className="maputnik-layout-drawer">
|
||||||
<ScrollContainer>
|
<ScrollContainer>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import {formatLayerId} from './util/format';
|
||||||
|
|
||||||
class MessagePanel extends React.Component {
|
class MessagePanel extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -8,6 +9,7 @@ class MessagePanel extends React.Component {
|
|||||||
mapStyle: PropTypes.object,
|
mapStyle: PropTypes.object,
|
||||||
onLayerSelect: PropTypes.func,
|
onLayerSelect: PropTypes.func,
|
||||||
currentLayer: PropTypes.object,
|
currentLayer: PropTypes.object,
|
||||||
|
selectedLayerIndex: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -15,6 +17,7 @@ class MessagePanel extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {selectedLayerIndex} = this.props;
|
||||||
const errors = this.props.errors.map((error, idx) => {
|
const errors = this.props.errors.map((error, idx) => {
|
||||||
let content;
|
let content;
|
||||||
if (error.parsed && error.parsed.type === "layer") {
|
if (error.parsed && error.parsed.type === "layer") {
|
||||||
@@ -23,13 +26,13 @@ class MessagePanel extends React.Component {
|
|||||||
const layerId = mapStyle.layers[parsed.data.index].id;
|
const layerId = mapStyle.layers[parsed.data.index].id;
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
Layer <span>'{layerId}'</span>: {parsed.data.message}
|
Layer <span>{formatLayerId(layerId)}</span>: {parsed.data.message}
|
||||||
{currentLayer.id !== layerId &&
|
{selectedLayerIndex !== parsed.data.index &&
|
||||||
<>
|
<>
|
||||||
—
|
—
|
||||||
<button
|
<button
|
||||||
className="maputnik-message-panel__switch-button"
|
className="maputnik-message-panel__switch-button"
|
||||||
onClick={() => this.props.onLayerSelect(layerId)}
|
onClick={() => this.props.onLayerSelect(parsed.data.index)}
|
||||||
>
|
>
|
||||||
switch to layer
|
switch to layer
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export default class DocLabel extends React.Component {
|
|||||||
PropTypes.object,
|
PropTypes.object,
|
||||||
PropTypes.string
|
PropTypes.string
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
fieldSpec: PropTypes.object.isRequired,
|
fieldSpec: PropTypes.object,
|
||||||
onToggleDoc: PropTypes.func.isRequired,
|
onToggleDoc: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
|||||||
@@ -6,12 +6,21 @@ import DataProperty from './_DataProperty'
|
|||||||
import ZoomProperty from './_ZoomProperty'
|
import ZoomProperty from './_ZoomProperty'
|
||||||
import ExpressionProperty from './_ExpressionProperty'
|
import ExpressionProperty from './_ExpressionProperty'
|
||||||
import {function as styleFunction} from '@mapbox/mapbox-gl-style-spec';
|
import {function as styleFunction} from '@mapbox/mapbox-gl-style-spec';
|
||||||
|
import {findDefaultFromSpec} from '../util/spec-helper';
|
||||||
|
|
||||||
|
|
||||||
function isLiteralExpression (value) {
|
function isLiteralExpression (value) {
|
||||||
return (Array.isArray(value) && value.length === 2 && value[0] === "literal");
|
return (Array.isArray(value) && value.length === 2 && value[0] === "literal");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGetExpression (value) {
|
||||||
|
return (
|
||||||
|
Array.isArray(value) &&
|
||||||
|
value.length === 2 &&
|
||||||
|
value[0] === "get"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function isZoomField(value) {
|
function isZoomField(value) {
|
||||||
return (
|
return (
|
||||||
typeof(value) === 'object' &&
|
typeof(value) === 'object' &&
|
||||||
@@ -28,7 +37,15 @@ function isZoomField(value) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDataField(value) {
|
function isIdentityProperty (value) {
|
||||||
|
return (
|
||||||
|
typeof(value) === 'object' &&
|
||||||
|
value.type === "identity" &&
|
||||||
|
value.hasOwnProperty("property")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDataStopProperty (value) {
|
||||||
return (
|
return (
|
||||||
typeof(value) === 'object' &&
|
typeof(value) === 'object' &&
|
||||||
value.stops &&
|
value.stops &&
|
||||||
@@ -45,6 +62,13 @@ function isDataField(value) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDataField(value) {
|
||||||
|
return (
|
||||||
|
isIdentityProperty(value) ||
|
||||||
|
isDataStopProperty(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function isPrimative (value) {
|
function isPrimative (value) {
|
||||||
const valid = ["string", "boolean", "number"];
|
const valid = ["string", "boolean", "number"];
|
||||||
return valid.includes(typeof(value));
|
return valid.includes(typeof(value));
|
||||||
@@ -78,24 +102,6 @@ function getDataType (value, fieldSpec={}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If we don't have a default value just make one up
|
|
||||||
*/
|
|
||||||
function findDefaultFromSpec (spec) {
|
|
||||||
if (spec.hasOwnProperty('default')) {
|
|
||||||
return spec.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaults = {
|
|
||||||
'color': '#000000',
|
|
||||||
'string': '',
|
|
||||||
'boolean': false,
|
|
||||||
'number': 0,
|
|
||||||
'array': [],
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaults[spec.type] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Supports displaying spec field for zoom function objects
|
/** Supports displaying spec field for zoom function objects
|
||||||
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
||||||
@@ -206,7 +212,16 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
undoExpression = () => {
|
undoExpression = () => {
|
||||||
const {value, fieldName} = this.props;
|
const {value, fieldName} = this.props;
|
||||||
|
|
||||||
if (isLiteralExpression(value)) {
|
if (isGetExpression(value)) {
|
||||||
|
this.props.onChange(fieldName, {
|
||||||
|
"type": "identity",
|
||||||
|
"property": value[1]
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
dataType: "value",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (isLiteralExpression(value)) {
|
||||||
this.props.onChange(fieldName, value[1]);
|
this.props.onChange(fieldName, value[1]);
|
||||||
this.setState({
|
this.setState({
|
||||||
dataType: "value",
|
dataType: "value",
|
||||||
@@ -217,6 +232,7 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
canUndo = () => {
|
canUndo = () => {
|
||||||
const {value, fieldSpec} = this.props;
|
const {value, fieldSpec} = this.props;
|
||||||
return (
|
return (
|
||||||
|
isGetExpression(value) ||
|
||||||
isLiteralExpression(value) ||
|
isLiteralExpression(value) ||
|
||||||
isPrimative(value) ||
|
isPrimative(value) ||
|
||||||
(Array.isArray(value) && fieldSpec.type === "array")
|
(Array.isArray(value) && fieldSpec.type === "array")
|
||||||
@@ -230,6 +246,9 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
if (typeof(value) === "object" && 'stops' in value) {
|
if (typeof(value) === "object" && 'stops' in value) {
|
||||||
expression = styleFunction.convertFunction(value, fieldSpec);
|
expression = styleFunction.convertFunction(value, fieldSpec);
|
||||||
}
|
}
|
||||||
|
else if (isIdentityProperty(value)) {
|
||||||
|
expression = ["get", value.property];
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
expression = ["literal", value || this.props.fieldSpec.default];
|
expression = ["literal", value || this.props.fieldSpec.default];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
||||||
|
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import SpecField from './SpecField'
|
import SpecField from './SpecField'
|
||||||
@@ -10,6 +11,7 @@ import DocLabel from './DocLabel'
|
|||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import docUid from '../../libs/document-uid'
|
import docUid from '../../libs/document-uid'
|
||||||
import sortNumerically from '../../libs/sort-numerically'
|
import sortNumerically from '../../libs/sort-numerically'
|
||||||
|
import {findDefaultFromSpec} from '../util/spec-helper';
|
||||||
|
|
||||||
import labelFromFieldName from './_labelFromFieldName'
|
import labelFromFieldName from './_labelFromFieldName'
|
||||||
import DeleteStopButton from './_DeleteStopButton'
|
import DeleteStopButton from './_DeleteStopButton'
|
||||||
@@ -89,10 +91,10 @@ export default class DataProperty extends React.Component {
|
|||||||
|
|
||||||
getDataFunctionTypes(fieldSpec) {
|
getDataFunctionTypes(fieldSpec) {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return ["categorical", "interval", "exponential"]
|
return ["categorical", "interval", "exponential", "identity"]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return ["categorical", "interval"]
|
return ["categorical", "interval", "identity"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +124,29 @@ export default class DataProperty extends React.Component {
|
|||||||
return mappedWithRef.map((item) => item.data);
|
return mappedWithRef.map((item) => item.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange = (fieldName, value) => {
|
||||||
|
if (value.type === "identity") {
|
||||||
|
value = {
|
||||||
|
type: value.type,
|
||||||
|
property: value.property,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const stopValue = value.type === 'categorical' ? '' : 0;
|
||||||
|
value = {
|
||||||
|
property: "",
|
||||||
|
type: value.type,
|
||||||
|
// Default props if they don't already exist.
|
||||||
|
stops: [
|
||||||
|
[{zoom: 6, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec)],
|
||||||
|
[{zoom: 10, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec)]
|
||||||
|
],
|
||||||
|
...value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.props.onChange(fieldName, value);
|
||||||
|
}
|
||||||
|
|
||||||
changeStop(changeIdx, stopData, value) {
|
changeStop(changeIdx, stopData, value) {
|
||||||
const stops = this.props.value.stops.slice(0)
|
const stops = this.props.value.stops.slice(0)
|
||||||
const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
||||||
@@ -133,7 +158,7 @@ export default class DataProperty extends React.Component {
|
|||||||
...this.props.value,
|
...this.props.value,
|
||||||
stops: orderedStops,
|
stops: orderedStops,
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, changedValue)
|
this.onChange(this.props.fieldName, changedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeDataProperty(propName, propVal) {
|
changeDataProperty(propName, propVal) {
|
||||||
@@ -143,7 +168,7 @@ export default class DataProperty extends React.Component {
|
|||||||
else {
|
else {
|
||||||
delete this.props.value[propName]
|
delete this.props.value[propName]
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, this.props.value)
|
this.onChange(this.props.fieldName, this.props.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -153,69 +178,72 @@ export default class DataProperty extends React.Component {
|
|||||||
this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec)
|
this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataFields = this.props.value.stops.map((stop, idx) => {
|
let dataFields;
|
||||||
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
|
if (this.props.value.stops) {
|
||||||
const key = this.state.refs[idx];
|
dataFields = this.props.value.stops.map((stop, idx) => {
|
||||||
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
|
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
|
||||||
const value = stop[1]
|
const key = this.state.refs[idx];
|
||||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
|
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
|
||||||
|
const value = stop[1]
|
||||||
|
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
|
||||||
|
|
||||||
const dataProps = {
|
const dataProps = {
|
||||||
label: "Data value",
|
label: "Data value",
|
||||||
value: dataLevel,
|
value: dataLevel,
|
||||||
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
|
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataInput;
|
let dataInput;
|
||||||
if(this.props.value.type === "categorical") {
|
if(this.props.value.type === "categorical") {
|
||||||
dataInput = <StringInput {...dataProps} />
|
dataInput = <StringInput {...dataProps} />
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dataInput = <NumberInput {...dataProps} />
|
dataInput = <NumberInput {...dataProps} />
|
||||||
}
|
}
|
||||||
|
|
||||||
let zoomInput = null;
|
let zoomInput = null;
|
||||||
if(zoomLevel !== undefined) {
|
if(zoomLevel !== undefined) {
|
||||||
zoomInput = <div className="maputnik-data-spec-property-stop-edit">
|
zoomInput = <div className="maputnik-data-spec-property-stop-edit">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={zoomLevel}
|
value={zoomLevel}
|
||||||
onChange={newZoom => this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
|
onChange={newZoom => this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
|
||||||
min={0}
|
min={0}
|
||||||
max={22}
|
max={22}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
|
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
|
||||||
const foundErrors = Object.entries(errors).filter(([key, error]) => {
|
const foundErrors = Object.entries(errors).filter(([key, error]) => {
|
||||||
return key.startsWith(errorKeyStart);
|
return key.startsWith(errorKeyStart);
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = foundErrors.map(([key, error]) => {
|
const message = foundErrors.map(([key, error]) => {
|
||||||
return error.message;
|
return error.message;
|
||||||
}).join("");
|
}).join("");
|
||||||
const error = message ? {message} : undefined;
|
const error = message ? {message} : undefined;
|
||||||
|
|
||||||
return <InputBlock
|
return <InputBlock
|
||||||
error={error}
|
error={error}
|
||||||
key={key}
|
key={key}
|
||||||
action={deleteStopBtn}
|
action={deleteStopBtn}
|
||||||
label=""
|
label=""
|
||||||
>
|
>
|
||||||
{zoomInput}
|
{zoomInput}
|
||||||
<div className="maputnik-data-spec-property-stop-data">
|
<div className="maputnik-data-spec-property-stop-data">
|
||||||
{dataInput}
|
{dataInput}
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-data-spec-property-stop-value">
|
<div className="maputnik-data-spec-property-stop-value">
|
||||||
<SpecField
|
<SpecField
|
||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
|
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="maputnik-data-spec-block">
|
return <div className="maputnik-data-spec-block">
|
||||||
<div className="maputnik-data-spec-property">
|
<div className="maputnik-data-spec-property">
|
||||||
@@ -223,18 +251,6 @@ export default class DataProperty extends React.Component {
|
|||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
label={labelFromFieldName(this.props.fieldName)}
|
label={labelFromFieldName(this.props.fieldName)}
|
||||||
>
|
>
|
||||||
<div className="maputnik-data-spec-property-group">
|
|
||||||
<DocLabel
|
|
||||||
label="Property"
|
|
||||||
/>
|
|
||||||
<div className="maputnik-data-spec-property-input">
|
|
||||||
<StringInput
|
|
||||||
value={this.props.value.property}
|
|
||||||
title={"Input a data property to base styles off of."}
|
|
||||||
onChange={propVal => this.changeDataProperty("property", propVal)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="maputnik-data-spec-property-group">
|
<div className="maputnik-data-spec-property-group">
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label="Type"
|
label="Type"
|
||||||
@@ -250,31 +266,53 @@ export default class DataProperty extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="maputnik-data-spec-property-group">
|
<div className="maputnik-data-spec-property-group">
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label="Default"
|
label="Property"
|
||||||
/>
|
/>
|
||||||
<div className="maputnik-data-spec-property-input">
|
<div className="maputnik-data-spec-property-input">
|
||||||
<SpecField
|
<StringInput
|
||||||
fieldName={this.props.fieldName}
|
value={this.props.value.property}
|
||||||
fieldSpec={this.props.fieldSpec}
|
title={"Input a data property to base styles off of."}
|
||||||
value={this.props.value.default}
|
onChange={propVal => this.changeDataProperty("property", propVal)}
|
||||||
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{dataFields &&
|
||||||
|
<div className="maputnik-data-spec-property-group">
|
||||||
|
<DocLabel
|
||||||
|
label="Default"
|
||||||
|
/>
|
||||||
|
<div className="maputnik-data-spec-property-input">
|
||||||
|
<SpecField
|
||||||
|
fieldName={this.props.fieldName}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
value={this.props.value.default}
|
||||||
|
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
</div>
|
</div>
|
||||||
{dataFields}
|
{dataFields &&
|
||||||
<Button
|
<>
|
||||||
className="maputnik-add-stop"
|
{dataFields}
|
||||||
onClick={this.props.onAddStop.bind(this)}
|
<Button
|
||||||
>
|
className="maputnik-add-stop"
|
||||||
Add stop
|
onClick={this.props.onAddStop.bind(this)}
|
||||||
</Button>
|
>
|
||||||
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
||||||
|
</svg> Add stop
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-add-stop"
|
className="maputnik-add-stop"
|
||||||
onClick={this.props.onExpressionClick.bind(this)}
|
onClick={this.props.onExpressionClick.bind(this)}
|
||||||
>
|
>
|
||||||
Convert to expression
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
|
</svg> Convert to expression
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
||||||
|
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import SpecField from './SpecField'
|
import SpecField from './SpecField'
|
||||||
@@ -176,13 +177,17 @@ export default class ZoomProperty extends React.Component {
|
|||||||
className="maputnik-add-stop"
|
className="maputnik-add-stop"
|
||||||
onClick={this.props.onAddStop.bind(this)}
|
onClick={this.props.onAddStop.bind(this)}
|
||||||
>
|
>
|
||||||
Add stop
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
||||||
|
</svg> Add stop
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-add-stop"
|
className="maputnik-add-stop"
|
||||||
onClick={this.props.onExpressionClick.bind(this)}
|
onClick={this.props.onExpressionClick.bind(this)}
|
||||||
>
|
>
|
||||||
Convert to expression
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d={mdiFunctionVariant} />
|
||||||
|
</svg> Convert to expression
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { combiningFilterOps } from '../../libs/filterops.js'
|
import { combiningFilterOps } from '../../libs/filterops.js'
|
||||||
|
import {mdiTableRowPlusAfter} from '@mdi/js';
|
||||||
|
|
||||||
import {latest, validate, migrate} from '@mapbox/mapbox-gl-style-spec'
|
import {latest, validate, migrate} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import DocLabel from '../fields/DocLabel'
|
import DocLabel from '../fields/DocLabel'
|
||||||
@@ -223,7 +224,7 @@ export default class CombiningFilterEditor extends React.Component {
|
|||||||
const error = errors[`filter[${idx+1}]`];
|
const error = errors[`filter[${idx+1}]`];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div key={`block-${idx}`}>
|
||||||
<FilterEditorBlock key={idx} onDelete={this.deleteFilterItem.bind(this, idx)}>
|
<FilterEditorBlock key={idx} onDelete={this.deleteFilterItem.bind(this, idx)}>
|
||||||
<SingleFilterEditor
|
<SingleFilterEditor
|
||||||
properties={this.props.properties}
|
properties={this.props.properties}
|
||||||
@@ -232,9 +233,9 @@ export default class CombiningFilterEditor extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</FilterEditorBlock>
|
</FilterEditorBlock>
|
||||||
{error &&
|
{error &&
|
||||||
<div className="maputnik-inline-error">{error.message}</div>
|
<div key="error" className="maputnik-inline-error">{error.message}</div>
|
||||||
}
|
}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -261,8 +262,11 @@ export default class CombiningFilterEditor extends React.Component {
|
|||||||
<Button
|
<Button
|
||||||
data-wd-key="layer-filter-button"
|
data-wd-key="layer-filter-button"
|
||||||
className="maputnik-add-filter"
|
className="maputnik-add-filter"
|
||||||
onClick={this.addFilterItem}>
|
onClick={this.addFilterItem}
|
||||||
Add filter
|
>
|
||||||
|
<svg style={{width:"14px", height:"14px", verticalAlign: "text-bottom"}} viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d={mdiTableRowPlusAfter} />
|
||||||
|
</svg> Add filter
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import AutocompleteInput from './AutocompleteInput'
|
|||||||
|
|
||||||
class FontInput extends React.Component {
|
class FontInput extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
value: PropTypes.array.isRequired,
|
value: PropTypes.array,
|
||||||
default: PropTypes.array,
|
default: PropTypes.array,
|
||||||
fonts: PropTypes.array,
|
fonts: PropTypes.array,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
@@ -16,7 +16,7 @@ class FontInput extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get values() {
|
get values() {
|
||||||
const out = this.props.value || this.props.default.slice(1) || [""];
|
const out = this.props.value || this.props.default || [];
|
||||||
|
|
||||||
// Always put a "" in the last field to you can keep adding entries
|
// Always put a "" in the last field to you can keep adding entries
|
||||||
if (out[out.length-1] !== ""){
|
if (out[out.length-1] !== ""){
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class NumberInput extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
if (!state.editing) {
|
if (!state.editing && props.value !== state.value) {
|
||||||
return {
|
return {
|
||||||
value: props.value,
|
value: props.value,
|
||||||
dirtyValue: props.value,
|
dirtyValue: props.value,
|
||||||
@@ -49,12 +49,17 @@ class NumberInput extends React.Component {
|
|||||||
if(this.isValid(value) && hasChanged) {
|
if(this.isValid(value) && hasChanged) {
|
||||||
this.props.onChange(value)
|
this.props.onChange(value)
|
||||||
this.setState({
|
this.setState({
|
||||||
dirtyValue: newValue,
|
value: newValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (!this.isValid(value) && hasChanged) {
|
||||||
|
this.setState({
|
||||||
|
value: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
value: newValue,
|
dirtyValue: newValue === "" ? undefined : newValue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +92,13 @@ class NumberInput extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If set value is invalid fall back to the last valid value from props or at last resort the default value
|
// If set value is invalid fall back to the last valid value from props or at last resort the default value
|
||||||
if(!this.isValid(this.state.value)) {
|
if (!this.isValid(this.state.value)) {
|
||||||
if(this.isValid(this.props.value)) {
|
if(this.isValid(this.props.value)) {
|
||||||
this.changeValue(this.props.value)
|
this.changeValue(this.props.value)
|
||||||
|
this.setState({dirtyValue: this.props.value});
|
||||||
} else {
|
} else {
|
||||||
this.changeValue(undefined);
|
this.changeValue(undefined);
|
||||||
|
this.setState({dirtyValue: undefined});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,8 +151,15 @@ class NumberInput extends React.Component {
|
|||||||
this.props.min !== undefined && this.props.max !== undefined &&
|
this.props.min !== undefined && this.props.max !== undefined &&
|
||||||
this.props.allowRange
|
this.props.allowRange
|
||||||
) {
|
) {
|
||||||
const dirtyValue = this.state.dirtyValue === undefined ? this.props.default : this.state.dirtyValue
|
const value = this.state.editing ? this.state.dirtyValue : this.state.value;
|
||||||
const value = this.state.value === undefined ? "" : this.state.value;
|
const defaultValue = this.props.default === undefined ? "" : this.props.default;
|
||||||
|
let inputValue;
|
||||||
|
if (this.state.editingRange) {
|
||||||
|
inputValue = this.state.value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inputValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="maputnik-number-container">
|
return <div className="maputnik-number-container">
|
||||||
<input
|
<input
|
||||||
@@ -156,21 +170,25 @@ class NumberInput extends React.Component {
|
|||||||
min={this.props.min}
|
min={this.props.min}
|
||||||
step="any"
|
step="any"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
value={dirtyValue}
|
value={value === undefined ? defaultValue : value}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
onChange={this.onChangeRange}
|
onChange={this.onChangeRange}
|
||||||
onKeyDown={() => {
|
onKeyDown={() => {
|
||||||
this._keyboardEvent = true;
|
this._keyboardEvent = true;
|
||||||
}}
|
}}
|
||||||
onPointerDown={() => {
|
onPointerDown={() => {
|
||||||
this.setState({editing: true});
|
this.setState({editing: true, editingRange: true});
|
||||||
}}
|
}}
|
||||||
onPointerUp={() => {
|
onPointerUp={() => {
|
||||||
// Safari doesn't get onBlur event
|
// Safari doesn't get onBlur event
|
||||||
this.setState({editing: false});
|
this.setState({editing: false, editingRange: false});
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
this.setState({editing: false});
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
editingRange: false,
|
||||||
|
dirtyValue: this.state.value,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@@ -179,25 +197,32 @@ class NumberInput extends React.Component {
|
|||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="maputnik-number"
|
className="maputnik-number"
|
||||||
placeholder={this.props.default}
|
placeholder={this.props.default}
|
||||||
value={value}
|
value={inputValue === undefined ? "" : inputValue}
|
||||||
onChange={e => {
|
onFocus={e => {
|
||||||
if (!this.state.editing) {
|
this.setState({editing: true});
|
||||||
this.changeValue(e.target.value);
|
}}
|
||||||
}
|
onChange={e => {
|
||||||
|
this.changeValue(e.target.value);
|
||||||
|
}}
|
||||||
|
onBlur={e => {
|
||||||
|
this.setState({editing: false});
|
||||||
|
this.resetValue()
|
||||||
}}
|
}}
|
||||||
onBlur={this.resetValue}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const value = this.state.value === undefined ? "" : this.state.value;
|
const value = this.state.editing ? this.state.dirtyValue : this.state.value;
|
||||||
|
|
||||||
return <input
|
return <input
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="maputnik-number"
|
className="maputnik-number"
|
||||||
placeholder={this.props.default}
|
placeholder={this.props.default}
|
||||||
value={value}
|
value={value === undefined ? "" : value}
|
||||||
onChange={e => this.changeValue(e.target.value)}
|
onChange={e => this.changeValue(e.target.value)}
|
||||||
|
onFocus={() => {
|
||||||
|
this.setState({editing: true});
|
||||||
|
}}
|
||||||
onBlur={this.resetValue}
|
onBlur={this.resetValue}
|
||||||
required={this.props.required}
|
required={this.props.required}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class StringInput extends React.Component {
|
|||||||
value: props.value
|
value: props.value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -79,6 +80,11 @@ class StringInput extends React.Component {
|
|||||||
this.props.onChange(this.state.value);
|
this.props.onChange(this.state.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onKeyDown: (e) => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.props.onChange(this.state.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
required: this.props.required,
|
required: this.props.required,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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';
|
||||||
|
|
||||||
|
|
||||||
function getLayoutForType (type) {
|
function getLayoutForType (type) {
|
||||||
@@ -108,7 +109,10 @@ export default class LayerEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
changeProperty(group, property, newValue) {
|
changeProperty(group, property, newValue) {
|
||||||
this.props.onLayerChanged(changeProperty(this.props.layer, group, property, newValue))
|
this.props.onLayerChanged(
|
||||||
|
this.props.layerIndex,
|
||||||
|
changeProperty(this.props.layer, group, property, newValue)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onGroupToggle(groupTitle, active) {
|
onGroupToggle(groupTitle, active) {
|
||||||
@@ -152,16 +156,19 @@ export default class LayerEditor extends React.Component {
|
|||||||
value={this.props.layer.id}
|
value={this.props.layer.id}
|
||||||
wdKey="layer-editor.layer-id"
|
wdKey="layer-editor.layer-id"
|
||||||
error={errorData.id}
|
error={errorData.id}
|
||||||
onChange={newId => this.props.onLayerIdChange(this.props.layer.id, newId)}
|
onChange={newId => this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)}
|
||||||
/>
|
/>
|
||||||
<LayerTypeBlock
|
<LayerTypeBlock
|
||||||
disabled={true}
|
disabled={true}
|
||||||
error={errorData.type}
|
error={errorData.type}
|
||||||
value={this.props.layer.type}
|
value={this.props.layer.type}
|
||||||
onChange={newType => this.props.onLayerChanged(changeType(this.props.layer, newType))}
|
onChange={newType => this.props.onLayerChanged(
|
||||||
|
this.props.layerIndex,
|
||||||
|
changeType(this.props.layer, newType)
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{this.props.layer.type !== 'background' && <LayerSourceBlock
|
{this.props.layer.type !== 'background' && <LayerSourceBlock
|
||||||
error={errorData.sources}
|
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)}
|
||||||
@@ -210,7 +217,12 @@ export default class LayerEditor extends React.Component {
|
|||||||
/>
|
/>
|
||||||
case 'jsoneditor': return <JSONEditor
|
case 'jsoneditor': return <JSONEditor
|
||||||
layer={this.props.layer}
|
layer={this.props.layer}
|
||||||
onChange={this.props.onLayerChanged}
|
onChange={(layer) => {
|
||||||
|
this.props.onLayerChanged(
|
||||||
|
this.props.layerIndex,
|
||||||
|
layer
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,15 +259,15 @@ export default class LayerEditor extends React.Component {
|
|||||||
const items = {
|
const items = {
|
||||||
delete: {
|
delete: {
|
||||||
text: "Delete",
|
text: "Delete",
|
||||||
handler: () => this.props.onLayerDestroy(this.props.layer.id)
|
handler: () => this.props.onLayerDestroy(this.props.layerIndex)
|
||||||
},
|
},
|
||||||
duplicate: {
|
duplicate: {
|
||||||
text: "Duplicate",
|
text: "Duplicate",
|
||||||
handler: () => this.props.onLayerCopy(this.props.layer.id)
|
handler: () => this.props.onLayerCopy(this.props.layerIndex)
|
||||||
},
|
},
|
||||||
hide: {
|
hide: {
|
||||||
text: (layout.visibility === "none") ? "Show" : "Hide",
|
text: (layout.visibility === "none") ? "Show" : "Hide",
|
||||||
handler: () => this.props.onLayerVisibilityToggle(this.props.layer.id)
|
handler: () => this.props.onLayerVisibilityToggle(this.props.layerIndex)
|
||||||
},
|
},
|
||||||
moveLayerUp: {
|
moveLayerUp: {
|
||||||
text: "Move layer up",
|
text: "Move layer up",
|
||||||
@@ -281,7 +293,7 @@ export default class LayerEditor extends React.Component {
|
|||||||
<header>
|
<header>
|
||||||
<div className="layer-header">
|
<div className="layer-header">
|
||||||
<h2 className="layer-header__title">
|
<h2 className="layer-header__title">
|
||||||
Layer: {this.props.layer.id}
|
Layer: {formatLayerId(this.props.layer.id)}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="layer-header__info">
|
<div className="layer-header__info">
|
||||||
<Wrapper
|
<Wrapper
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ class LayerIdBlock extends React.Component {
|
|||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
wdKey: PropTypes.string.isRequired,
|
wdKey: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"ID"} fieldSpec={latest.layer.id}
|
return <InputBlock label={"ID"} fieldSpec={latest.layer.id}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
|
error={this.props.error}
|
||||||
>
|
>
|
||||||
<StringInput
|
<StringInput
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
|
|||||||
@@ -42,11 +42,16 @@ class LayerListContainer extends React.Component {
|
|||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
constructor(props) {
|
||||||
collapsedGroups: {},
|
super(props);
|
||||||
areAllGroupsExpanded: false,
|
this.selectedItemRef = React.createRef();
|
||||||
isOpen: {
|
this.scrollContainerRef = React.createRef();
|
||||||
add: false,
|
this.state = {
|
||||||
|
collapsedGroups: {},
|
||||||
|
areAllGroupsExpanded: false,
|
||||||
|
isOpen: {
|
||||||
|
add: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,16 +166,40 @@ class LayerListContainer extends React.Component {
|
|||||||
return propsChanged;
|
return propsChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) {
|
||||||
|
const selectedItemNode = this.selectedItemRef.current;
|
||||||
|
if (selectedItemNode && selectedItemNode.node) {
|
||||||
|
const target = selectedItemNode.node;
|
||||||
|
const options = {
|
||||||
|
root: this.scrollContainerRef.current,
|
||||||
|
threshold: 1.0
|
||||||
|
}
|
||||||
|
const observer = new IntersectionObserver(entries => {
|
||||||
|
observer.unobserve(target);
|
||||||
|
if (entries.length > 0 && entries[0].intersectionRatio < 1) {
|
||||||
|
target.scrollIntoView();
|
||||||
|
}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
observer.observe(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const listItems = []
|
const listItems = []
|
||||||
let idx = 0
|
let idx = 0
|
||||||
this.groupedLayers().forEach(layers => {
|
const layerIdCount = new Map();
|
||||||
|
|
||||||
|
const layersByGroup = this.groupedLayers();
|
||||||
|
layersByGroup.forEach(layers => {
|
||||||
const groupPrefix = layerPrefix(layers[0].id)
|
const groupPrefix = layerPrefix(layers[0].id)
|
||||||
if(layers.length > 1) {
|
if(layers.length > 1) {
|
||||||
const grp = <LayerListGroup
|
const grp = <LayerListGroup
|
||||||
data-wd-key={[groupPrefix, idx].join('-')}
|
data-wd-key={[groupPrefix, idx].join('-')}
|
||||||
key={[groupPrefix, idx].join('-')}
|
key={`group-${groupPrefix}-${idx}`}
|
||||||
title={groupPrefix}
|
title={groupPrefix}
|
||||||
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
||||||
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
||||||
@@ -189,6 +218,15 @@ class LayerListContainer extends React.Component {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const additionalProps = {};
|
||||||
|
if (idx === this.props.selectedLayerIndex) {
|
||||||
|
additionalProps.ref = this.selectedItemRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
layerIdCount.set(layer.id,
|
||||||
|
layerIdCount.has(layer.id) ? layerIdCount.get(layer.id) + 1 : 0
|
||||||
|
);
|
||||||
|
const key = `${layer.id}-${layerIdCount.get(layer.id)}`;
|
||||||
const listItem = <LayerListItem
|
const listItem = <LayerListItem
|
||||||
className={classnames({
|
className={classnames({
|
||||||
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
||||||
@@ -196,8 +234,9 @@ class LayerListContainer extends React.Component {
|
|||||||
'maputnik-layer-list-item--error': !!layerError
|
'maputnik-layer-list-item--error': !!layerError
|
||||||
})}
|
})}
|
||||||
index={idx}
|
index={idx}
|
||||||
key={layer.id}
|
key={key}
|
||||||
layerId={layer.id}
|
layerId={layer.id}
|
||||||
|
layerIndex={idx}
|
||||||
layerType={layer.type}
|
layerType={layer.type}
|
||||||
visibility={(layer.layout || {}).visibility}
|
visibility={(layer.layout || {}).visibility}
|
||||||
isSelected={idx === this.props.selectedLayerIndex}
|
isSelected={idx === this.props.selectedLayerIndex}
|
||||||
@@ -205,13 +244,14 @@ class LayerListContainer extends React.Component {
|
|||||||
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}
|
||||||
/>
|
/>
|
||||||
listItems.push(listItem)
|
listItems.push(listItem)
|
||||||
idx += 1
|
idx += 1
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return <div className="maputnik-layer-list">
|
return <div className="maputnik-layer-list" ref={this.scrollContainerRef}>
|
||||||
<AddModal
|
<AddModal
|
||||||
layers={this.props.layers}
|
layers={this.props.layers}
|
||||||
sources={this.props.sources}
|
sources={this.props.sources}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class IconAction extends React.Component {
|
|||||||
|
|
||||||
class LayerListItem extends React.Component {
|
class LayerListItem extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
layerIndex: PropTypes.number.isRequired,
|
||||||
layerId: PropTypes.string.isRequired,
|
layerId: PropTypes.string.isRequired,
|
||||||
layerType: PropTypes.string.isRequired,
|
layerType: PropTypes.string.isRequired,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
@@ -97,7 +98,7 @@ class LayerListItem extends React.Component {
|
|||||||
|
|
||||||
return <li
|
return <li
|
||||||
key={this.props.layerId}
|
key={this.props.layerId}
|
||||||
onClick={e => this.props.onLayerSelect(this.props.layerId)}
|
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,
|
||||||
@@ -110,20 +111,20 @@ 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.layerId)}
|
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.layerId)}
|
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.layerId)}
|
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerIndex)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class LayerSourceBlock extends React.Component {
|
|||||||
wdKey: PropTypes.string,
|
wdKey: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
sourceIds: PropTypes.array,
|
sourceIds: PropTypes.array,
|
||||||
|
error: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -19,7 +20,10 @@ class LayerSourceBlock extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"Source"} fieldSpec={latest.layer.source}
|
return <InputBlock
|
||||||
|
label={"Source"}
|
||||||
|
fieldSpec={latest.layer.source}
|
||||||
|
error={this.props.error}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
>
|
>
|
||||||
<AutocompleteInput
|
<AutocompleteInput
|
||||||
|
|||||||
@@ -58,23 +58,8 @@ class FeatureLayerPopup extends React.Component {
|
|||||||
|
|
||||||
if(propName) {
|
if(propName) {
|
||||||
const propertySpec = latest["paint_"+feature.layer.type][propName];
|
const propertySpec = latest["paint_"+feature.layer.type][propName];
|
||||||
|
|
||||||
let color = feature.layer.paint[propName];
|
let color = feature.layer.paint[propName];
|
||||||
|
return String(color);
|
||||||
if(typeof(color) === "object") {
|
|
||||||
if(color.stops) {
|
|
||||||
color = styleFunction.convertFunction(color, propertySpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
const exprResult = expression.createExpression(color, propertySpec);
|
|
||||||
const val = exprResult.value.evaluate({
|
|
||||||
zoom: zoom
|
|
||||||
}, feature);
|
|
||||||
return val.toString();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Default color
|
// Default color
|
||||||
@@ -84,7 +69,7 @@ class FeatureLayerPopup extends React.Component {
|
|||||||
// This is quite complex, just incase there's an edgecase we're missing
|
// This is quite complex, just incase there's an edgecase we're missing
|
||||||
// always return black if we get an unexpected error.
|
// always return black if we get an unexpected error.
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error("Unable to get feature color, error:", err);
|
console.warn("Unable to get feature color, error:", err);
|
||||||
return "black";
|
return "black";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,19 @@ function renderProperties(feature) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderFeatureId(feature) {
|
||||||
|
return <InputBlock key={"feature-id"} label={"feature_id"}>
|
||||||
|
<StringInput value={displayValue(feature.id)} style={{backgroundColor: 'transparent'}} />
|
||||||
|
</InputBlock>
|
||||||
|
}
|
||||||
|
|
||||||
function renderFeature(feature, idx) {
|
function renderFeature(feature, idx) {
|
||||||
return <div key={`${feature.sourceLayer}-${idx}`}>
|
return <div key={`${feature.sourceLayer}-${idx}`}>
|
||||||
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
|
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
|
||||||
<InputBlock key={"property-type"} label={"$type"}>
|
<InputBlock key={"property-type"} label={"$type"}>
|
||||||
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
|
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
{renderFeatureId(feature)}
|
||||||
{renderProperties(feature)}
|
{renderProperties(feature)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -36,7 +43,7 @@ function removeDuplicatedFeatures(features) {
|
|||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
const featureIndex = uniqueFeatures.findIndex(feature2 => {
|
const featureIndex = uniqueFeatures.findIndex(feature2 => {
|
||||||
return feature.layer['source-layer'] === feature2.layer['source-layer']
|
return feature.layer['source-layer'] === feature2.layer['source-layer']
|
||||||
&& JSON.stringify(feature.properties) === JSON.stringify(feature2.properties)
|
&& JSON.stringify(feature.properties) === JSON.stringify(feature2.properties)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -104,17 +104,24 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
|
|
||||||
this.updateMapFromProps(this.props);
|
this.updateMapFromProps(this.props);
|
||||||
|
|
||||||
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
|
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
||||||
// HACK: Fix for <https://github.com/maputnik/editor/issues/576>, while we wait for a proper fix.
|
// HACK: Fix for <https://github.com/maputnik/editor/issues/576>, while we wait for a proper fix.
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
this.state.inspect._popupBlocked = false;
|
this.state.inspect._popupBlocked = false;
|
||||||
this.state.inspect.toggleInspector()
|
this.state.inspect.toggleInspector()
|
||||||
}
|
}
|
||||||
if(this.props.inspectModeEnabled) {
|
|
||||||
this.state.inspect.render()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map) {
|
if (map) {
|
||||||
|
if (this.props.inspectModeEnabled) {
|
||||||
|
// HACK: We need to work out why we need to do this and what's causing
|
||||||
|
// this error. I'm assuming an issue with mapbox-gl update and
|
||||||
|
// mapbox-gl-inspect.
|
||||||
|
try {
|
||||||
|
this.state.inspect.render();
|
||||||
|
} catch(err) {
|
||||||
|
console.error("FIXME: Caught error", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -170,7 +177,7 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
if(this.props.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled) {
|
||||||
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
|
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
|
||||||
} else {
|
} else {
|
||||||
return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} zoom={this.state.zoom} />, tmpNode);
|
return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.onLayerSelectById} zoom={this.state.zoom} />, tmpNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -182,9 +189,6 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
inspect,
|
inspect,
|
||||||
zoom: map.getZoom()
|
zoom: map.getZoom()
|
||||||
});
|
});
|
||||||
if(this.props.inspectModeEnabled) {
|
|
||||||
inspect.toggleInspector();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
map.on("data", e => {
|
map.on("data", e => {
|
||||||
@@ -208,6 +212,11 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
map.on("zoomend", mapViewChange);
|
map.on("zoomend", mapViewChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLayerSelectById = (id) => {
|
||||||
|
const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id);
|
||||||
|
this.props.onLayerSelect(index);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(IS_SUPPORTED) {
|
if(IS_SUPPORTED) {
|
||||||
return <div
|
return <div
|
||||||
|
|||||||
@@ -15,16 +15,6 @@ import fieldSpecAdditional from '../../libs/field-spec-additional'
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function stripAccessTokens(mapStyle) {
|
|
||||||
const changedMetadata = { ...mapStyle.metadata }
|
|
||||||
delete changedMetadata['maputnik:mapbox_access_token']
|
|
||||||
delete changedMetadata['maputnik:openmaptiles_access_token']
|
|
||||||
return {
|
|
||||||
...mapStyle,
|
|
||||||
metadata: changedMetadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExportModal extends React.Component {
|
class ExportModal extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
mapStyle: PropTypes.object.isRequired,
|
mapStyle: PropTypes.object.isRequired,
|
||||||
@@ -38,7 +28,11 @@ class ExportModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadStyle() {
|
downloadStyle() {
|
||||||
const tokenStyle = format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle)));
|
const tokenStyle = format(
|
||||||
|
style.stripAccessTokens(
|
||||||
|
style.replaceAccessTokens(this.props.mapStyle)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
|
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
|
||||||
let exportName;
|
let exportName;
|
||||||
|
|||||||
@@ -303,9 +303,6 @@ class SourcesModal extends React.Component {
|
|||||||
<div className="maputnik-public-sources" style={{maxwidth: 500}}>
|
<div className="maputnik-public-sources" style={{maxwidth: 500}}>
|
||||||
{tilesetOptions}
|
{tilesetOptions}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
|
||||||
<strong>Note:</strong> Some of the tilesets are not optimised for online use, and as a result the file sizes of the tiles can be quite large (heavy) for online vector rendering. Please review any tilesets before use.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="maputnik-modal-section">
|
<div className="maputnik-modal-section">
|
||||||
|
|||||||
@@ -214,6 +214,11 @@ class GeoJSONSourceJSONEditor extends React.Component {
|
|||||||
<JSONEditor
|
<JSONEditor
|
||||||
layer={this.props.source.data}
|
layer={this.props.source.data}
|
||||||
maxHeight={200}
|
maxHeight={200}
|
||||||
|
mode={{
|
||||||
|
name: "javascript",
|
||||||
|
json: true
|
||||||
|
}}
|
||||||
|
lint={true}
|
||||||
onChange={data => {
|
onChange={data => {
|
||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
|
|||||||
@@ -12,6 +12,30 @@ CodeMirror.defineMode("mgl", function(config, parserConfig) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("lint", "json", function(text) {
|
||||||
|
const found = [];
|
||||||
|
|
||||||
|
// NOTE: This was modified from the original to remove the global, also the
|
||||||
|
// old jsonlint API was 'jsonlint.parseError' its now
|
||||||
|
// 'jsonlint.parser.parseError'
|
||||||
|
jsonlint.parser.parseError = function(str, hash) {
|
||||||
|
const loc = hash.loc;
|
||||||
|
found.push({
|
||||||
|
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
||||||
|
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
|
||||||
|
message: str
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsonlint.parse(text);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
// Do nothing we catch the error above
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
});
|
||||||
|
|
||||||
CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
CodeMirror.registerHelper("lint", "mgl", function(text, opts, doc) {
|
||||||
const found = [];
|
const found = [];
|
||||||
const {parser} = jsonlint;
|
const {parser} = jsonlint;
|
||||||
|
|||||||
3
src/components/util/format.js
Normal file
3
src/components/util/format.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function formatLayerId (id) {
|
||||||
|
return id === "" ? "[empty_string]" : `'${id}'`;
|
||||||
|
}
|
||||||
18
src/components/util/spec-helper.js
Normal file
18
src/components/util/spec-helper.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* If we don't have a default value just make one up
|
||||||
|
*/
|
||||||
|
export function findDefaultFromSpec (spec) {
|
||||||
|
if (spec.hasOwnProperty('default')) {
|
||||||
|
return spec.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
'color': '#000000',
|
||||||
|
'string': '',
|
||||||
|
'boolean': false,
|
||||||
|
'number': 0,
|
||||||
|
'array': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaults[spec.type] || '';
|
||||||
|
}
|
||||||
@@ -2,21 +2,21 @@
|
|||||||
"openmaptiles": {
|
"openmaptiles": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "https://api.maptiler.com/tiles/v3/tiles.json?key={key}",
|
"url": "https://api.maptiler.com/tiles/v3/tiles.json?key={key}",
|
||||||
"title": "OpenMapTiles"
|
"title": "OpenMapTiles v3"
|
||||||
},
|
},
|
||||||
"thunderforest_transport": {
|
"thunderforest_transport": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "https://tile.thunderforest.com/thunderforest.transport-v1.json?apikey={key}",
|
"url": "https://tile.thunderforest.com/thunderforest.transport-v2.json?apikey={key}",
|
||||||
"title": "Thunderforest Transport (heavy)"
|
"title": "Thunderforest Transport v2"
|
||||||
},
|
},
|
||||||
"thunderforest_outdoors": {
|
"thunderforest_outdoors": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "https://tile.thunderforest.com/thunderforest.outdoors-v1.json?apikey={key}",
|
"url": "https://tile.thunderforest.com/thunderforest.outdoors-v2.json?apikey={key}",
|
||||||
"title": "Thunderforest Outdoors (heavy)"
|
"title": "Thunderforest Outdoors v2"
|
||||||
},
|
},
|
||||||
"open_zoomstack": {
|
"open_zoomstack": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/data/vector/open-zoomstack/config.json",
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/v2/data/vector/open-zoomstack/config.json",
|
||||||
"title": "OS Open Zoomstack"
|
"title": "OS Open Zoomstack v2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import style from './style.js'
|
import style from './style.js'
|
||||||
|
import {format} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||||
|
|
||||||
export class ApiStyleStore {
|
export class ApiStyleStore {
|
||||||
@@ -64,6 +65,12 @@ export class ApiStyleStore {
|
|||||||
|
|
||||||
// Save current style replacing previous version
|
// Save current style replacing previous version
|
||||||
save(mapStyle) {
|
save(mapStyle) {
|
||||||
|
const styleJSON = format(
|
||||||
|
style.stripAccessTokens(
|
||||||
|
style.replaceAccessTokens(newStyle)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const id = mapStyle.id
|
const id = mapStyle.id
|
||||||
fetch(this.localUrl + '/styles/' + id, {
|
fetch(this.localUrl + '/styles/' + id, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -71,7 +78,7 @@ export class ApiStyleStore {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(mapStyle)
|
body: styleJSON
|
||||||
})
|
})
|
||||||
.catch(function(error) {
|
.catch(function(error) {
|
||||||
if(error) console.error(error)
|
if(error) console.error(error)
|
||||||
|
|||||||
@@ -114,6 +114,18 @@ function replaceAccessTokens(mapStyle, opts={}) {
|
|||||||
return changedStyle
|
return changedStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripAccessTokens(mapStyle) {
|
||||||
|
const changedMetadata = {
|
||||||
|
...mapStyle.metadata
|
||||||
|
};
|
||||||
|
delete changedMetadata['maputnik:mapbox_access_token'];
|
||||||
|
delete changedMetadata['maputnik:openmaptiles_access_token'];
|
||||||
|
return {
|
||||||
|
...mapStyle,
|
||||||
|
metadata: changedMetadata
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
ensureStyleValidity,
|
ensureStyleValidity,
|
||||||
emptyStyle,
|
emptyStyle,
|
||||||
@@ -121,4 +133,5 @@ export default {
|
|||||||
generateId,
|
generateId,
|
||||||
getAccessToken,
|
getAccessToken,
|
||||||
replaceAccessTokens,
|
replaceAccessTokens,
|
||||||
|
stripAccessTokens,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
// LAYER LIST
|
// LAYER LIST
|
||||||
.maputnik-layer-list {
|
.maputnik-layer-list {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
padding: $margin-2 $margin-2 $margin-3;
|
padding: $margin-2 $margin-2 $margin-3;
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
.maputnik-toolbar-logo {
|
.maputnik-toolbar-logo {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
display: block;
|
||||||
flex: 0 0 180px;
|
flex: 0 0 190px;
|
||||||
width: 180px;
|
width: 190px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-color: $color-black;
|
background-color: $color-black;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
|
|||||||
Reference in New Issue
Block a user