mirror of
https://github.com/maputnik/editor.git
synced 2025-12-07 14:50:02 +00:00
Compare commits
361 Commits
v1.3.0-bet
...
v1.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f1e212759 | ||
|
|
2b7db498ef | ||
|
|
e6464790f6 | ||
|
|
13ddf9f754 | ||
|
|
30edb881ed | ||
|
|
b30bbdc248 | ||
|
|
824616f6bd | ||
|
|
109198a524 | ||
|
|
920e4fe630 | ||
|
|
5e143e0a8e | ||
|
|
57f803d52c | ||
|
|
c55d342c7e | ||
|
|
e9065635cd | ||
|
|
6057721249 | ||
|
|
975487d271 | ||
|
|
46b2fd5978 | ||
|
|
f61313449f | ||
|
|
366ad4d7df | ||
|
|
b5cfb44cf0 | ||
|
|
050cc9cea9 | ||
|
|
b2f194eeee | ||
|
|
97b0b8541d | ||
|
|
b5eb74fe20 | ||
|
|
0500172d42 | ||
|
|
0e7bd98485 | ||
|
|
ff0ece5149 | ||
|
|
db9ad86ac2 | ||
|
|
a066710bfb | ||
|
|
52740483b6 | ||
|
|
518a624e20 | ||
|
|
4ba71c8bd5 | ||
|
|
ceeb628784 | ||
|
|
2ec6a90dc3 | ||
|
|
4e37d834ed | ||
|
|
a7922d894d | ||
|
|
eeda3296ab | ||
|
|
acd26e0162 | ||
|
|
fbf828e202 | ||
|
|
af9015f529 | ||
|
|
7a172b2022 | ||
|
|
a609dc4029 | ||
|
|
92bfee4bcc | ||
|
|
559d4618d1 | ||
|
|
5c391ee287 | ||
|
|
db74cfeb2a | ||
|
|
726b825e4c | ||
|
|
84d56b2606 | ||
|
|
e9a8b094a2 | ||
|
|
924b14621a | ||
|
|
b072e3a98c | ||
|
|
827bd5fa24 | ||
|
|
9e0410afe6 | ||
|
|
ef08a9347e | ||
|
|
9b732540a6 | ||
|
|
24c52074b8 | ||
|
|
cb6c6e0d9f | ||
|
|
884dc6fa49 | ||
|
|
efe42021f1 | ||
|
|
470277c253 | ||
|
|
c1cab38c7a | ||
|
|
1cf36ddb08 | ||
|
|
cc3c17078d | ||
|
|
47965d5f57 | ||
|
|
c947d9e3ed | ||
|
|
8318180e96 | ||
|
|
87daf6fb76 | ||
|
|
5d254ac2ff | ||
|
|
482f322d9f | ||
|
|
da456b08fe | ||
|
|
2776ac3ce0 | ||
|
|
eb2fc4c715 | ||
|
|
d2ffc3a0b1 | ||
|
|
40a9978f31 | ||
|
|
22688933b3 | ||
|
|
7cf01f0c12 | ||
|
|
1e87765f95 | ||
|
|
cc8fe4e02e | ||
|
|
979fc98e70 | ||
|
|
c3c0c35d8a | ||
|
|
f9913cad63 | ||
|
|
0ed64f94e8 | ||
|
|
1deecd4e2a | ||
|
|
e0e9201b46 | ||
|
|
362c7b437e | ||
|
|
efd0b547e9 | ||
|
|
afbaaa66bc | ||
|
|
de8c687dfb | ||
|
|
1bb079f078 | ||
|
|
b35322522f | ||
|
|
6a605571e0 | ||
|
|
c4ec77c911 | ||
|
|
f7643cee7e | ||
|
|
cd95202dcc | ||
|
|
facba3998b | ||
|
|
1f9cc2ce33 | ||
|
|
ad505378ab | ||
|
|
fb7b30c81d | ||
|
|
cb3f93c67d | ||
|
|
76cb5a6b7c | ||
|
|
990e47cb24 | ||
|
|
8be7465428 | ||
|
|
e2b7a6a517 | ||
|
|
284e00b665 | ||
|
|
1fbfa8428b | ||
|
|
b13b89c7ce | ||
|
|
3fa31e2a2e | ||
|
|
0e7d2d8ff5 | ||
|
|
2def3820dd | ||
|
|
e1a489f318 | ||
|
|
760839eb83 | ||
|
|
7136d2dea3 | ||
|
|
21f4d26b50 | ||
|
|
627ea268c9 | ||
|
|
65f77db4d6 | ||
|
|
03b4f1eb8d | ||
|
|
82d1fc0a8b | ||
|
|
3b16de5df4 | ||
|
|
94d4070653 | ||
|
|
d856a1cd8e | ||
|
|
7c1d0f7bee | ||
|
|
d02036321f | ||
|
|
1edadcc6bb | ||
|
|
aa92091d2d | ||
|
|
14e0385575 | ||
|
|
6cf861d44e | ||
|
|
1375240bfa | ||
|
|
8f391d7d52 | ||
|
|
84654e81af | ||
|
|
7ff0524bb7 | ||
|
|
06c3c92fd6 | ||
|
|
4c2941e9b6 | ||
|
|
ed7a25646e | ||
|
|
8c821176cf | ||
|
|
98ded98583 | ||
|
|
10ae69e41f | ||
|
|
04531b4305 | ||
|
|
7ab4b2481c | ||
|
|
6fa88e6869 | ||
|
|
5b90c31645 | ||
|
|
5eba11faee | ||
|
|
54b4fc473c | ||
|
|
fe8595cdc9 | ||
|
|
2565a89474 | ||
|
|
92b970377d | ||
|
|
dd3a550ec3 | ||
|
|
7d5d9e2d82 | ||
|
|
9daa71befc | ||
|
|
f10a2d28df | ||
|
|
d52c6c70bb | ||
|
|
6e2f46a0da | ||
|
|
2d6f91d0cd | ||
|
|
f409079d93 | ||
|
|
7004259867 | ||
|
|
f3128cb6d2 | ||
|
|
1dfd4d8d48 | ||
|
|
2f30eb6cbe | ||
|
|
cf45c04069 | ||
|
|
05d149bcfa | ||
|
|
3acbc3291c | ||
|
|
24aa2fd5fa | ||
|
|
cc7d7a56f5 | ||
|
|
da17646b8d | ||
|
|
f9233a1e31 | ||
|
|
c94f536e5a | ||
|
|
b712e7f184 | ||
|
|
b8ab802de5 | ||
|
|
a74eb2989c | ||
|
|
0f0684e701 | ||
|
|
e9b5bfb572 | ||
|
|
59ad91fdf8 | ||
|
|
5de5281b49 | ||
|
|
deec7894dd | ||
|
|
c9a0c0400e | ||
|
|
419e62f69b | ||
|
|
9ffbe3a7a2 | ||
|
|
c8e548e3be | ||
|
|
b9160bd333 | ||
|
|
8ad8b4cdea | ||
|
|
c51c40a20e | ||
|
|
2ccb1a8e0a | ||
|
|
008bb75c04 | ||
|
|
82d3c934c8 | ||
|
|
f2f0270936 | ||
|
|
00f646d489 | ||
|
|
90a02df45c | ||
|
|
ad40a15a77 | ||
|
|
d6809cb504 | ||
|
|
ff8d3055b4 | ||
|
|
1f81449e3c | ||
|
|
c59e0cb046 | ||
|
|
2d1675c181 | ||
|
|
48ebca6236 | ||
|
|
bce8e8b807 | ||
|
|
1f4dfa7603 | ||
|
|
c23af0063d | ||
|
|
9f6250c489 | ||
|
|
cf6c6f1c17 | ||
|
|
c6163b6ba2 | ||
|
|
7121a680b4 | ||
|
|
cf391031f0 | ||
|
|
9cac5305cd | ||
|
|
b0adb8cd3d | ||
|
|
3d2a1d5d19 | ||
|
|
3c93c41de1 | ||
|
|
4baed5d8ab | ||
|
|
f17b02b1fe | ||
|
|
3c72d07a88 | ||
|
|
7495c0dfcf | ||
|
|
b0c877d4ae | ||
|
|
e1fd0f8014 | ||
|
|
cb2198b661 | ||
|
|
68beeeb599 | ||
|
|
218ce148d5 | ||
|
|
d0cafb06ee | ||
|
|
5671a58704 | ||
|
|
9cf74ca405 | ||
|
|
1c6e3648eb | ||
|
|
b3a4628a79 | ||
|
|
941cc37c87 | ||
|
|
906d7ac3d5 | ||
|
|
588b18d10e | ||
|
|
90024c5ec7 | ||
|
|
889005de6c | ||
|
|
843d3df8bc | ||
|
|
825b9044b9 | ||
|
|
92a1be83b6 | ||
|
|
36e35eb208 | ||
|
|
2fcdb47fe5 | ||
|
|
012e4b670e | ||
|
|
492cc244d8 | ||
|
|
d17d6b43c0 | ||
|
|
1bf10cd6d6 | ||
|
|
b0cd9140be | ||
|
|
cc6196969f | ||
|
|
802a7eb1be | ||
|
|
a666f86be0 | ||
|
|
44fad76d45 | ||
|
|
c8d23a534e | ||
|
|
cf3650c8cd | ||
|
|
1a8349f821 | ||
|
|
855771a6b6 | ||
|
|
b711168e44 | ||
|
|
4134919dde | ||
|
|
158153e366 | ||
|
|
6b890d162a | ||
|
|
1525807d06 | ||
|
|
a356bfd601 | ||
|
|
e6d2a6d5ff | ||
|
|
c8a004422f | ||
|
|
df457fc7bf | ||
|
|
6e03f1f077 | ||
|
|
4c13350c14 | ||
|
|
63a2495c68 | ||
|
|
f9de73e18a | ||
|
|
e6e2be61f0 | ||
|
|
060f7aa42c | ||
|
|
8b0ae178b8 | ||
|
|
6b94e9b78b | ||
|
|
b171bf3127 | ||
|
|
0c6a179cec | ||
|
|
aa50785c12 | ||
|
|
252403b1e3 | ||
|
|
bc1d0de057 | ||
|
|
4a0b9fd0de | ||
|
|
e8777e1857 | ||
|
|
94a2a16330 | ||
|
|
004d135d93 | ||
|
|
0973dcee8a | ||
|
|
c908f7dcd0 | ||
|
|
b7fd889fcd | ||
|
|
35600c253d | ||
|
|
673465af77 | ||
|
|
cc5d0dc4fe | ||
|
|
e6da977c48 | ||
|
|
e4aa016713 | ||
|
|
8b67499a64 | ||
|
|
bcdc7c6811 | ||
|
|
8f07a79a49 | ||
|
|
cdcf16196c | ||
|
|
a0ed6a379b | ||
|
|
7ffb44f604 | ||
|
|
225d0388ce | ||
|
|
0468db8cc2 | ||
|
|
695f612110 | ||
|
|
9c07852b87 | ||
|
|
9ef198fb86 | ||
|
|
fd34e31462 | ||
|
|
8eb49427fd | ||
|
|
ebafb3c3dd | ||
|
|
09c6154949 | ||
|
|
53c8661cd3 | ||
|
|
3d5eec897e | ||
|
|
3763ec3737 | ||
|
|
f1216795d2 | ||
|
|
0ac70df00f | ||
|
|
7d0a985f1d | ||
|
|
c5ff67b6e0 | ||
|
|
db6b9ac176 | ||
|
|
77475af3c6 | ||
|
|
805133d10c | ||
|
|
fff8fb72c5 | ||
|
|
e02b18cea3 | ||
|
|
b8e9307ce2 | ||
|
|
00b22eb902 | ||
|
|
be954143c3 | ||
|
|
b314642586 | ||
|
|
b5fc315b37 | ||
|
|
26ff9f63bb | ||
|
|
7e5fb4d42f | ||
|
|
762bb786be | ||
|
|
cec87765fc | ||
|
|
b966fae926 | ||
|
|
f1ddf4e57e | ||
|
|
64e65dc7d3 | ||
|
|
1e07a88aed | ||
|
|
6e49cc65a9 | ||
|
|
06d579118a | ||
|
|
f0e4b5b930 | ||
|
|
088127a9a5 | ||
|
|
a4b4d077fa | ||
|
|
bc2ec4d0b7 | ||
|
|
e4de101553 | ||
|
|
6207416b32 | ||
|
|
f0202241f4 | ||
|
|
0e8c94af1e | ||
|
|
922ee616ec | ||
|
|
98c235bc21 | ||
|
|
70f1f9ffac | ||
|
|
409f81f0d8 | ||
|
|
1aa90bef37 | ||
|
|
c5ea9494df | ||
|
|
9a34db7008 | ||
|
|
988b7fca0f | ||
|
|
bdc6489db4 | ||
|
|
49b096b601 | ||
|
|
31d83f6a26 | ||
|
|
03e52b7a72 | ||
|
|
551e950c39 | ||
|
|
a7620f83a6 | ||
|
|
0384181ee1 | ||
|
|
fd59f42819 | ||
|
|
cc51774259 | ||
|
|
5a19245ee0 | ||
|
|
45f45b7547 | ||
|
|
530bfaf3b3 | ||
|
|
6ea70ab9cf | ||
|
|
a0e2d68dae | ||
|
|
1447e8bfb5 | ||
|
|
c0480a50ea | ||
|
|
09ba2be416 | ||
|
|
115ce3305d | ||
|
|
960b2022ed | ||
|
|
252b442ca9 | ||
|
|
03b9ddda9c | ||
|
|
968d7d7fda | ||
|
|
b211f1cd12 | ||
|
|
870d4349f4 | ||
|
|
d88bc59720 | ||
|
|
85a28999fb | ||
|
|
45bdf53a41 | ||
|
|
00e94212bd |
12
.babelrc
12
.babelrc
@@ -1,11 +1,17 @@
|
|||||||
{
|
{
|
||||||
"presets": ["env", "react"],
|
"presets": [
|
||||||
"plugins": ["transform-object-rest-spread", "transform-class-properties"],
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"react-hot-loader/babel",
|
||||||
|
"@babel/plugin-proposal-class-properties"
|
||||||
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"test": {
|
"test": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
["istanbul", {
|
["istanbul", {
|
||||||
exclude: ["node_modules/**", "test/**"]
|
"exclude": ["node_modules/**", "test/**"]
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,29 +49,21 @@ templates:
|
|||||||
path: /tmp/artifacts
|
path: /tmp/artifacts
|
||||||
destination: /artifacts
|
destination: /artifacts
|
||||||
jobs:
|
jobs:
|
||||||
build-linux-node-v6:
|
|
||||||
docker:
|
|
||||||
- image: node:6
|
|
||||||
working_directory: ~/repo-linux-node-v6
|
|
||||||
steps: *build-steps
|
|
||||||
build-linux-node-v8:
|
build-linux-node-v8:
|
||||||
docker:
|
docker:
|
||||||
- image: node:8
|
- image: node:8
|
||||||
- image: selenium/standalone-chrome:3.8.1
|
|
||||||
working_directory: ~/repo-linux-node-v8
|
working_directory: ~/repo-linux-node-v8
|
||||||
steps: *wdio-steps
|
steps: *build-steps
|
||||||
build-linux-node-v10:
|
build-linux-node-v10:
|
||||||
docker:
|
docker:
|
||||||
- image: node:10
|
- image: node:10
|
||||||
|
- image: selenium/standalone-chrome:3.141.59
|
||||||
working_directory: ~/repo-linux-node-v10
|
working_directory: ~/repo-linux-node-v10
|
||||||
steps: *build-steps
|
steps: *wdio-steps
|
||||||
build-osx-node-v6:
|
build-linux-node-v12:
|
||||||
macos:
|
docker:
|
||||||
xcode: "9.0"
|
- image: node:12
|
||||||
dependencies:
|
working_directory: ~/repo-linux-node-v12
|
||||||
override:
|
|
||||||
- brew install node@6
|
|
||||||
working_directory: ~/repo-osx-node-v6
|
|
||||||
steps: *build-steps
|
steps: *build-steps
|
||||||
build-osx-node-v8:
|
build-osx-node-v8:
|
||||||
macos:
|
macos:
|
||||||
@@ -89,15 +81,22 @@ jobs:
|
|||||||
- brew install node@10
|
- brew install node@10
|
||||||
working_directory: ~/repo-osx-node-v10
|
working_directory: ~/repo-osx-node-v10
|
||||||
steps: *build-steps
|
steps: *build-steps
|
||||||
|
build-osx-node-v12:
|
||||||
|
macos:
|
||||||
|
xcode: "9.0"
|
||||||
|
dependencies:
|
||||||
|
override:
|
||||||
|
- brew install node@12
|
||||||
|
working_directory: ~/repo-osx-node-v12
|
||||||
|
steps: *build-steps
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build:
|
build:
|
||||||
jobs:
|
jobs:
|
||||||
- build-linux-node-v6
|
|
||||||
- build-linux-node-v8
|
- build-linux-node-v8
|
||||||
- build-linux-node-v10
|
- build-linux-node-v10
|
||||||
- build-osx-node-v6
|
- build-linux-node-v12
|
||||||
- build-osx-node-v8
|
- build-osx-node-v8
|
||||||
- build-osx-node-v10
|
- build-osx-node-v10
|
||||||
|
- build-osx-node-v12
|
||||||
|
|||||||
15
.topissuesrc
Normal file
15
.topissuesrc
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"bug": 5,
|
||||||
|
"maintenance": 3,
|
||||||
|
"mentioned in the 1st survey": 2
|
||||||
|
},
|
||||||
|
"reactions": {
|
||||||
|
"+1": 2,
|
||||||
|
"-1": -1,
|
||||||
|
"laugh": 1,
|
||||||
|
"hooray": 2,
|
||||||
|
"confused": 1,
|
||||||
|
"heart": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
22
.travis.yml
22
.travis.yml
@@ -1,22 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: osx
|
|
||||||
node_js: "6"
|
|
||||||
- os: osx
|
|
||||||
node_js: "8"
|
|
||||||
- os: osx
|
|
||||||
node_js: "9"
|
|
||||||
install:
|
|
||||||
- npm install
|
|
||||||
script:
|
|
||||||
- mkdir public
|
|
||||||
- node --stack_size=100000 $(which npm) run build
|
|
||||||
- npm run lint
|
|
||||||
- npm run lint-styles
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- g++-4.8
|
|
||||||
12
Dockerfile
12
Dockerfile
@@ -1,4 +1,9 @@
|
|||||||
FROM nodesource/xenial:6.1.0
|
FROM node:10-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
git \
|
||||||
|
python \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
EXPOSE 8888
|
EXPOSE 8888
|
||||||
|
|
||||||
@@ -9,7 +14,8 @@ COPY . ${HOME}/
|
|||||||
|
|
||||||
WORKDIR ${HOME}
|
WORKDIR ${HOME}
|
||||||
|
|
||||||
RUN npm install -d --dev
|
RUN npm install -d
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
CMD npm run start -- --host 0.0.0.0
|
WORKDIR ${HOME}/build/build
|
||||||
|
CMD python -m SimpleHTTPServer 8888
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -1,26 +1,27 @@
|
|||||||
# Maputnik
|
# Maputnik
|
||||||
|
|
||||||
[][travis]
|
[][circleci]
|
||||||
[][appveyor]
|
[][appveyor]
|
||||||
[][dm-prod]
|
[][dm-prod]
|
||||||
[][dm-dev]
|
[][dm-dev]
|
||||||
[][license]
|
[][license]
|
||||||
|
|
||||||
[travis]: https://travis-ci.org/maputnik/editor
|
[circleci]: https://circleci.com/gh/maputnik/editor/tree/master
|
||||||
[appveyor]: https://ci.appveyor.com/project/lukasmartinelli/editor
|
[appveyor]: https://ci.appveyor.com/project/lukasmartinelli/editor
|
||||||
[dm-prod]: https://david-dm.org/maputnik/editor
|
[dm-prod]: https://david-dm.org/maputnik/editor
|
||||||
[dm-dev]: https://david-dm.org/maputnik/editor#info=devDependencies
|
[dm-dev]: https://david-dm.org/maputnik/editor?type=dev
|
||||||
[license]: https://tldrlegal.com/license/mit-license
|
[license]: https://tldrlegal.com/license/mit-license
|
||||||
|
|
||||||
<img width="200" align="right" alt="Maputnik" src="src/img/maputnik.png" />
|
<img width="200" align="right" alt="Maputnik" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/src/img/maputnik.png" />
|
||||||
|
|
||||||
A free and open visual editor for the [Mapbox GL styles](https://www.mapbox.com/mapbox-gl-style-spec/)
|
A free and open visual editor for the [Mapbox GL styles](https://www.mapbox.com/mapbox-gl-style-spec/)
|
||||||
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.6.0-beta release at: https://maputnik.github.io/releases/v1.6.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 independance 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.
|
||||||
|
|
||||||
|
|
||||||
## Donations
|
## Donations
|
||||||
@@ -40,10 +41,7 @@ The documentation can be found in the [Wiki](https://github.com/maputnik/editor/
|
|||||||
|
|
||||||
Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/).
|
Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/).
|
||||||
|
|
||||||
We ensure building and developing Maputnik works with
|
We ensure building and developing Maputnik works with the [current active LTS Node.js version and above](https://github.com/nodejs/Release#release-schedule).
|
||||||
|
|
||||||
- Linux, OSX and Windows
|
|
||||||
- Node >4
|
|
||||||
|
|
||||||
Install the deps, start the dev server and open the web browser on `http://localhost:8888/`.
|
Install the deps, start the dev server and open the web browser on `http://localhost:8888/`.
|
||||||
|
|
||||||
@@ -54,12 +52,18 @@ npm install
|
|||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
The build process will watch for changes to the filesystem, rebuild and autoreload the editor. However note this from the webpack-dev-server docs
|
If you want Maputnik to be accessible externally use the [`--host` option](https://webpack.js.org/configuration/dev-server/#devserverhost):
|
||||||
|
|
||||||
> webpack uses the file system to get notified of file changes. In some cases this does not work. For example, when using Network File System (NFS). Vagrant also has a lot of problems with this.
|
```bash
|
||||||
Snippet from <https://webpack.js.org/configuration/dev-server/#devserver-watchoptions->
|
# start externally accessible dev server
|
||||||
|
npm start -- --host 0.0.0.0
|
||||||
|
```
|
||||||
|
|
||||||
To enable polling add `export WEBPACK_DEV_SERVER_POLLING=1` to your enviroment.
|
The build process will watch for changes to the filesystem, rebuild and autoreload the editor. However note this from the [webpack-dev-server docs](https://webpack.js.org/configuration/dev-server/):
|
||||||
|
|
||||||
|
> webpack uses the file system to get notified of file changes. In some cases this does not work. For example, when using Network File System (NFS). Vagrant also has a lot of problems with this. ([snippet source](https://webpack.js.org/configuration/dev-server/#devserverwatchoptions-))
|
||||||
|
|
||||||
|
To enable polling add `export WEBPACK_DEV_SERVER_POLLING=1` to your environment.
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run build
|
npm run build
|
||||||
@@ -79,7 +83,7 @@ For testing we use [webdriverio](http://webdriver.io) and [selenium-standalone](
|
|||||||
|
|
||||||
[selenium-standalone](https://github.com/vvo/selenium-standalone) starts a server that will launch browsers on your local machine. We use chrome so you **must** have chrome installed on your machine.
|
[selenium-standalone](https://github.com/vvo/selenium-standalone) starts a server that will launch browsers on your local machine. We use chrome so you **must** have chrome installed on your machine.
|
||||||
|
|
||||||
Now open and terminal and run the following. This will install the drivers on your local machine
|
Now open a terminal and run the following. This will install the drivers on your local machine
|
||||||
|
|
||||||
```
|
```
|
||||||
./node_modules/.bin/selenium-standalone install
|
./node_modules/.bin/selenium-standalone install
|
||||||
@@ -115,13 +119,13 @@ Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter
|
|||||||
- [Terranodo](http://terranodo.io/)
|
- [Terranodo](http://terranodo.io/)
|
||||||
|
|
||||||
<a href="https://getwemap.com/">
|
<a href="https://getwemap.com/">
|
||||||
<img width="33%" alt="Wemap" style="display:inline" src="media/sponsors/wemap.jpg" />
|
<img width="33%" alt="Wemap" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/wemap.jpg" />
|
||||||
</a>
|
</a>
|
||||||
<a href="http://terranodo.io/">
|
<a href="http://terranodo.io/">
|
||||||
<img width="33%" alt="Terranodo" style="display:inline" src="media/sponsors/terranodo.png" />
|
<img width="33%" alt="Terranodo" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/terranodo.png" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.orbiconinformatik.dk/">
|
<a href="https://www.orbiconinformatik.dk/">
|
||||||
<img width="32%" alt="Terranodo" style="display:inline" src="media/sponsors/orbicon_informatik.png" />
|
<img width="32%" alt="Terranodo" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/orbicon_informatik.png" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
@@ -133,13 +137,13 @@ Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter
|
|||||||
- [Dreipol](https://www.dreipol.ch/)
|
- [Dreipol](https://www.dreipol.ch/)
|
||||||
|
|
||||||
<a href="https://www.klokantech.com/">
|
<a href="https://www.klokantech.com/">
|
||||||
<img width="18%" alt="Klokan Technologies" style="display:inline-block" src="media/sponsors/klokantech.png" />
|
<img width="18%" alt="Klokan Technologies" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/klokantech.png" />
|
||||||
</a>
|
</a>
|
||||||
<a href="http://www.geofabrik.de/">
|
<a href="http://www.geofabrik.de/">
|
||||||
<img width="18%" alt="Geofabrik" style="display:inline-block" src="media/sponsors/geofabrik.png" />
|
<img width="18%" alt="Geofabrik" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/geofabrik.png" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.dreipol.ch/">
|
<a href="https://www.dreipol.ch/">
|
||||||
<img width="18%" alt="Dreipol" style="display:inline-block" src="media/sponsors/dreipol.png" />
|
<img width="18%" alt="Dreipol" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/dreipol.png" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
@@ -162,6 +166,6 @@ Sina Martinelli, Nicholas Doiron, Neil Cawse, Urs42, Benedikt Groß, Manuel Roth
|
|||||||
|
|
||||||
Maputnik is [licensed under MIT](LICENSE) and is Copyright (c) Lukas Martinelli and contributors.
|
Maputnik is [licensed under MIT](LICENSE) and is Copyright (c) Lukas Martinelli and contributors.
|
||||||
|
|
||||||
**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is a independent style editor for the
|
**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is an independent style editor for the
|
||||||
open source technology in the Mapbox GL ecosystem.
|
open source technology in the Mapbox GL ecosystem.
|
||||||
As contributor please take extra care of not violating any Mapbox trademarks. Do not get inspired by Mapbox Studio and make your own decisions for a good style editor.
|
As contributor please take extra care of not violating any Mapbox trademarks. Do not get inspired by Mapbox Studio and make your own decisions for a good style editor.
|
||||||
|
|||||||
17
appveyor.yml
17
appveyor.yml
@@ -1,17 +1,26 @@
|
|||||||
|
image: Visual Studio 2015
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- nodejs_version: "6"
|
|
||||||
- nodejs_version: "8"
|
- nodejs_version: "8"
|
||||||
- nodejs_version: "9"
|
- nodejs_version: "10"
|
||||||
|
- nodejs_version: "12"
|
||||||
platform:
|
platform:
|
||||||
- x86
|
- x86
|
||||||
- x64
|
- x64
|
||||||
install:
|
install:
|
||||||
- ps: Install-Product node $env:nodejs_version
|
# https://github.com/appveyor/ci/issues/2921#issuecomment-501016533
|
||||||
|
- ps: |
|
||||||
|
try {
|
||||||
|
Install-Product node $env:nodejs_version $env:platform
|
||||||
|
} catch {
|
||||||
|
echo "Unable to install node $env:nodejs_version, trying update..."
|
||||||
|
Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
|
||||||
|
}
|
||||||
- md public
|
- md public
|
||||||
- npm install --global --production windows-build-tools
|
- npm --vs2015 install --global windows-build-tools
|
||||||
- npm install
|
- npm install
|
||||||
build_script:
|
build_script:
|
||||||
- npm run build
|
- npm run build
|
||||||
test_script:
|
test_script:
|
||||||
- npm run lint
|
- npm run lint
|
||||||
|
- npm run lint-styles
|
||||||
|
|||||||
@@ -10,53 +10,279 @@ var server;
|
|||||||
var SCREENSHOT_PATH = artifacts.pathSync("screenshots");
|
var SCREENSHOT_PATH = artifacts.pathSync("screenshots");
|
||||||
|
|
||||||
exports.config = {
|
exports.config = {
|
||||||
specs: [
|
//
|
||||||
'./test/functional/index.js'
|
// ====================
|
||||||
],
|
// Runner Configuration
|
||||||
exclude: [
|
// ====================
|
||||||
],
|
//
|
||||||
maxInstances: 10,
|
// WebdriverIO allows it to run your tests in arbitrary locations (e.g. locally or
|
||||||
capabilities: [{
|
// on a remote machine).
|
||||||
maxInstances: 5,
|
runner: 'local',
|
||||||
browserName: 'chrome'
|
//
|
||||||
}],
|
// ==================
|
||||||
sync: true,
|
// Specify Test Files
|
||||||
logLevel: 'verbose',
|
// ==================
|
||||||
coloredLogs: true,
|
// Define which test specs should run. The pattern is relative to the directory
|
||||||
bail: 0,
|
// from which `wdio` was called. Notice that, if you are calling `wdio` from an
|
||||||
screenshotPath: SCREENSHOT_PATH,
|
// NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
|
||||||
// Note: This is here because @orangemug currently runs Maputnik inside a docker container.
|
// directory is where your package.json resides, so `wdio` will be called from there.
|
||||||
host: process.env.DOCKER_HOST || "0.0.0.0",
|
//
|
||||||
baseUrl: 'http://localhost',
|
specs: [
|
||||||
waitforTimeout: 10000,
|
'./test/functional/index.js'
|
||||||
connectionRetryTimeout: 90000,
|
],
|
||||||
connectionRetryCount: 3,
|
// Patterns to exclude.
|
||||||
framework: 'mocha',
|
exclude: [
|
||||||
reporters: ['spec'],
|
// 'path/to/excluded/files'
|
||||||
mochaOpts: {
|
],
|
||||||
ui: 'bdd',
|
//
|
||||||
// Because we don't know how long the initial build will take...
|
// ============
|
||||||
timeout: 4*60*1000
|
// Capabilities
|
||||||
},
|
// ============
|
||||||
onPrepare: function (config, capabilities) {
|
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
|
||||||
return new Promise(function(resolve, reject) {
|
// time. Depending on the number of capabilities, WebdriverIO launches several test
|
||||||
var compiler = webpack(webpackConfig);
|
// sessions. Within your capabilities you can overwrite the spec and exclude options in
|
||||||
server = new WebpackDevServer(compiler, {
|
// order to group specific specs to a specific capability.
|
||||||
stats: {
|
//
|
||||||
colors: true
|
// First, you can define how many instances should be started at the same time. Let's
|
||||||
}
|
// say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
|
||||||
});
|
// set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
|
||||||
server.listen(testConfig.port, (isDocker() ? "0.0.0.0" : "localhost"), function(err) {
|
// files and you set maxInstances to 10, all spec files will get tested at the same time
|
||||||
if(err) {
|
// and 30 processes will get spawned. The property handles how many capabilities
|
||||||
reject(err);
|
// from the same test should run tests.
|
||||||
}
|
//
|
||||||
else {
|
maxInstances: 10,
|
||||||
resolve();
|
//
|
||||||
}
|
// If you have trouble getting all important capabilities together, check out the
|
||||||
});
|
// Sauce Labs platform configurator - a great tool to configure your capabilities:
|
||||||
})
|
// https://docs.saucelabs.com/reference/platforms-configurator
|
||||||
},
|
//
|
||||||
onComplete: function(exitCode) {
|
capabilities: [{
|
||||||
server.close()
|
// maxInstances can get overwritten per capability. So if you have an in-house Selenium
|
||||||
}
|
// grid with only 5 firefox instances available you can make sure that not more than
|
||||||
|
// 5 instances get started at a time.
|
||||||
|
maxInstances: 5,
|
||||||
|
//
|
||||||
|
browserName: 'chrome',
|
||||||
|
// If outputDir is provided WebdriverIO can capture driver session logs
|
||||||
|
// it is possible to configure which logTypes to include/exclude.
|
||||||
|
// excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs
|
||||||
|
// excludeDriverLogs: ['bugreport', 'server'],
|
||||||
|
}],
|
||||||
|
//
|
||||||
|
// ===================
|
||||||
|
// Test Configurations
|
||||||
|
// ===================
|
||||||
|
// Define all options that are relevant for the WebdriverIO instance here
|
||||||
|
//
|
||||||
|
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||||
|
logLevel: 'info',
|
||||||
|
//
|
||||||
|
// Set specific log levels per logger
|
||||||
|
// loggers:
|
||||||
|
// - webdriver, webdriverio
|
||||||
|
// - @wdio/applitools-service, @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
|
||||||
|
// - @wdio/mocha-framework, @wdio/jasmine-framework
|
||||||
|
// - @wdio/local-runner, @wdio/lambda-runner
|
||||||
|
// - @wdio/sumologic-reporter
|
||||||
|
// - @wdio/cli, @wdio/config, @wdio/sync, @wdio/utils
|
||||||
|
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||||
|
// logLevels: {
|
||||||
|
// webdriver: 'debug',
|
||||||
|
// '@wdio/applitools-service': 'info'
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// If you only want to run your tests until a specific amount of tests have failed use
|
||||||
|
// bail (default is 0 - don't bail, run all tests).
|
||||||
|
bail: 0,
|
||||||
|
//
|
||||||
|
screenshotPath: SCREENSHOT_PATH,
|
||||||
|
// Note: This is here because @orangemug currently runs Maputnik inside a docker container.
|
||||||
|
host: process.env.DOCKER_HOST || "0.0.0.0",
|
||||||
|
// Set a base URL in order to shorten url command calls. If your `url` parameter starts
|
||||||
|
// with `/`, the base url gets prepended, not including the path portion of your baseUrl.
|
||||||
|
// If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
|
||||||
|
// gets prepended directly.
|
||||||
|
baseUrl: 'http://localhost',
|
||||||
|
//
|
||||||
|
// Default timeout for all waitFor* commands.
|
||||||
|
waitforTimeout: 10000,
|
||||||
|
//
|
||||||
|
// Default timeout in milliseconds for request
|
||||||
|
// if Selenium Grid doesn't send response
|
||||||
|
connectionRetryTimeout: 90000,
|
||||||
|
//
|
||||||
|
// Default request retries count
|
||||||
|
connectionRetryCount: 3,
|
||||||
|
//
|
||||||
|
// Test runner services
|
||||||
|
// Services take over a specific job you don't want to take care of. They enhance
|
||||||
|
// your test setup with almost no effort. Unlike plugins, they don't add new
|
||||||
|
// commands. Instead, they hook themselves up into the test process.
|
||||||
|
services: ['selenium-standalone'],
|
||||||
|
//
|
||||||
|
// Framework you want to run your specs with.
|
||||||
|
// The following are supported: Mocha, Jasmine, and Cucumber
|
||||||
|
// see also: https://webdriver.io/docs/frameworks.html
|
||||||
|
//
|
||||||
|
// Make sure you have the wdio adapter package for the specific framework installed
|
||||||
|
// before running any tests.
|
||||||
|
framework: 'mocha',
|
||||||
|
//
|
||||||
|
// The number of times to retry the entire specfile when it fails as a whole
|
||||||
|
// specFileRetries: 1,
|
||||||
|
//
|
||||||
|
// Test reporter for stdout.
|
||||||
|
// The only one supported by default is 'dot'
|
||||||
|
// see also: https://webdriver.io/docs/dot-reporter.html
|
||||||
|
reporters: ['spec'],
|
||||||
|
|
||||||
|
//
|
||||||
|
// Options to be passed to Mocha.
|
||||||
|
// See the full list at http://mochajs.org/
|
||||||
|
mochaOpts: {
|
||||||
|
ui: 'bdd',
|
||||||
|
// Because we don't know how long the initial build will take...
|
||||||
|
timeout: 4*60*1000
|
||||||
|
},
|
||||||
|
onPrepare: function (config, capabilities) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var compiler = webpack(webpackConfig);
|
||||||
|
server = new WebpackDevServer(compiler, {
|
||||||
|
stats: {
|
||||||
|
colors: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.listen(testConfig.port, (isDocker() ? "0.0.0.0" : "localhost"), function(err) {
|
||||||
|
if(err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onComplete: function(exitCode) {
|
||||||
|
server.close()
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// =====
|
||||||
|
// Hooks
|
||||||
|
// =====
|
||||||
|
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
|
||||||
|
// it and to build services around it. You can either apply a single function or an array of
|
||||||
|
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
|
||||||
|
// resolved to continue.
|
||||||
|
/**
|
||||||
|
* Gets executed once before all workers get launched.
|
||||||
|
* @param {Object} config wdio configuration object
|
||||||
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
|
*/
|
||||||
|
// onPrepare: function (config, capabilities) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Gets executed just before initialising the webdriver session and test framework. It allows you
|
||||||
|
* to manipulate configurations depending on the capability or spec.
|
||||||
|
* @param {Object} config wdio configuration object
|
||||||
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
|
* @param {Array.<String>} specs List of spec file paths that are to be run
|
||||||
|
*/
|
||||||
|
// beforeSession: function (config, capabilities, specs) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Gets executed before test execution begins. At this point you can access to all global
|
||||||
|
* variables like `browser`. It is the perfect place to define custom commands.
|
||||||
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
|
* @param {Array.<String>} specs List of spec file paths that are to be run
|
||||||
|
*/
|
||||||
|
// before: function (capabilities, specs) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Runs before a WebdriverIO command gets executed.
|
||||||
|
* @param {String} commandName hook command name
|
||||||
|
* @param {Array} args arguments that command would receive
|
||||||
|
*/
|
||||||
|
// beforeCommand: function (commandName, args) {
|
||||||
|
// },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook that gets executed before the suite starts
|
||||||
|
* @param {Object} suite suite details
|
||||||
|
*/
|
||||||
|
// beforeSuite: function (suite) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
|
||||||
|
* @param {Object} test test details
|
||||||
|
*/
|
||||||
|
// beforeTest: function (test) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
|
||||||
|
* beforeEach in Mocha)
|
||||||
|
*/
|
||||||
|
// beforeHook: function () {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
|
||||||
|
* afterEach in Mocha)
|
||||||
|
*/
|
||||||
|
// afterHook: function () {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
|
||||||
|
* @param {Object} test test details
|
||||||
|
*/
|
||||||
|
// afterTest: function (test) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Hook that gets executed after the suite has ended
|
||||||
|
* @param {Object} suite suite details
|
||||||
|
*/
|
||||||
|
// afterSuite: function (suite) {
|
||||||
|
// },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs after a WebdriverIO command gets executed
|
||||||
|
* @param {String} commandName hook command name
|
||||||
|
* @param {Array} args arguments that command would receive
|
||||||
|
* @param {Number} result 0 - command success, 1 - command error
|
||||||
|
* @param {Object} error error object if any
|
||||||
|
*/
|
||||||
|
// afterCommand: function (commandName, args, result, error) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Gets executed after all tests are done. You still have access to all global variables from
|
||||||
|
* the test.
|
||||||
|
* @param {Number} result 0 - test pass, 1 - test fail
|
||||||
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
|
* @param {Array.<String>} specs List of spec file paths that ran
|
||||||
|
*/
|
||||||
|
// after: function (result, capabilities, specs) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Gets executed right after terminating the webdriver session.
|
||||||
|
* @param {Object} config wdio configuration object
|
||||||
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
|
* @param {Array.<String>} specs List of spec file paths that ran
|
||||||
|
*/
|
||||||
|
// afterSession: function (config, capabilities, specs) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Gets executed after all workers got shut down and the process is about to exit. An error
|
||||||
|
* thrown in the onComplete hook will result in the test run failing.
|
||||||
|
* @param {Object} exitCode 0 - success, 1 - fail
|
||||||
|
* @param {Object} config wdio configuration object
|
||||||
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
|
* @param {<Object>} results object containing test results
|
||||||
|
*/
|
||||||
|
// onComplete: function(exitCode, config, capabilities, results) {
|
||||||
|
// },
|
||||||
|
/**
|
||||||
|
* Gets executed when a refresh happens.
|
||||||
|
* @param {String} oldSessionId session ID of the old session
|
||||||
|
* @param {String} newSessionId session ID of the new session
|
||||||
|
*/
|
||||||
|
//onReload: function(oldSessionId, newSessionId) {
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var webpack = require('webpack');
|
var webpack = require('webpack');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var loaders = require('./webpack.loaders');
|
var rules = require('./webpack.rules');
|
||||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ const PORT = process.env.PORT || "8888";
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
target: 'web',
|
target: 'web',
|
||||||
|
mode: 'development',
|
||||||
entry: [
|
entry: [
|
||||||
`webpack-dev-server/client?http://${HOST}:${PORT}`,
|
`webpack-dev-server/client?http://${HOST}:${PORT}`,
|
||||||
`webpack/hot/only-dev-server`,
|
`webpack/hot/only-dev-server`,
|
||||||
@@ -27,7 +28,7 @@ module.exports = {
|
|||||||
noParse: [
|
noParse: [
|
||||||
/mapbox-gl\/dist\/mapbox-gl.js/
|
/mapbox-gl\/dist\/mapbox-gl.js/
|
||||||
],
|
],
|
||||||
loaders: loaders
|
rules: rules
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
fs: "empty",
|
fs: "empty",
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
module.exports = [
|
|
||||||
{
|
|
||||||
test: /\.jsx?$/,
|
|
||||||
exclude: /(node_modules|bower_components|public)/,
|
|
||||||
loaders: ['react-hot-loader/webpack']
|
|
||||||
},
|
|
||||||
// HACK: This is a massive hack and reaches into the mapbox-gl private API.
|
|
||||||
// We have to include this for access to `normalizeSourceURL`. We should
|
|
||||||
// remove this ASAP, see <https://github.com/mapbox/mapbox-gl-js/issues/2416>
|
|
||||||
{
|
|
||||||
test: /.*node_modules[\/\\]mapbox-gl[\/\\]src[\/\\]util[\/\\].*\.js/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
query: {
|
|
||||||
presets: ['env', 'react', 'flow'],
|
|
||||||
plugins: ['transform-runtime', 'transform-decorators-legacy', 'transform-class-properties'],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.jsx?$/,
|
|
||||||
// Note: These modules aren't ES5 therefore we much compile them.
|
|
||||||
exclude: /(.*node_modules(?)|bower_components|public)/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
query: {
|
|
||||||
presets: ['env', 'react'],
|
|
||||||
plugins: ['transform-runtime', 'transform-decorators-legacy', 'transform-class-properties'],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(eot|ttf|woff|woff2)$/,
|
|
||||||
loader: 'file-loader?name=fonts/[name].[ext]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.ico$/,
|
|
||||||
loader: 'file-loader?name=[name].[ext]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(svg|gif|jpg|png)$/,
|
|
||||||
loader: 'file-loader?name=img/[name].[ext]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.json$/,
|
|
||||||
loader: 'json-loader'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /[\/\\](node_modules|global|src)[\/\\].*\.scss$/,
|
|
||||||
loaders: ["style-loader", "css-loader", "sass-loader"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /[\/\\](node_modules|global|src)[\/\\].*\.css$/,
|
|
||||||
loaders: [
|
|
||||||
'style-loader?sourceMap',
|
|
||||||
'css-loader'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,43 +1,22 @@
|
|||||||
var webpack = require('webpack');
|
var webpack = require('webpack');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var loaders = require('./webpack.loaders');
|
var rules = require('./webpack.rules');
|
||||||
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
|
||||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
|
var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
|
||||||
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
var artifacts = require("../test/artifacts");
|
var artifacts = require("../test/artifacts");
|
||||||
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
|
||||||
|
|
||||||
var OUTPATH = artifacts.pathSync("/build");
|
var OUTPATH = artifacts.pathSync("/build");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
app: './src/index.jsx',
|
app: './src/index.jsx',
|
||||||
vendor: [
|
|
||||||
'file-saver',
|
|
||||||
'mapbox-gl/dist/mapbox-gl.js',
|
|
||||||
"lodash.clonedeep",
|
|
||||||
"lodash.throttle",
|
|
||||||
'color',
|
|
||||||
'react',
|
|
||||||
"react-dom",
|
|
||||||
"react-color",
|
|
||||||
"react-file-reader-input",
|
|
||||||
"react-collapse",
|
|
||||||
"react-height",
|
|
||||||
"react-icon-base",
|
|
||||||
"react-motion",
|
|
||||||
"react-sortable-hoc",
|
|
||||||
"request",
|
|
||||||
//TODO: Icons raise multi vendor errors?
|
|
||||||
//"react-icons",
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: OUTPATH,
|
path: OUTPATH,
|
||||||
filename: '[name].[chunkhash].js',
|
filename: '[name].[contenthash].js',
|
||||||
chunkFilename: '[chunkhash].js'
|
chunkFilename: '[contenthash].js'
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.jsx']
|
extensions: ['.js', '.jsx']
|
||||||
@@ -46,7 +25,7 @@ module.exports = {
|
|||||||
noParse: [
|
noParse: [
|
||||||
/mapbox-gl\/dist\/mapbox-gl.js/
|
/mapbox-gl\/dist\/mapbox-gl.js/
|
||||||
],
|
],
|
||||||
loaders
|
rules: rules
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
fs: "empty",
|
fs: "empty",
|
||||||
@@ -55,17 +34,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
new webpack.NoEmitOnErrorsPlugin(),
|
||||||
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[chunkhash].vendor.js' }),
|
|
||||||
new WebpackCleanupPlugin(),
|
new WebpackCleanupPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
NODE_ENV: '"production"'
|
NODE_ENV: '"production"'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new UglifyJsPlugin(),
|
|
||||||
new ExtractTextPlugin('[contenthash].css', {
|
|
||||||
allChunks: true
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: './src/template.html',
|
template: './src/template.html',
|
||||||
title: 'Maputnik'
|
title: 'Maputnik'
|
||||||
@@ -74,8 +48,8 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
from: './src/manifest.json',
|
from: './src/manifest.json',
|
||||||
to: 'manifest.json'
|
to: 'manifest.json'
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
new BundleAnalyzerPlugin({
|
new BundleAnalyzerPlugin({
|
||||||
analyzerMode: 'static',
|
analyzerMode: 'static',
|
||||||
defaultSizes: 'gzip',
|
defaultSizes: 'gzip',
|
||||||
|
|||||||
38
config/webpack.rules.js
Normal file
38
config/webpack.rules.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
test: /\.jsx?$/,
|
||||||
|
exclude: [
|
||||||
|
path.resolve(__dirname, '../node_modules')
|
||||||
|
],
|
||||||
|
use: 'babel-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(eot|ttf|woff|woff2)$/,
|
||||||
|
use: 'file-loader?name=fonts/[name].[ext]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.ico$/,
|
||||||
|
use: 'file-loader?name=[name].[ext]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(svg|gif|jpg|png)$/,
|
||||||
|
use: 'file-loader?name=img/[name].[ext]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /[\/\\](node_modules|global|src)[\/\\].*\.scss$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
"css-loader",
|
||||||
|
"sass-loader"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /[\/\\](node_modules|global|src)[\/\\].*\.css$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
16081
package-lock.json
generated
16081
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
139
package.json
139
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "maputnik",
|
"name": "maputnik",
|
||||||
"version": "1.3.0-beta",
|
"version": "1.6.0",
|
||||||
"description": "A MapboxGL visual style editor",
|
"description": "A MapboxGL visual style editor",
|
||||||
"main": "''",
|
"main": "''",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,9 +9,8 @@
|
|||||||
"test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
|
"test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
|
||||||
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
|
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
|
||||||
"start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
|
"start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
|
||||||
"lint": "eslint --ext js --ext jsx {src,test}",
|
"lint": "eslint --ext js --ext jsx src test",
|
||||||
"lint-styles": "stylelint 'src/styles/*.scss'",
|
"lint-styles": "stylelint \"src/styles/*.scss\""
|
||||||
"nsp": "nsp check --reporter summary"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -21,44 +20,43 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/maputnik/editor#readme",
|
"homepage": "https://github.com/maputnik/editor#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.1.2",
|
"@babel/runtime": "^7.1.2",
|
||||||
"@mapbox/mapbox-gl-style-spec": "^12.0.0",
|
"@mapbox/mapbox-gl-rtl-text": "^0.2.2",
|
||||||
"classnames": "^2.2.5",
|
"@mapbox/mapbox-gl-style-spec": "^13.7.2",
|
||||||
"codemirror": "^5.37.0",
|
"classnames": "^2.2.6",
|
||||||
|
"codemirror": "^5.40.2",
|
||||||
"color": "^3.0.0",
|
"color": "^3.0.0",
|
||||||
|
"detect-browser": "^4.5.0",
|
||||||
"file-saver": "^1.3.8",
|
"file-saver": "^1.3.8",
|
||||||
"github-api": "^3.0.0",
|
|
||||||
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
||||||
"lodash.capitalize": "^4.2.1",
|
"lodash.capitalize": "^4.2.1",
|
||||||
"lodash.clamp": "^4.0.3",
|
"lodash.clamp": "^4.0.3",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"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": "^0.45.0",
|
"mapbox-gl": "^1.2.0",
|
||||||
"mapbox-gl-inspect": "^1.3.1",
|
"mapbox-gl-inspect": "^1.3.1",
|
||||||
"maputnik-design": "github:maputnik/design",
|
"maputnik-design": "github:maputnik/design",
|
||||||
"mousetrap": "^1.6.1",
|
"ol": "^6.0.0-beta.8",
|
||||||
"ol-mapbox-style": "^2.10.1",
|
"ol-mapbox-style": "^5.0.0-beta.2",
|
||||||
"ol": "^4.6.5",
|
"prop-types": "^15.6.2",
|
||||||
"prop-types": "^15.6.0",
|
"react": "^16.5.2",
|
||||||
"react": "^16.3.2",
|
"react-aria-menubutton": "^6.0.1",
|
||||||
"react-addons-pure-render-mixin": "^15.6.2",
|
"react-aria-modal": "^3.0.0",
|
||||||
"react-aria-menubutton": "^5.1.1",
|
"react-autobind": "^1.0.6",
|
||||||
"react-aria-modal": "^2.12.1",
|
"react-autocomplete": "^1.8.1",
|
||||||
"react-autocomplete": "^1.7.2",
|
"react-codemirror2": "^5.1.0",
|
||||||
"react-codemirror2": "^4.2.1",
|
|
||||||
"react-collapse": "^4.0.3",
|
"react-collapse": "^4.0.3",
|
||||||
"react-color": "^2.14.1",
|
"react-color": "^2.14.1",
|
||||||
"react-copy-to-clipboard": "^5.0.1",
|
"react-dom": "^16.5.2",
|
||||||
"react-dom": "^16.3.2",
|
"react-file-reader-input": "^2.0.0",
|
||||||
"react-file-reader-input": "^1.1.4",
|
"react-icon-base": "^2.1.2",
|
||||||
"react-height": "^3.0.0",
|
"react-icons": "^3.1.0",
|
||||||
"react-icon-base": "^2.1.1",
|
|
||||||
"react-icons": "^2.2.7",
|
|
||||||
"react-motion": "^0.5.2",
|
"react-motion": "^0.5.2",
|
||||||
"react-sortable-hoc": "^0.6.8",
|
"react-sortable-hoc": "^0.8.3",
|
||||||
"reconnecting-websocket": "^3.2.2",
|
"reconnecting-websocket": "^3.2.2",
|
||||||
"request": "^2.85.0",
|
"slugify": "^1.3.1",
|
||||||
"url": "^0.11.0"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"jshintConfig": {
|
"jshintConfig": {
|
||||||
@@ -102,58 +100,51 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.26.3",
|
"@babel/core": "^7.1.2",
|
||||||
"babel-eslint": "^8.2.3",
|
"@babel/plugin-proposal-class-properties": "^7.1.0",
|
||||||
"babel-loader": "7.1.4",
|
"@babel/plugin-transform-runtime": "^7.1.0",
|
||||||
"babel-plugin-istanbul": "^4.1.6",
|
"@babel/preset-env": "^7.1.0",
|
||||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
"@babel/preset-flow": "^7.0.0",
|
||||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"babel-plugin-transform-flow-strip-types": "^6.22.0",
|
"@wdio/cli": "^5.10.4",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
"@wdio/local-runner": "^5.10.4",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"@wdio/mocha-framework": "^5.10.1",
|
||||||
"babel-preset-env": "^1.6.1",
|
"@wdio/selenium-standalone-service": "^5.9.3",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"@wdio/spec-reporter": "^5.9.3",
|
||||||
"babel-preset-flow": "^6.23.0",
|
"@wdio/sync": "^5.10.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-register": "^6.26.0",
|
"babel-loader": "8.0.4",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-plugin-istanbul": "^5.0.1",
|
||||||
"base64-loader": "^1.0.0",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"copy-webpack-plugin": "^4.5.1",
|
|
||||||
"cors": "^2.8.4",
|
"cors": "^2.8.4",
|
||||||
"cross-env": "^5.1.4",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^1.0.0",
|
||||||
"eslint": "^4.19.1",
|
"eslint": "^5.6.1",
|
||||||
"eslint-plugin-react": "^7.4.0",
|
"eslint-plugin-react": "^7.11.1",
|
||||||
"express": "^4.16.3",
|
"express": "^4.17.1",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"file-loader": "^2.0.0",
|
||||||
"file-loader": "^1.1.5",
|
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"is-docker": "^1.1.0",
|
"is-docker": "^2.0.0",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"istanbul-lib-coverage": "^1.2.0",
|
"istanbul-lib-coverage": "^2.0.1",
|
||||||
"json-loader": "^0.5.7",
|
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mocha": "^5.1.1",
|
"mocha": "^6.1.4",
|
||||||
"node-sass": "^4.9.0",
|
"node-sass": "^4.12.0",
|
||||||
"nsp": "^3.1.0",
|
"raw-loader": "^0.5.1",
|
||||||
"react-hot-loader": "^3.1.1",
|
"react-hot-loader": "^4.3.11",
|
||||||
"sass-loader": "^7.0.1",
|
"sass-loader": "^7.1.0",
|
||||||
"selenium-standalone": "^6.14.0",
|
"selenium-standalone": "^6.16.0",
|
||||||
"style-loader": "^0.20.3",
|
"style-loader": "^0.23.0",
|
||||||
"stylelint": "^9.2.0",
|
"stylelint": "^10.0.0",
|
||||||
"stylelint-config-recommended-scss": "^3.2.0",
|
"stylelint-config-recommended-scss": "^3.2.0",
|
||||||
"stylelint-scss": "^3.0.0",
|
"stylelint-scss": "^3.5.4",
|
||||||
"transform-loader": "^0.2.4",
|
"transform-loader": "^0.2.4",
|
||||||
"uglifyjs-webpack-plugin": "^1.2.4",
|
"uuid": "^3.3.2",
|
||||||
"uuid": "^3.1.0",
|
"webdriverio": "^5.10.4",
|
||||||
"wdio-mocha-framework": "^0.5.13",
|
"webpack": "^4.20.2",
|
||||||
"wdio-phantomjs-service": "^0.2.2",
|
"webpack-bundle-analyzer": "^3.0.2",
|
||||||
"wdio-selenium-standalone-service": "0.0.10",
|
|
||||||
"wdio-spec-reporter": "^0.1.2",
|
|
||||||
"webdriverio": "^4.12.0",
|
|
||||||
"webpack": "^3.8.1",
|
|
||||||
"webpack-bundle-analyzer": "^2.9.0",
|
|
||||||
"webpack-cleanup-plugin": "^0.5.1",
|
"webpack-cleanup-plugin": "^0.5.1",
|
||||||
"webpack-dev-server": "^2.9.4"
|
"webpack-cli": "^3.1.2",
|
||||||
|
"webpack-dev-server": "^3.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
import autoBind from 'react-autobind';
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Mousetrap from 'mousetrap'
|
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
import cloneDeep from 'lodash.clonedeep'
|
||||||
import clamp from 'lodash.clamp'
|
import clamp from 'lodash.clamp'
|
||||||
|
import get from 'lodash.get'
|
||||||
import {arrayMove} from 'react-sortable-hoc'
|
import {arrayMove} from 'react-sortable-hoc'
|
||||||
import url from 'url'
|
import url from 'url'
|
||||||
|
|
||||||
import MapboxGlMap from './map/MapboxGlMap'
|
import MapboxGlMap from './map/MapboxGlMap'
|
||||||
import OpenLayers3Map from './map/OpenLayers3Map'
|
import OpenLayersMap from './map/OpenLayersMap'
|
||||||
import LayerList from './layers/LayerList'
|
import LayerList from './layers/LayerList'
|
||||||
import LayerEditor from './layers/LayerEditor'
|
import LayerEditor from './layers/LayerEditor'
|
||||||
import Toolbar from './Toolbar'
|
import Toolbar from './Toolbar'
|
||||||
@@ -19,13 +20,14 @@ import SourcesModal from './modals/SourcesModal'
|
|||||||
import OpenModal from './modals/OpenModal'
|
import OpenModal from './modals/OpenModal'
|
||||||
import ShortcutsModal from './modals/ShortcutsModal'
|
import ShortcutsModal from './modals/ShortcutsModal'
|
||||||
import SurveyModal from './modals/SurveyModal'
|
import SurveyModal from './modals/SurveyModal'
|
||||||
|
import DebugModal from './modals/DebugModal'
|
||||||
|
|
||||||
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest, validate} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import style from '../libs/style.js'
|
import style from '../libs/style'
|
||||||
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
|
import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
|
||||||
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
||||||
import { loadDefaultStyle, StyleStore } from '../libs/stylestore'
|
import { StyleStore } from '../libs/stylestore'
|
||||||
import { ApiStyleStore } from '../libs/apistore'
|
import { ApiStyleStore } from '../libs/apistore'
|
||||||
import { RevisionStore } from '../libs/revisions'
|
import { RevisionStore } from '../libs/revisions'
|
||||||
import LayerWatcher from '../libs/layerwatcher'
|
import LayerWatcher from '../libs/layerwatcher'
|
||||||
@@ -35,9 +37,41 @@ import Debug from '../libs/debug'
|
|||||||
import queryUtil from '../libs/query-util'
|
import queryUtil from '../libs/query-util'
|
||||||
|
|
||||||
import MapboxGl from 'mapbox-gl'
|
import MapboxGl from 'mapbox-gl'
|
||||||
import mapboxUtil from 'mapbox-gl/src/util/mapbox'
|
|
||||||
|
|
||||||
|
|
||||||
|
// Similar functionality as <https://github.com/mapbox/mapbox-gl-js/blob/7e30aadf5177486c2cfa14fe1790c60e217b5e56/src/util/mapbox.js>
|
||||||
|
function normalizeSourceURL (url, apiToken="") {
|
||||||
|
const matches = url.match(/^mapbox:\/\/(.*)/);
|
||||||
|
if (matches) {
|
||||||
|
// mapbox://mapbox.mapbox-streets-v7
|
||||||
|
return `https://api.mapbox.com/v4/${matches[1]}.json?secure&access_token=${apiToken}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFetchAccessToken(url, mapStyle) {
|
||||||
|
const matchesTilehosting = url.match(/\.tilehosting\.com/);
|
||||||
|
const matchesMaptiler = url.match(/\.maptiler\.com/);
|
||||||
|
const matchesThunderforest = url.match(/\.thunderforest\.com/);
|
||||||
|
if (matchesTilehosting || matchesMaptiler) {
|
||||||
|
const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true})
|
||||||
|
if (accessToken) {
|
||||||
|
return url.replace('{key}', accessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (matchesThunderforest) {
|
||||||
|
const accessToken = style.getAccessToken("thunderforest", mapStyle, {allowFallback: true})
|
||||||
|
if (accessToken) {
|
||||||
|
return url.replace('{key}', accessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateRootSpec(spec, fieldName, newValues) {
|
function updateRootSpec(spec, fieldName, newValues) {
|
||||||
return {
|
return {
|
||||||
...spec,
|
...spec,
|
||||||
@@ -54,89 +88,100 @@ function updateRootSpec(spec, fieldName, newValues) {
|
|||||||
export default class App extends React.Component {
|
export default class App extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
autoBind(this);
|
||||||
|
|
||||||
this.revisionStore = new RevisionStore()
|
this.revisionStore = new RevisionStore()
|
||||||
|
const params = new URLSearchParams(window.location.search.substring(1))
|
||||||
|
let port = params.get("localport")
|
||||||
|
if (port == null && (window.location.port != 80 && window.location.port != 443)) {
|
||||||
|
port = window.location.port
|
||||||
|
}
|
||||||
this.styleStore = new ApiStyleStore({
|
this.styleStore = new ApiStyleStore({
|
||||||
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
|
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false),
|
||||||
|
port: port,
|
||||||
|
host: params.get("localhost")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const keyCodes = {
|
|
||||||
"esc": 27,
|
|
||||||
"?": 191,
|
|
||||||
"o": 79,
|
|
||||||
"e": 69,
|
|
||||||
"s": 83,
|
|
||||||
"d": 68,
|
|
||||||
"i": 73,
|
|
||||||
"m": 77,
|
|
||||||
}
|
|
||||||
|
|
||||||
const shortcuts = [
|
const shortcuts = [
|
||||||
{
|
{
|
||||||
keyCode: keyCodes["?"],
|
key: "?",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.toggleModal("shortcuts");
|
this.toggleModal("shortcuts");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyCode: keyCodes["o"],
|
key: "o",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.toggleModal("open");
|
this.toggleModal("open");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyCode: keyCodes["e"],
|
key: "e",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.toggleModal("export");
|
this.toggleModal("export");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyCode: keyCodes["d"],
|
key: "d",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.toggleModal("sources");
|
this.toggleModal("sources");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyCode: keyCodes["s"],
|
key: "s",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.toggleModal("settings");
|
this.toggleModal("settings");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyCode: keyCodes["i"],
|
key: "i",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.changeInspectMode();
|
this.setMapState(
|
||||||
|
this.state.mapState === "map" ? "inspect" : "map"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyCode: keyCodes["m"],
|
key: "m",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
document.querySelector(".mapboxgl-canvas").focus();
|
document.querySelector(".mapboxgl-canvas").focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "!",
|
||||||
|
handler: () => {
|
||||||
|
this.toggleModal("debug");
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
document.body.addEventListener("keyup", (e) => {
|
document.body.addEventListener("keyup", (e) => {
|
||||||
if(e.keyCode === keyCodes["esc"]) {
|
if(e.key === "Escape") {
|
||||||
e.target.blur();
|
e.target.blur();
|
||||||
document.body.focus();
|
document.body.focus();
|
||||||
}
|
}
|
||||||
else if(document.activeElement === document.body) {
|
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
|
||||||
const shortcut = shortcuts.find((shortcut) => {
|
const shortcut = shortcuts.find((shortcut) => {
|
||||||
return (shortcut.keyCode === e.keyCode)
|
return (shortcut.key === e.key)
|
||||||
})
|
})
|
||||||
|
|
||||||
if(shortcut) {
|
if(shortcut) {
|
||||||
|
this.setModal("shortcuts", false);
|
||||||
shortcut.handler(e);
|
shortcut.handler(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const styleUrl = initialStyleUrl()
|
const styleUrl = initialStyleUrl()
|
||||||
if(styleUrl) {
|
if(styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?")) {
|
||||||
this.styleStore = new StyleStore()
|
this.styleStore = new StyleStore()
|
||||||
loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle))
|
loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle))
|
||||||
|
removeStyleQuerystring()
|
||||||
} else {
|
} else {
|
||||||
|
if(styleUrl) {
|
||||||
|
removeStyleQuerystring()
|
||||||
|
}
|
||||||
this.styleStore.init(err => {
|
this.styleStore.init(err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
console.log('Falling back to local storage for storing styles')
|
console.log('Falling back to local storage for storing styles')
|
||||||
@@ -165,20 +210,25 @@ export default class App extends React.Component {
|
|||||||
selectedLayerIndex: 0,
|
selectedLayerIndex: 0,
|
||||||
sources: {},
|
sources: {},
|
||||||
vectorLayers: {},
|
vectorLayers: {},
|
||||||
inspectModeEnabled: false,
|
mapState: "map",
|
||||||
spec: styleSpec.latest,
|
spec: latest,
|
||||||
isOpen: {
|
isOpen: {
|
||||||
settings: false,
|
settings: false,
|
||||||
sources: false,
|
sources: false,
|
||||||
open: false,
|
open: false,
|
||||||
shortcuts: false,
|
shortcuts: false,
|
||||||
export: false,
|
export: false,
|
||||||
survey: localStorage.hasOwnProperty('survey') ? false : true
|
survey: localStorage.hasOwnProperty('survey') ? false : true,
|
||||||
|
debug: false,
|
||||||
},
|
},
|
||||||
mapOptions: {
|
mapboxGlDebugOptions: {
|
||||||
showTileBoundaries: queryUtil.asBool(queryObj, "show-tile-boundaries")
|
showTileBoundaries: false,
|
||||||
|
showCollisionBoxes: false,
|
||||||
|
showOverdrawInspector: false,
|
||||||
|
},
|
||||||
|
openlayersDebugOptions: {
|
||||||
|
debugToolbox: false,
|
||||||
},
|
},
|
||||||
mapFilter: queryObj["color-blindness-emulation"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.layerWatcher = new LayerWatcher({
|
this.layerWatcher = new LayerWatcher({
|
||||||
@@ -186,14 +236,35 @@ export default class App extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
|
||||||
|
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.onRedo(e);
|
||||||
|
}
|
||||||
|
else if(e.metaKey && e.keyCode === 90) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.onUndo(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(e.ctrlKey && e.keyCode === 90) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.onUndo(e);
|
||||||
|
}
|
||||||
|
else if(e.ctrlKey && e.keyCode === 89) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.onRedo(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
Mousetrap.bind(['mod+z'], this.onUndo.bind(this));
|
window.addEventListener("keydown", this.handleKeyPress);
|
||||||
Mousetrap.bind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
Mousetrap.unbind(['mod+z'], this.onUndo.bind(this));
|
window.removeEventListener("keydown", this.handleKeyPress);
|
||||||
Mousetrap.unbind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveStyle(snapshotStyle) {
|
saveStyle(snapshotStyle) {
|
||||||
@@ -216,9 +287,30 @@ export default class App extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onStyleChanged(newStyle, save=true) {
|
onChangeMetadataProperty = (property, value) => {
|
||||||
|
// If we're changing renderer reset the map state.
|
||||||
|
if (
|
||||||
|
property === 'maputnik:renderer' &&
|
||||||
|
value !== get(this.state.mapStyle, ['metadata', 'maputnik:renderer'], 'mbgljs')
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
mapState: 'map'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const errors = styleSpec.validate(newStyle, styleSpec.latest)
|
const changedStyle = {
|
||||||
|
...this.state.mapStyle,
|
||||||
|
metadata: {
|
||||||
|
...this.state.mapStyle.metadata,
|
||||||
|
[property]: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.onStyleChanged(changedStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
onStyleChanged = (newStyle, save=true) => {
|
||||||
|
|
||||||
|
const errors = validate(newStyle, latest)
|
||||||
if(errors.length === 0) {
|
if(errors.length === 0) {
|
||||||
|
|
||||||
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
|
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
|
||||||
@@ -243,7 +335,7 @@ export default class App extends React.Component {
|
|||||||
this.fetchSources();
|
this.fetchSources();
|
||||||
}
|
}
|
||||||
|
|
||||||
onUndo() {
|
onUndo = () => {
|
||||||
const activeStyle = this.revisionStore.undo()
|
const activeStyle = this.revisionStore.undo()
|
||||||
const messages = undoMessages(this.state.mapStyle, activeStyle)
|
const messages = undoMessages(this.state.mapStyle, activeStyle)
|
||||||
this.saveStyle(activeStyle)
|
this.saveStyle(activeStyle)
|
||||||
@@ -253,7 +345,7 @@ export default class App extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onRedo() {
|
onRedo = () => {
|
||||||
const activeStyle = this.revisionStore.redo()
|
const activeStyle = this.revisionStore.redo()
|
||||||
const messages = redoMessages(this.state.mapStyle, activeStyle)
|
const messages = redoMessages(this.state.mapStyle, activeStyle)
|
||||||
this.saveStyle(activeStyle)
|
this.saveStyle(activeStyle)
|
||||||
@@ -263,7 +355,7 @@ export default class App extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMoveLayer(move) {
|
onMoveLayer = (move) => {
|
||||||
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);
|
||||||
@@ -281,7 +373,7 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(layers);
|
this.onLayersChange(layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayersChange(changedLayers) {
|
onLayersChange = (changedLayers) => {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.state.mapStyle,
|
...this.state.mapStyle,
|
||||||
layers: changedLayers
|
layers: changedLayers
|
||||||
@@ -289,7 +381,7 @@ export default class App extends React.Component {
|
|||||||
this.onStyleChanged(changedStyle)
|
this.onStyleChanged(changedStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerDestroy(layerId) {
|
onLayerDestroy = (layerId) => {
|
||||||
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)
|
const idx = style.indexOfLayer(remainingLayers, layerId)
|
||||||
@@ -297,7 +389,7 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(remainingLayers);
|
this.onLayersChange(remainingLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerCopy(layerId) {
|
onLayerCopy = (layerId) => {
|
||||||
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 idx = style.indexOfLayer(changedLayers, layerId)
|
||||||
@@ -308,7 +400,7 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerVisibilityToggle(layerId) {
|
onLayerVisibilityToggle = (layerId) => {
|
||||||
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 idx = style.indexOfLayer(changedLayers, layerId)
|
||||||
@@ -323,7 +415,7 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onLayerIdChange(oldId, newId) {
|
onLayerIdChange = (oldId, newId) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||||
const idx = style.indexOfLayer(changedLayers, oldId)
|
const idx = style.indexOfLayer(changedLayers, oldId)
|
||||||
|
|
||||||
@@ -335,7 +427,7 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerChanged(layer) {
|
onLayerChanged = (layer) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||||
const idx = style.indexOfLayer(changedLayers, layer.id)
|
const idx = style.indexOfLayer(changedLayers, layer.id)
|
||||||
changedLayers[idx] = layer
|
changedLayers[idx] = layer
|
||||||
@@ -343,12 +435,33 @@ export default class App extends React.Component {
|
|||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeInspectMode() {
|
setMapState = (newState) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
inspectModeEnabled: !this.state.inspectModeEnabled
|
mapState: newState
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDefaultValues = (styleObj) => {
|
||||||
|
const metadata = styleObj.metadata || {}
|
||||||
|
if(metadata['maputnik:renderer'] === undefined) {
|
||||||
|
const changedStyle = {
|
||||||
|
...styleObj,
|
||||||
|
metadata: {
|
||||||
|
...styleObj.metadata,
|
||||||
|
'maputnik:renderer': 'mbgljs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changedStyle
|
||||||
|
} else {
|
||||||
|
return styleObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openStyle = (styleObj) => {
|
||||||
|
styleObj = this.setDefaultValues(styleObj)
|
||||||
|
this.onStyleChanged(styleObj)
|
||||||
|
}
|
||||||
|
|
||||||
fetchSources() {
|
fetchSources() {
|
||||||
const sourceList = {...this.state.sources};
|
const sourceList = {...this.state.sources};
|
||||||
|
|
||||||
@@ -365,12 +478,20 @@ export default class App extends React.Component {
|
|||||||
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) {
|
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) {
|
||||||
let url = val.url;
|
let url = val.url;
|
||||||
try {
|
try {
|
||||||
url = mapboxUtil.normalizeSourceURL(url, MapboxGl.accessToken);
|
url = normalizeSourceURL(url, MapboxGl.accessToken);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn("Failed to normalizeSourceURL: ", err);
|
console.warn("Failed to normalizeSourceURL: ", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url)
|
try {
|
||||||
|
url = setFetchAccessToken(url, this.state.mapStyle)
|
||||||
|
} catch(err) {
|
||||||
|
console.warn("Failed to setFetchAccessToken: ", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
mode: 'cors',
|
||||||
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
@@ -405,56 +526,93 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getRenderer () {
|
||||||
|
const metadata = this.state.mapStyle.metadata || {};
|
||||||
|
return metadata['maputnik:renderer'] || 'mbgljs';
|
||||||
|
}
|
||||||
|
|
||||||
mapRenderer() {
|
mapRenderer() {
|
||||||
|
const metadata = this.state.mapStyle.metadata || {};
|
||||||
|
|
||||||
const mapProps = {
|
const mapProps = {
|
||||||
mapStyle: style.replaceAccessToken(this.state.mapStyle, {allowFallback: true}),
|
mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}),
|
||||||
options: this.state.mapOptions,
|
|
||||||
onDataChange: (e) => {
|
onDataChange: (e) => {
|
||||||
this.layerWatcher.analyzeMap(e.map)
|
this.layerWatcher.analyzeMap(e.map)
|
||||||
this.fetchSources();
|
this.fetchSources();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = this.state.mapStyle.metadata || {}
|
const renderer = this._getRenderer();
|
||||||
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
|
|
||||||
|
|
||||||
let mapElement;
|
let mapElement;
|
||||||
|
|
||||||
// Check if OL3 code has been loaded?
|
// Check if OL code has been loaded?
|
||||||
if(renderer === 'ol3') {
|
if(renderer === 'ol') {
|
||||||
mapElement = <OpenLayers3Map {...mapProps} />
|
mapElement = <OpenLayersMap
|
||||||
|
{...mapProps}
|
||||||
|
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
|
||||||
|
onLayerSelect={this.onLayerSelect}
|
||||||
|
/>
|
||||||
} else {
|
} else {
|
||||||
mapElement = <MapboxGlMap {...mapProps}
|
mapElement = <MapboxGlMap {...mapProps}
|
||||||
inspectModeEnabled={this.state.inspectModeEnabled}
|
options={this.state.mapboxGlDebugOptions}
|
||||||
|
inspectModeEnabled={this.state.mapState === "inspect"}
|
||||||
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
||||||
onLayerSelect={this.onLayerSelect.bind(this)} />
|
onLayerSelect={this.onLayerSelect} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const elementStyle = {
|
let filterName;
|
||||||
"filter": `url('#${this.state.mapFilter}')`
|
if(this.state.mapState.match(/^filter-/)) {
|
||||||
|
filterName = this.state.mapState.replace(/^filter-/, "");
|
||||||
|
}
|
||||||
|
const elementStyle = {};
|
||||||
|
if (filterName) {
|
||||||
|
elementStyle.filter = `url('#${filterName}')`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div style={elementStyle}>
|
return <div style={elementStyle} className="maputnik-map__container">
|
||||||
{mapElement}
|
{mapElement}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerSelect(layerId) {
|
onLayerSelect = (layerId) => {
|
||||||
const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
|
const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
|
||||||
this.setState({ selectedLayerIndex: idx })
|
this.setState({ selectedLayerIndex: idx })
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModal(modalName) {
|
setModal(modalName, value) {
|
||||||
|
if(modalName === 'survey' && value === false) {
|
||||||
|
localStorage.setItem('survey', '');
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpen: {
|
isOpen: {
|
||||||
...this.state.isOpen,
|
...this.state.isOpen,
|
||||||
[modalName]: !this.state.isOpen[modalName]
|
[modalName]: value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if(modalName === 'survey') {
|
toggleModal(modalName) {
|
||||||
localStorage.setItem('survey', '');
|
this.setModal(modalName, !this.state.isOpen[modalName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeOpenlayersDebug = (key, value) => {
|
||||||
|
this.setState({
|
||||||
|
openlayersDebugOptions: {
|
||||||
|
...this.state.openlayersDebugOptions,
|
||||||
|
[key]: value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeMaboxGlDebug = (key, value) => {
|
||||||
|
this.setState({
|
||||||
|
mapboxGlDebugOptions: {
|
||||||
|
...this.state.mapboxGlDebugOptions,
|
||||||
|
[key]: value,
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -463,22 +621,24 @@ export default class App extends React.Component {
|
|||||||
const metadata = this.state.mapStyle.metadata || {}
|
const metadata = this.state.mapStyle.metadata || {}
|
||||||
|
|
||||||
const toolbar = <Toolbar
|
const toolbar = <Toolbar
|
||||||
|
renderer={this._getRenderer()}
|
||||||
|
mapState={this.state.mapState}
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
inspectModeEnabled={this.state.inspectModeEnabled}
|
inspectModeEnabled={this.state.mapState === "inspect"}
|
||||||
sources={this.state.sources}
|
sources={this.state.sources}
|
||||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
onStyleChanged={this.onStyleChanged}
|
||||||
onStyleOpen={this.onStyleChanged.bind(this)}
|
onStyleOpen={this.onStyleChanged}
|
||||||
onInspectModeToggle={this.changeInspectMode.bind(this)}
|
onSetMapState={this.setMapState}
|
||||||
onToggleModal={this.toggleModal.bind(this)}
|
onToggleModal={this.toggleModal.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
const layerList = <LayerList
|
const layerList = <LayerList
|
||||||
onMoveLayer={this.onMoveLayer.bind(this)}
|
onMoveLayer={this.onMoveLayer}
|
||||||
onLayerDestroy={this.onLayerDestroy.bind(this)}
|
onLayerDestroy={this.onLayerDestroy}
|
||||||
onLayerCopy={this.onLayerCopy.bind(this)}
|
onLayerCopy={this.onLayerCopy}
|
||||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
|
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
||||||
onLayersChange={this.onLayersChange.bind(this)}
|
onLayersChange={this.onLayersChange}
|
||||||
onLayerSelect={this.onLayerSelect.bind(this)}
|
onLayerSelect={this.onLayerSelect}
|
||||||
selectedLayerIndex={this.state.selectedLayerIndex}
|
selectedLayerIndex={this.state.selectedLayerIndex}
|
||||||
layers={layers}
|
layers={layers}
|
||||||
sources={this.state.sources}
|
sources={this.state.sources}
|
||||||
@@ -492,12 +652,12 @@ export default class App extends React.Component {
|
|||||||
sources={this.state.sources}
|
sources={this.state.sources}
|
||||||
vectorLayers={this.state.vectorLayers}
|
vectorLayers={this.state.vectorLayers}
|
||||||
spec={this.state.spec}
|
spec={this.state.spec}
|
||||||
onMoveLayer={this.onMoveLayer.bind(this)}
|
onMoveLayer={this.onMoveLayer}
|
||||||
onLayerChanged={this.onLayerChanged.bind(this)}
|
onLayerChanged={this.onLayerChanged}
|
||||||
onLayerDestroy={this.onLayerDestroy.bind(this)}
|
onLayerDestroy={this.onLayerDestroy}
|
||||||
onLayerCopy={this.onLayerCopy.bind(this)}
|
onLayerCopy={this.onLayerCopy}
|
||||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
|
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
||||||
onLayerIdChange={this.onLayerIdChange.bind(this)}
|
onLayerIdChange={this.onLayerIdChange}
|
||||||
/> : null
|
/> : null
|
||||||
|
|
||||||
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
|
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
|
||||||
@@ -507,30 +667,42 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
|
|
||||||
const modals = <div>
|
const modals = <div>
|
||||||
|
<DebugModal
|
||||||
|
renderer={this._getRenderer()}
|
||||||
|
mapboxGlDebugOptions={this.state.mapboxGlDebugOptions}
|
||||||
|
openlayersDebugOptions={this.state.openlayersDebugOptions}
|
||||||
|
onChangeMaboxGlDebug={this.onChangeMaboxGlDebug}
|
||||||
|
onChangeOpenlayersDebug={this.onChangeOpenlayersDebug}
|
||||||
|
isOpen={this.state.isOpen.debug}
|
||||||
|
onOpenToggle={this.toggleModal.bind(this, 'debug')}
|
||||||
|
/>
|
||||||
<ShortcutsModal
|
<ShortcutsModal
|
||||||
|
ref={(el) => this.shortcutEl = el}
|
||||||
isOpen={this.state.isOpen.shortcuts}
|
isOpen={this.state.isOpen.shortcuts}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
|
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
|
||||||
/>
|
/>
|
||||||
<SettingsModal
|
<SettingsModal
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
onStyleChanged={this.onStyleChanged}
|
||||||
|
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}
|
||||||
/>
|
/>
|
||||||
<ExportModal
|
<ExportModal
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
onStyleChanged={this.onStyleChanged}
|
||||||
isOpen={this.state.isOpen.export}
|
isOpen={this.state.isOpen.export}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'export')}
|
onOpenToggle={this.toggleModal.bind(this, 'export')}
|
||||||
/>
|
/>
|
||||||
<OpenModal
|
<OpenModal
|
||||||
isOpen={this.state.isOpen.open}
|
isOpen={this.state.isOpen.open}
|
||||||
onStyleOpen={this.onStyleChanged.bind(this)}
|
onStyleOpen={this.openStyle}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'open')}
|
onOpenToggle={this.toggleModal.bind(this, 'open')}
|
||||||
/>
|
/>
|
||||||
<SourcesModal
|
<SourcesModal
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
onStyleChanged={this.onStyleChanged}
|
||||||
isOpen={this.state.isOpen.sources}
|
isOpen={this.state.isOpen.sources}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ class Button extends React.Component {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <button
|
return <button
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
|
disabled={this.props.disabled}
|
||||||
aria-label={this.props["aria-label"]}
|
aria-label={this.props["aria-label"]}
|
||||||
className={classnames("maputnik-button", this.props.className)}
|
className={classnames("maputnik-button", this.props.className)}
|
||||||
data-wd-key={this.props["data-wd-key"]}
|
data-wd-key={this.props["data-wd-key"]}
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import FileReaderInput from 'react-file-reader-input'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import {detect} from 'detect-browser';
|
||||||
|
|
||||||
|
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md'
|
||||||
|
|
||||||
import MdFileDownload from 'react-icons/lib/md/file-download'
|
|
||||||
import MdFileUpload from 'react-icons/lib/md/file-upload'
|
|
||||||
import OpenIcon from 'react-icons/lib/md/open-in-browser'
|
|
||||||
import SettingsIcon from 'react-icons/lib/md/settings'
|
|
||||||
import MdInfo from 'react-icons/lib/md/info'
|
|
||||||
import SourcesIcon from 'react-icons/lib/md/layers'
|
|
||||||
import MdSave from 'react-icons/lib/md/save'
|
|
||||||
import MdStyle from 'react-icons/lib/md/style'
|
|
||||||
import MdMap from 'react-icons/lib/md/map'
|
|
||||||
import MdInsertEmoticon from 'react-icons/lib/md/insert-emoticon'
|
|
||||||
import MdFontDownload from 'react-icons/lib/md/font-download'
|
|
||||||
import HelpIcon from 'react-icons/lib/md/help-outline'
|
|
||||||
import InspectionIcon from 'react-icons/lib/md/find-in-page'
|
|
||||||
import SurveyIcon from 'react-icons/lib/md/assignment-turned-in'
|
|
||||||
|
|
||||||
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
||||||
import pkgJson from '../../package.json'
|
import pkgJson from '../../package.json'
|
||||||
|
|
||||||
import style from '../libs/style'
|
|
||||||
|
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
||||||
|
const browser = detect();
|
||||||
|
const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser.name) > -1;
|
||||||
|
|
||||||
|
|
||||||
class IconText extends React.Component {
|
class IconText extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -75,6 +67,22 @@ class ToolbarLinkHighlighted extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ToolbarSelect extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
wdKey: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div
|
||||||
|
className='maputnik-toolbar-select'
|
||||||
|
data-wd-key={this.props.wdKey}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ToolbarAction extends React.Component {
|
class ToolbarAction extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
@@ -102,25 +110,64 @@ export default class Toolbar extends React.Component {
|
|||||||
onStyleOpen: PropTypes.func.isRequired,
|
onStyleOpen: PropTypes.func.isRequired,
|
||||||
// A dict of source id's and the available source layers
|
// A dict of source id's and the available source layers
|
||||||
sources: PropTypes.object.isRequired,
|
sources: PropTypes.object.isRequired,
|
||||||
onInspectModeToggle: PropTypes.func.isRequired,
|
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
onToggleModal: PropTypes.func,
|
onToggleModal: PropTypes.func,
|
||||||
|
onSetMapState: PropTypes.func,
|
||||||
|
mapState: PropTypes.string,
|
||||||
|
renderer: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props)
|
isOpen: {
|
||||||
this.state = {
|
settings: false,
|
||||||
isOpen: {
|
sources: false,
|
||||||
settings: false,
|
open: false,
|
||||||
sources: false,
|
add: false,
|
||||||
open: false,
|
export: false,
|
||||||
add: false,
|
|
||||||
export: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSelection(val) {
|
||||||
|
this.props.onSetMapState(val);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const views = [
|
||||||
|
{
|
||||||
|
id: "map",
|
||||||
|
title: "Map",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "inspect",
|
||||||
|
title: "Inspect",
|
||||||
|
disabled: this.props.renderer !== 'mbgljs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "filter-deuteranopia",
|
||||||
|
title: "Map (deuteranopia)",
|
||||||
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "filter-protanopia",
|
||||||
|
title: "Map (protanopia)",
|
||||||
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "filter-tritanopia",
|
||||||
|
title: "Map (tritanopia)",
|
||||||
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "filter-achromatopsia",
|
||||||
|
title: "Map (achromatopsia)",
|
||||||
|
disabled: !colorAccessibilityFiltersEnabled,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentView = views.find((view) => {
|
||||||
|
return view.id === this.props.mapState;
|
||||||
|
});
|
||||||
|
|
||||||
return <div className='maputnik-toolbar'>
|
return <div className='maputnik-toolbar'>
|
||||||
<div className="maputnik-toolbar__inner">
|
<div className="maputnik-toolbar__inner">
|
||||||
<div
|
<div
|
||||||
@@ -136,14 +183,15 @@ export default class Toolbar extends React.Component {
|
|||||||
className="maputnik-toolbar-logo"
|
className="maputnik-toolbar-logo"
|
||||||
>
|
>
|
||||||
<img src={logoImage} alt="Maputnik" />
|
<img src={logoImage} alt="Maputnik" />
|
||||||
<h1>Maputnik
|
<h1>
|
||||||
|
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
|
||||||
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
||||||
</h1>
|
</h1>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-toolbar__actions">
|
<div className="maputnik-toolbar__actions">
|
||||||
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
||||||
<OpenIcon />
|
<MdOpenInBrowser />
|
||||||
<IconText>Open</IconText>
|
<IconText>Open</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
||||||
@@ -151,26 +199,34 @@ export default class Toolbar extends React.Component {
|
|||||||
<IconText>Export</IconText>
|
<IconText>Export</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
||||||
<SourcesIcon />
|
<MdLayers />
|
||||||
<IconText>Data Sources</IconText>
|
<IconText>Data Sources</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
||||||
<SettingsIcon />
|
<MdSettings />
|
||||||
<IconText>Style Settings</IconText>
|
<IconText>Style Settings</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:inspect" onClick={this.props.onInspectModeToggle}>
|
|
||||||
<InspectionIcon />
|
<ToolbarSelect wdKey="nav:inspect">
|
||||||
<IconText>
|
<MdFindInPage />
|
||||||
{ this.props.inspectModeEnabled && <span>Map Mode</span> }
|
<IconText>View </IconText>
|
||||||
{ !this.props.inspectModeEnabled && <span>Inspect Mode</span> }
|
<select onChange={(e) => this.handleSelection(e.target.value)} value={currentView.id}>
|
||||||
</IconText>
|
{views.map((item) => {
|
||||||
</ToolbarAction>
|
return (
|
||||||
|
<option key={item.id} value={item.id} disabled={item.disabled}>
|
||||||
|
{item.title}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</ToolbarSelect>
|
||||||
|
|
||||||
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
|
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
|
||||||
<HelpIcon />
|
<MdHelpOutline />
|
||||||
<IconText>Help</IconText>
|
<IconText>Help</IconText>
|
||||||
</ToolbarLink>
|
</ToolbarLink>
|
||||||
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
|
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
|
||||||
<SurveyIcon />
|
<MdAssignmentTurnedIn />
|
||||||
<IconText>Take the Maputnik Survey</IconText>
|
<IconText>Take the Maputnik Survey</IconText>
|
||||||
</ToolbarLinkHighlighted>
|
</ToolbarLinkHighlighted>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,17 +19,14 @@ class ColorField extends React.Component {
|
|||||||
default: PropTypes.string,
|
default: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props)
|
pickerOpened: false
|
||||||
this.state = {
|
|
||||||
pickerOpened: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: I much rather would do this with absolute positioning
|
//TODO: I much rather would do this with absolute positioning
|
||||||
//but I am too stupid to get it to work together with fixed position
|
//but I am too stupid to get it to work together with fixed position
|
||||||
//and scrollbars so I have to fallback to JavaScript
|
//and scrollbars so I have to fallback to JavaScript
|
||||||
calcPickerOffset() {
|
calcPickerOffset = () => {
|
||||||
const elem = this.colorInput
|
const elem = this.colorInput
|
||||||
if(elem) {
|
if(elem) {
|
||||||
const pos = elem.getBoundingClientRect()
|
const pos = elem.getBoundingClientRect()
|
||||||
@@ -45,7 +42,7 @@ class ColorField extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePicker() {
|
togglePicker = () => {
|
||||||
this.setState({ pickerOpened: !this.state.pickerOpened })
|
this.setState({ pickerOpened: !this.state.pickerOpened })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +57,10 @@ class ColorField extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange (v) {
|
||||||
|
this.props.onChange(v === "" ? undefined : v);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const offset = this.calcPickerOffset()
|
const offset = this.calcPickerOffset()
|
||||||
var currentColor = this.color.object()
|
var currentColor = this.color.object()
|
||||||
@@ -85,7 +86,7 @@ class ColorField extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="maputnik-color-picker-offset"
|
className="maputnik-color-picker-offset"
|
||||||
onClick={this.togglePicker.bind(this)}
|
onClick={this.togglePicker}
|
||||||
style={{
|
style={{
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
@@ -108,12 +109,12 @@ class ColorField extends React.Component {
|
|||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="maputnik-color"
|
className="maputnik-color"
|
||||||
ref={(input) => this.colorInput = input}
|
ref={(input) => this.colorInput = input}
|
||||||
onClick={this.togglePicker.bind(this)}
|
onClick={this.togglePicker}
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
name={this.props.name}
|
name={this.props.name}
|
||||||
placeholder={this.props.default}
|
placeholder={this.props.default}
|
||||||
value={this.props.value ? this.props.value : ""}
|
value={this.props.value ? this.props.value : ""}
|
||||||
onChange={(e) => this.props.onChange(e.target.value)}
|
onChange={(e) => this.onChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default class DocLabel extends React.Component {
|
|||||||
<div className="maputnik-doc-popup">
|
<div className="maputnik-doc-popup">
|
||||||
{this.props.doc}
|
{this.props.doc}
|
||||||
</div>
|
</div>
|
||||||
</div >
|
</div>
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,17 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
addStop() {
|
getFieldFunctionType(fieldSpec) {
|
||||||
|
if (fieldSpec.expression.interpolated) {
|
||||||
|
return "exponential"
|
||||||
|
}
|
||||||
|
if (fieldSpec.type === "number") {
|
||||||
|
return "interval"
|
||||||
|
}
|
||||||
|
return "categorical"
|
||||||
|
}
|
||||||
|
|
||||||
|
addStop = () => {
|
||||||
const stops = this.props.value.stops.slice(0)
|
const stops = this.props.value.stops.slice(0)
|
||||||
const lastStop = stops[stops.length - 1]
|
const lastStop = stops[stops.length - 1]
|
||||||
if (typeof lastStop[0] === "object") {
|
if (typeof lastStop[0] === "object") {
|
||||||
@@ -53,7 +63,7 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
this.props.onChange(this.props.fieldName, changedValue)
|
this.props.onChange(this.props.fieldName, changedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteStop(stopIdx) {
|
deleteStop = (stopIdx) => {
|
||||||
const stops = this.props.value.stops.slice(0)
|
const stops = this.props.value.stops.slice(0)
|
||||||
stops.splice(stopIdx, 1)
|
stops.splice(stopIdx, 1)
|
||||||
|
|
||||||
@@ -69,7 +79,7 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
this.props.onChange(this.props.fieldName, changedValue)
|
this.props.onChange(this.props.fieldName, changedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
makeZoomFunction() {
|
makeZoomFunction = () => {
|
||||||
const zoomFunc = {
|
const zoomFunc = {
|
||||||
stops: [
|
stops: [
|
||||||
[6, this.props.value],
|
[6, this.props.value],
|
||||||
@@ -79,13 +89,15 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
this.props.onChange(this.props.fieldName, zoomFunc)
|
this.props.onChange(this.props.fieldName, zoomFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
makeDataFunction() {
|
makeDataFunction = () => {
|
||||||
|
const functionType = this.getFieldFunctionType(this.props.fieldSpec);
|
||||||
|
const stopValue = functionType === 'categorical' ? '' : 0;
|
||||||
const dataFunc = {
|
const dataFunc = {
|
||||||
property: "",
|
property: "",
|
||||||
type: "categorical",
|
type: functionType,
|
||||||
stops: [
|
stops: [
|
||||||
[{zoom: 6, value: 0}, this.props.value],
|
[{zoom: 6, value: stopValue}, this.props.value || stopValue],
|
||||||
[{zoom: 10, value: 0}, this.props.value]
|
[{zoom: 10, value: stopValue}, this.props.value || stopValue]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.fieldName, dataFunc)
|
this.props.onChange(this.props.fieldName, dataFunc)
|
||||||
@@ -102,8 +114,8 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onDeleteStop={this.deleteStop.bind(this)}
|
onDeleteStop={this.deleteStop}
|
||||||
onAddStop={this.addStop.bind(this)}
|
onAddStop={this.addStop}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -114,8 +126,8 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onDeleteStop={this.deleteStop.bind(this)}
|
onDeleteStop={this.deleteStop}
|
||||||
onAddStop={this.addStop.bind(this)}
|
onAddStop={this.addStop}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -126,8 +138,8 @@ export default class FunctionSpecProperty extends React.Component {
|
|||||||
fieldName={this.props.fieldName}
|
fieldName={this.props.fieldName}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onZoomClick={this.makeZoomFunction.bind(this)}
|
onZoomClick={this.makeZoomFunction}
|
||||||
onDataClick={this.makeDataFunction.bind(this)}
|
onDataClick={this.makeDataFunction}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default class PropertyGroup extends React.Component {
|
|||||||
spec: PropTypes.object.isRequired,
|
spec: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
onPropertyChange(property, newValue) {
|
onPropertyChange = (property, newValue) => {
|
||||||
const group = getGroupName(this.props.spec, this.props.layer.type, property)
|
const group = getGroupName(this.props.spec, this.props.layer.type, property)
|
||||||
this.props.onChange(group , property, newValue)
|
this.props.onChange(group , property, newValue)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ export default class PropertyGroup extends React.Component {
|
|||||||
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
|
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
|
||||||
|
|
||||||
return <FunctionSpecField
|
return <FunctionSpecField
|
||||||
onChange={this.onPropertyChange.bind(this)}
|
onChange={this.onPropertyChange}
|
||||||
key={fieldName}
|
key={fieldName}
|
||||||
fieldName={fieldName}
|
fieldName={fieldName}
|
||||||
value={fieldValue === undefined ? fieldSpec.default : fieldValue}
|
value={fieldValue === undefined ? fieldSpec.default : fieldValue}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import color from 'color'
|
|
||||||
|
|
||||||
import ColorField from './ColorField'
|
import ColorField from './ColorField'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
@@ -82,6 +81,7 @@ export default class SpecField extends React.Component {
|
|||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
case 'formatted':
|
||||||
case 'string':
|
case 'string':
|
||||||
if(iconProperties.indexOf(this.props.fieldName) >= 0) {
|
if(iconProperties.indexOf(this.props.fieldName) >= 0) {
|
||||||
return <IconInput
|
return <IconInput
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default class DataProperty extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFieldFunctionType(fieldSpec) {
|
getFieldFunctionType(fieldSpec) {
|
||||||
if (fieldSpec.function === "interpolated") {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return "exponential"
|
return "exponential"
|
||||||
}
|
}
|
||||||
if (fieldSpec.type === "number") {
|
if (fieldSpec.type === "number") {
|
||||||
@@ -39,8 +39,8 @@ export default class DataProperty extends React.Component {
|
|||||||
return "categorical"
|
return "categorical"
|
||||||
}
|
}
|
||||||
|
|
||||||
getDataFunctionTypes(functionType) {
|
getDataFunctionTypes(fieldSpec) {
|
||||||
if (functionType === "interpolated") {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return ["categorical", "interval", "exponential"]
|
return ["categorical", "interval", "exponential"]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -150,7 +150,7 @@ export default class DataProperty extends React.Component {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
value={this.props.value.type}
|
value={this.props.value.type}
|
||||||
onChange={propVal => this.changeDataProperty("type", propVal)}
|
onChange={propVal => this.changeDataProperty("type", propVal)}
|
||||||
options={this.getDataFunctionTypes(this.props.fieldSpec.function)}
|
options={this.getDataFunctionTypes(this.props.fieldSpec)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
|
|||||||
|
|
||||||
import DocLabel from './DocLabel'
|
import DocLabel from './DocLabel'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import DeleteIcon from 'react-icons/lib/md/delete'
|
import {MdDelete} from 'react-icons/md'
|
||||||
|
|
||||||
|
|
||||||
export default class DeleteStopButton extends React.Component {
|
export default class DeleteStopButton extends React.Component {
|
||||||
@@ -17,7 +17,7 @@ export default class DeleteStopButton extends React.Component {
|
|||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label={<DeleteIcon />}
|
label={<MdDelete />}
|
||||||
doc={"Remove zoom level stop."}
|
doc={"Remove zoom level stop."}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import PropTypes from 'prop-types'
|
|||||||
|
|
||||||
import DocLabel from './DocLabel'
|
import DocLabel from './DocLabel'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import FunctionIcon from 'react-icons/lib/md/functions'
|
import {MdFunctions, MdInsertChart} from 'react-icons/md'
|
||||||
import MdInsertChart from 'react-icons/lib/md/insert-chart'
|
|
||||||
|
|
||||||
|
|
||||||
export default class FunctionButtons extends React.Component {
|
export default class FunctionButtons extends React.Component {
|
||||||
@@ -16,19 +15,19 @@ export default class FunctionButtons extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let makeZoomButton, makeDataButton
|
let makeZoomButton, makeDataButton
|
||||||
if (this.props.fieldSpec['zoom-function']) {
|
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
|
||||||
makeZoomButton = <Button
|
makeZoomButton = <Button
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
onClick={this.props.onZoomClick}
|
onClick={this.props.onZoomClick}
|
||||||
>
|
>
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label={<FunctionIcon />}
|
label={<MdFunctions />}
|
||||||
cursorTargetStyle={{ cursor: 'pointer' }}
|
cursorTargetStyle={{ cursor: 'pointer' }}
|
||||||
doc={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
|
doc={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
if (this.props.fieldSpec['property-function'] && ['piecewise-constant', 'interpolated'].indexOf(this.props.fieldSpec['function']) !== -1) {
|
if (this.props.fieldSpec['property-type'] === 'data-driven') {
|
||||||
makeDataButton = <Button
|
makeDataButton = <Button
|
||||||
className="maputnik-make-data-function"
|
className="maputnik-make-data-function"
|
||||||
onClick={this.props.onDataClick}
|
onClick={this.props.onDataClick}
|
||||||
|
|||||||
@@ -13,6 +13,30 @@ import docUid from '../../libs/document-uid'
|
|||||||
import sortNumerically from '../../libs/sort-numerically'
|
import sortNumerically from '../../libs/sort-numerically'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We cache a reference for each stop by its index.
|
||||||
|
*
|
||||||
|
* When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus.
|
||||||
|
*/
|
||||||
|
function setStopRefs(props, state) {
|
||||||
|
// This is initialsed below only if required to improved performance.
|
||||||
|
let newRefs;
|
||||||
|
|
||||||
|
if(props.value && props.value.stops) {
|
||||||
|
props.value.stops.forEach((val, idx) => {
|
||||||
|
if(!state.refs.hasOwnProperty(idx)) {
|
||||||
|
if(!newRefs) {
|
||||||
|
newRefs = {...state};
|
||||||
|
}
|
||||||
|
newRefs[idx] = docUid("stop-");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default class ZoomProperty extends React.Component {
|
export default class ZoomProperty extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
@@ -29,45 +53,13 @@ export default class ZoomProperty extends React.Component {
|
|||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
constructor() {
|
refs: {}
|
||||||
super()
|
|
||||||
this.state = {
|
|
||||||
refs: {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setState({
|
const newRefs = setStopRefs(this.props, this.state);
|
||||||
refs: this.setStopRefs(this.props)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We cache a reference for each stop by its index.
|
|
||||||
*
|
|
||||||
* When the stops are reordered the references are also updated (see this.orderStops) this allows React to use the same key for the element and keep keyboard focus.
|
|
||||||
*/
|
|
||||||
setStopRefs(props) {
|
|
||||||
// This is initialsed below only if required to improved performance.
|
|
||||||
let newRefs;
|
|
||||||
|
|
||||||
if(props.value && props.value.stops) {
|
|
||||||
props.value.stops.forEach((val, idx) => {
|
|
||||||
if(!this.state.refs.hasOwnProperty(idx)) {
|
|
||||||
if(!newRefs) {
|
|
||||||
newRefs = {...this.state.refs};
|
|
||||||
}
|
|
||||||
newRefs[idx] = docUid("stop-");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return newRefs;
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
||||||
const newRefs = this.setStopRefs(nextProps);
|
|
||||||
if(newRefs) {
|
if(newRefs) {
|
||||||
this.setState({
|
this.setState({
|
||||||
refs: newRefs
|
refs: newRefs
|
||||||
@@ -75,6 +67,16 @@ export default class ZoomProperty extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
const newRefs = setStopRefs(props, state);
|
||||||
|
if(newRefs) {
|
||||||
|
return {
|
||||||
|
refs: newRefs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Order the stops altering the refs to reflect their new position.
|
// Order the stops altering the refs to reflect their new position.
|
||||||
orderStopsByZoom(stops) {
|
orderStopsByZoom(stops) {
|
||||||
const mappedWithRef = stops
|
const mappedWithRef = stops
|
||||||
|
|||||||
@@ -2,16 +2,13 @@ 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 * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import DocLabel from '../fields/DocLabel'
|
import DocLabel from '../fields/DocLabel'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
import SingleFilterEditor from './SingleFilterEditor'
|
import SingleFilterEditor from './SingleFilterEditor'
|
||||||
import FilterEditorBlock from './FilterEditorBlock'
|
import FilterEditorBlock from './FilterEditorBlock'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
|
|
||||||
import DeleteIcon from 'react-icons/lib/md/delete'
|
|
||||||
import AddIcon from 'react-icons/lib/fa/plus'
|
|
||||||
|
|
||||||
function hasCombiningFilter(filter) {
|
function hasCombiningFilter(filter) {
|
||||||
return combiningFilterOps.indexOf(filter[0]) >= 0
|
return combiningFilterOps.indexOf(filter[0]) >= 0
|
||||||
}
|
}
|
||||||
@@ -60,7 +57,7 @@ export default class CombiningFilterEditor extends React.Component {
|
|||||||
this.props.onChange(newFilter)
|
this.props.onChange(newFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
addFilterItem() {
|
addFilterItem = () => {
|
||||||
const newFilterItem = this.combiningFilter().slice(0)
|
const newFilterItem = this.combiningFilter().slice(0)
|
||||||
newFilterItem.push(['==', 'name', ''])
|
newFilterItem.push(['==', 'name', ''])
|
||||||
this.props.onChange(newFilterItem)
|
this.props.onChange(newFilterItem)
|
||||||
@@ -92,7 +89,7 @@ export default class CombiningFilterEditor extends React.Component {
|
|||||||
<div className="maputnik-filter-editor-compound-select" data-wd-key="layer-filter">
|
<div className="maputnik-filter-editor-compound-select" data-wd-key="layer-filter">
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label={"Compound Filter"}
|
label={"Compound Filter"}
|
||||||
doc={styleSpec.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."}
|
||||||
/>
|
/>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
value={combiningOp}
|
value={combiningOp}
|
||||||
@@ -105,7 +102,7 @@ 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.bind(this)}>
|
onClick={this.addFilterItem}>
|
||||||
Add filter
|
Add filter
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import DeleteIcon from 'react-icons/lib/md/delete'
|
import {MdDelete} from 'react-icons/md'
|
||||||
|
|
||||||
class FilterEditorBlock extends React.Component {
|
class FilterEditorBlock extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -16,7 +16,7 @@ class FilterEditorBlock extends React.Component {
|
|||||||
className="maputnik-delete-filter"
|
className="maputnik-delete-filter"
|
||||||
onClick={this.props.onDelete}
|
onClick={this.props.onDelete}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<MdDelete />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-filter-editor-block-content">
|
<div className="maputnik-filter-editor-block-content">
|
||||||
|
|||||||
@@ -14,18 +14,15 @@ class AutocompleteInput extends React.Component {
|
|||||||
keepMenuWithinWindowBounds: PropTypes.bool
|
keepMenuWithinWindowBounds: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
maxHeight: MAX_HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
options: [],
|
options: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
maxHeight: MAX_HEIGHT
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
calcMaxHeight() {
|
calcMaxHeight() {
|
||||||
if(this.props.keepMenuWithinWindowBounds) {
|
if(this.props.keepMenuWithinWindowBounds) {
|
||||||
const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
|
const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
|
||||||
@@ -38,6 +35,7 @@ class AutocompleteInput extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.calcMaxHeight();
|
this.calcMaxHeight();
|
||||||
}
|
}
|
||||||
@@ -46,6 +44,10 @@ class AutocompleteInput extends React.Component {
|
|||||||
this.calcMaxHeight();
|
this.calcMaxHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChange (v) {
|
||||||
|
this.props.onChange(v === "" ? undefined : v);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div
|
return <div
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
@@ -56,7 +58,8 @@ class AutocompleteInput extends React.Component {
|
|||||||
menuStyle={{
|
menuStyle={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
maxHeight: this.state.maxHeight
|
maxHeight: this.state.maxHeight,
|
||||||
|
zIndex: '998'
|
||||||
}}
|
}}
|
||||||
wrapperProps={{
|
wrapperProps={{
|
||||||
className: "maputnik-autocomplete",
|
className: "maputnik-autocomplete",
|
||||||
@@ -69,10 +72,12 @@ class AutocompleteInput extends React.Component {
|
|||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
items={this.props.options}
|
items={this.props.options}
|
||||||
getItemValue={(item) => item[0]}
|
getItemValue={(item) => item[0]}
|
||||||
onSelect={v => this.props.onChange(v)}
|
onSelect={v => this.onChange(v)}
|
||||||
onChange={(e, v) => this.props.onChange(v)}
|
onChange={(e, v) => this.onChange(v)}
|
||||||
shouldItemRender={(item, value) => {
|
shouldItemRender={(item, value="") => {
|
||||||
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
|
if (typeof(value) === "string") {
|
||||||
|
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
renderItem={(item, isHighlighted) => (
|
renderItem={(item, isHighlighted) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
|
|||||||
import StringInput from './StringInput'
|
import StringInput from './StringInput'
|
||||||
import NumberInput from './NumberInput'
|
import NumberInput from './NumberInput'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import DeleteIcon from 'react-icons/lib/md/delete'
|
import {MdDelete} from 'react-icons/md'
|
||||||
import DocLabel from '../fields/DocLabel'
|
import DocLabel from '../fields/DocLabel'
|
||||||
|
|
||||||
|
|
||||||
@@ -27,14 +27,13 @@ class DynamicArrayInput extends React.Component {
|
|||||||
return this.props.value || this.props.default || []
|
return this.props.value || this.props.default || []
|
||||||
}
|
}
|
||||||
|
|
||||||
addValue() {
|
addValue = () => {
|
||||||
const values = this.values.slice(0)
|
const values = this.values.slice(0)
|
||||||
if (this.props.type === 'number') {
|
if (this.props.type === 'number') {
|
||||||
values.push(0)
|
values.push(0)
|
||||||
} else {
|
} else {
|
||||||
values.push("")
|
values.push("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.props.onChange(values)
|
this.props.onChange(values)
|
||||||
}
|
}
|
||||||
@@ -77,7 +76,7 @@ class DynamicArrayInput extends React.Component {
|
|||||||
{inputs}
|
{inputs}
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-array-add-value"
|
className="maputnik-array-add-value"
|
||||||
onClick={this.addValue.bind(this)}
|
onClick={this.addValue}
|
||||||
>
|
>
|
||||||
Add value
|
Add value
|
||||||
</Button>
|
</Button>
|
||||||
@@ -96,7 +95,7 @@ class DeleteValueButton extends React.Component {
|
|||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
<DocLabel
|
<DocLabel
|
||||||
label={<DeleteIcon />}
|
label={<MdDelete />}
|
||||||
doc={"Remove array entry."}
|
doc={"Remove array entry."}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -16,13 +16,25 @@ class FontInput extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get values() {
|
get values() {
|
||||||
return this.props.value || this.props.default.slice(1) || []
|
const out = this.props.value || this.props.default.slice(1) || [""];
|
||||||
|
|
||||||
|
// Always put a "" in the last field to you can keep adding entries
|
||||||
|
if (out[out.length-1] !== ""){
|
||||||
|
return out.concat("");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeFont(idx, newValue) {
|
changeFont(idx, newValue) {
|
||||||
const changedValues = this.values.slice(0)
|
const changedValues = this.values.slice(0)
|
||||||
changedValues[idx] = newValue
|
changedValues[idx] = newValue
|
||||||
this.props.onChange(changedValues)
|
const filteredValues = changedValues
|
||||||
|
.filter(v => v !== undefined)
|
||||||
|
.filter(v => v !== "")
|
||||||
|
|
||||||
|
this.props.onChange(filteredValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import AutocompleteInput from './AutocompleteInput'
|
|||||||
|
|
||||||
class IconInput extends React.Component {
|
class IconInput extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
value: PropTypes.array,
|
value: PropTypes.string,
|
||||||
icons: PropTypes.array,
|
icons: PropTypes.array,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class InputBlock extends React.Component {
|
|||||||
|
|
||||||
onChange(e) {
|
onChange(e) {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
return this.props.onChange(value === "" ? null: value)
|
return this.props.onChange(value === "" ? undefined : value)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -13,23 +13,28 @@ class NumberInput extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
value: props.value
|
editing: false,
|
||||||
|
value: props.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
this.setState({ value: nextProps.value })
|
if (!state.editing) {
|
||||||
|
return {
|
||||||
|
value: props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeValue(newValue) {
|
changeValue(newValue) {
|
||||||
|
this.setState({editing: true});
|
||||||
const value = parseFloat(newValue)
|
const value = parseFloat(newValue)
|
||||||
|
|
||||||
const hasChanged = this.state.value !== value
|
const hasChanged = this.state.value !== value
|
||||||
if(this.isValid(value) && hasChanged) {
|
if(this.isValid(value) && hasChanged) {
|
||||||
this.props.onChange(value)
|
this.props.onChange(value)
|
||||||
} else {
|
|
||||||
this.setState({ value: newValue })
|
|
||||||
}
|
}
|
||||||
|
this.setState({ value: newValue })
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid(v) {
|
isValid(v) {
|
||||||
@@ -49,7 +54,8 @@ class NumberInput extends React.Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
resetValue() {
|
resetValue = () => {
|
||||||
|
this.setState({editing: false});
|
||||||
// Reset explicitly to default value if value has been cleared
|
// Reset explicitly to default value if value has been cleared
|
||||||
if(this.state.value === "") {
|
if(this.state.value === "") {
|
||||||
return this.changeValue(this.props.default)
|
return this.changeValue(this.props.default)
|
||||||
@@ -72,7 +78,7 @@ class NumberInput extends React.Component {
|
|||||||
placeholder={this.props.default}
|
placeholder={this.props.default}
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
onChange={e => this.changeValue(e.target.value)}
|
onChange={e => this.changeValue(e.target.value)}
|
||||||
onBlur={this.resetValue.bind(this)}
|
onBlur={this.resetValue}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,17 @@ class StringInput extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
|
editing: false,
|
||||||
value: props.value || ''
|
value: props.value || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
this.setState({ value: nextProps.value || '' })
|
if (!state.editing) {
|
||||||
|
return {
|
||||||
|
value: props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -49,11 +54,15 @@ class StringInput extends React.Component {
|
|||||||
placeholder: this.props.default,
|
placeholder: this.props.default,
|
||||||
onChange: e => {
|
onChange: e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
editing: true,
|
||||||
value: e.target.value
|
value: e.target.value
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onBlur: () => {
|
onBlur: () => {
|
||||||
if(this.state.value!==this.props.value) this.props.onChange(this.state.value)
|
if(this.state.value!==this.props.value) {
|
||||||
|
this.setState({editing: false});
|
||||||
|
this.props.onChange(this.state.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export default class CollapseAlt extends React.Component {
|
|||||||
children: PropTypes.element.isRequired
|
children: PropTypes.element.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (accessibility.reducedMotionEnabled()) {
|
if (accessibility.reducedMotionEnabled()) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import CollapseOpenIcon from 'react-icons/lib/md/arrow-drop-down'
|
import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md'
|
||||||
import CollapseCloseIcon from 'react-icons/lib/md/arrow-drop-up'
|
|
||||||
|
|
||||||
export default class Collapser extends React.Component {
|
export default class Collapser extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -15,7 +14,7 @@ export default class Collapser extends React.Component {
|
|||||||
height: 20,
|
height: 20,
|
||||||
...this.props.style,
|
...this.props.style,
|
||||||
}
|
}
|
||||||
return this.props.isCollapsed ? <CollapseCloseIcon style={iconStyle}/> : <CollapseOpenIcon style={iconStyle} />
|
return this.props.isCollapsed ? <MdArrowDropUp style={iconStyle}/> : <MdArrowDropDown style={iconStyle} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,20 +29,11 @@ class JSONEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
componentDidUpdate(prevProps) {
|
||||||
this.setState({
|
if (prevProps.layer !== this.props.layer) {
|
||||||
code: JSON.stringify(nextProps.layer, null, 2)
|
this.setState({
|
||||||
})
|
code: JSON.stringify(this.props.layer, null, 2)
|
||||||
}
|
})
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
try {
|
|
||||||
const parsedLayer = JSON.parse(this.state.code)
|
|
||||||
// If the structure is still the same do not update
|
|
||||||
// because it affects editing experience by reformatting all the time
|
|
||||||
return nextState.code !== JSON.stringify(parsedLayer, null, 2)
|
|
||||||
} catch(err) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ import CommentBlock from './CommentBlock'
|
|||||||
import LayerSourceBlock from './LayerSourceBlock'
|
import LayerSourceBlock from './LayerSourceBlock'
|
||||||
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
|
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
|
||||||
|
|
||||||
import MoreVertIcon from 'react-icons/lib/md/more-vert'
|
import {MdMoreVert} from 'react-icons/md'
|
||||||
|
|
||||||
import InputBlock from '../inputs/InputBlock'
|
|
||||||
import MultiButtonInput from '../inputs/MultiButtonInput'
|
|
||||||
|
|
||||||
import { changeType, changeProperty } from '../../libs/layer'
|
import { changeType, changeProperty } from '../../libs/layer'
|
||||||
import layout from '../../config/layout.json'
|
import layout from '../../config/layout.json'
|
||||||
@@ -36,7 +33,7 @@ function layoutGroups(layerType) {
|
|||||||
title: 'JSON Editor',
|
title: 'JSON Editor',
|
||||||
type: 'jsoneditor'
|
type: 'jsoneditor'
|
||||||
}
|
}
|
||||||
return [layerGroup, filterGroup].concat(layout[layerType].groups).concat([editorGroup])
|
return [layerGroup, filterGroup].concat(layout[layerType].groups).concat([editorGroup])
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Layer editor supporting multiple types of layers. */
|
/** Layer editor supporting multiple types of layers. */
|
||||||
@@ -79,18 +76,18 @@ export default class LayerEditor extends React.Component {
|
|||||||
this.state = { editorGroups }
|
this.state = { editorGroups }
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
const additionalGroups = { ...this.state.editorGroups }
|
const additionalGroups = { ...state.editorGroups }
|
||||||
|
|
||||||
layout[nextProps.layer.type].groups.forEach(group => {
|
layout[props.layer.type].groups.forEach(group => {
|
||||||
if(!(group.title in additionalGroups)) {
|
if(!(group.title in additionalGroups)) {
|
||||||
additionalGroups[group.title] = true
|
additionalGroups[group.title] = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setState({
|
return {
|
||||||
editorGroups: additionalGroups
|
editorGroups: additionalGroups
|
||||||
})
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildContext () {
|
getChildContext () {
|
||||||
@@ -257,7 +254,7 @@ export default class LayerEditor extends React.Component {
|
|||||||
closeOnSelection={false}
|
closeOnSelection={false}
|
||||||
>
|
>
|
||||||
<Button className='more-menu__button'>
|
<Button className='more-menu__button'>
|
||||||
<MoreVertIcon className="more-menu__button__svg" />
|
<MdMoreVert className="more-menu__button__svg" />
|
||||||
</Button>
|
</Button>
|
||||||
<Menu>
|
<Menu>
|
||||||
<ul className="more-menu__menu">
|
<ul className="more-menu__menu">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ class LayerIdBlock extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"ID"} doc={styleSpec.latest.layer.id.doc}
|
return <InputBlock label={"ID"} doc={latest.layer.id.doc}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
>
|
>
|
||||||
<StringInput
|
<StringInput
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import Button from '../Button'
|
|
||||||
import LayerListGroup from './LayerListGroup'
|
import LayerListGroup from './LayerListGroup'
|
||||||
import LayerListItem from './LayerListItem'
|
import LayerListItem from './LayerListItem'
|
||||||
import AddIcon from 'react-icons/lib/md/add-circle-outline'
|
|
||||||
import AddModal from '../modals/AddModal'
|
import AddModal from '../modals/AddModal'
|
||||||
|
|
||||||
import style from '../../libs/style.js'
|
import {SortableContainer} from 'react-sortable-hoc';
|
||||||
import {SortableContainer, SortableHandle} from 'react-sortable-hoc';
|
|
||||||
|
|
||||||
const layerListPropTypes = {
|
const layerListPropTypes = {
|
||||||
layers: PropTypes.array.isRequired,
|
layers: PropTypes.array.isRequired,
|
||||||
@@ -38,21 +35,17 @@ function findClosestCommonPrefix(layers, idx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List of collapsible layer editors
|
// List of collapsible layer editors
|
||||||
@SortableContainer
|
|
||||||
class LayerListContainer extends React.Component {
|
class LayerListContainer extends React.Component {
|
||||||
static propTypes = {...layerListPropTypes}
|
static propTypes = {...layerListPropTypes}
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props)
|
collapsedGroups: {},
|
||||||
this.state = {
|
areAllGroupsExpanded: false,
|
||||||
collapsedGroups: {},
|
isOpen: {
|
||||||
areAllGroupsExpanded: false,
|
add: false,
|
||||||
isOpen: {
|
|
||||||
add: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +58,7 @@ class LayerListContainer extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLayers() {
|
toggleLayers = () => {
|
||||||
let idx=0
|
let idx=0
|
||||||
|
|
||||||
let newGroups=[]
|
let newGroups=[]
|
||||||
@@ -73,12 +66,12 @@ class LayerListContainer extends React.Component {
|
|||||||
this.groupedLayers().forEach(layers => {
|
this.groupedLayers().forEach(layers => {
|
||||||
const groupPrefix = layerPrefix(layers[0].id)
|
const groupPrefix = layerPrefix(layers[0].id)
|
||||||
const lookupKey = [groupPrefix, idx].join('-')
|
const lookupKey = [groupPrefix, idx].join('-')
|
||||||
|
|
||||||
|
|
||||||
if (layers.length > 1) {
|
if (layers.length > 1) {
|
||||||
newGroups[lookupKey] = this.state.areAllGroupsExpanded
|
newGroups[lookupKey] = this.state.areAllGroupsExpanded
|
||||||
}
|
}
|
||||||
|
|
||||||
layers.forEach((layer) => {
|
layers.forEach((layer) => {
|
||||||
idx += 1
|
idx += 1
|
||||||
})
|
})
|
||||||
@@ -179,7 +172,7 @@ class LayerListContainer extends React.Component {
|
|||||||
<div className="maputnik-multibutton">
|
<div className="maputnik-multibutton">
|
||||||
<button
|
<button
|
||||||
id="skip-menu"
|
id="skip-menu"
|
||||||
onClick={this.toggleLayers.bind(this)}
|
onClick={this.toggleLayers}
|
||||||
className="maputnik-button">
|
className="maputnik-button">
|
||||||
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
||||||
</button>
|
</button>
|
||||||
@@ -203,12 +196,15 @@ class LayerListContainer extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LayerListContainerSortable = SortableContainer((props) => <LayerListContainer {...props} />)
|
||||||
|
|
||||||
export default class LayerList extends React.Component {
|
export default class LayerList extends React.Component {
|
||||||
static propTypes = {...layerListPropTypes}
|
static propTypes = {...layerListPropTypes}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <LayerListContainer
|
return <LayerListContainerSortable
|
||||||
{...this.props}
|
{...this.props}
|
||||||
|
helperClass='sortableHelper'
|
||||||
onSortEnd={this.props.onMoveLayer.bind(this)}
|
onSortEnd={this.props.onMoveLayer.bind(this)}
|
||||||
useDragHandle={true}
|
useDragHandle={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,55 +1,57 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Color from 'color'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import CopyIcon from 'react-icons/lib/md/content-copy'
|
import {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from 'react-icons/md'
|
||||||
import VisibilityIcon from 'react-icons/lib/md/visibility'
|
|
||||||
import VisibilityOffIcon from 'react-icons/lib/md/visibility-off'
|
|
||||||
import DeleteIcon from 'react-icons/lib/md/delete'
|
|
||||||
|
|
||||||
import LayerIcon from '../icons/LayerIcon'
|
import LayerIcon from '../icons/LayerIcon'
|
||||||
import LayerEditor from './LayerEditor'
|
|
||||||
import {SortableElement, SortableHandle} from 'react-sortable-hoc'
|
import {SortableElement, SortableHandle} from 'react-sortable-hoc'
|
||||||
|
|
||||||
@SortableHandle
|
|
||||||
class LayerTypeDragHandle extends React.Component {
|
|
||||||
static propTypes = LayerIcon.propTypes
|
|
||||||
|
|
||||||
render() {
|
const DraggableLabel = SortableHandle((props) => {
|
||||||
return <LayerIcon
|
return <div className="maputnik-layer-list-item-handle">
|
||||||
{...this.props}
|
<LayerIcon
|
||||||
style={{
|
className="layer-handle__icon"
|
||||||
cursor: 'move',
|
type={props.layerType}
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
paddingRight: 3,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
<span className="maputnik-layer-list-item-id">{props.layerId}</span>
|
||||||
}
|
</div>
|
||||||
|
});
|
||||||
|
|
||||||
class IconAction extends React.Component {
|
class IconAction extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
action: PropTypes.string.isRequired,
|
action: PropTypes.string.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
wdKey: PropTypes.string
|
wdKey: PropTypes.string,
|
||||||
|
classBlockName: PropTypes.string,
|
||||||
|
classBlockModifier: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIcon() {
|
renderIcon() {
|
||||||
switch(this.props.action) {
|
switch(this.props.action) {
|
||||||
case 'duplicate': return <CopyIcon />
|
case 'duplicate': return <MdContentCopy />
|
||||||
case 'show': return <VisibilityIcon />
|
case 'show': return <MdVisibility />
|
||||||
case 'hide': return <VisibilityOffIcon />
|
case 'hide': return <MdVisibilityOff />
|
||||||
case 'delete': return <DeleteIcon />
|
case 'delete': return <MdDelete />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {classBlockName, classBlockModifier} = this.props;
|
||||||
|
|
||||||
|
let classAdditions = '';
|
||||||
|
if (classBlockName) {
|
||||||
|
classAdditions = `maputnik-layer-list-icon-action__${classBlockName}`;
|
||||||
|
|
||||||
|
if (classBlockModifier) {
|
||||||
|
classAdditions += ` maputnik-layer-list-icon-action__${classBlockName}--${classBlockModifier}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <button
|
return <button
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
title={this.props.action}
|
title={this.props.action}
|
||||||
className="maputnik-layer-list-icon-action"
|
className={`maputnik-layer-list-icon-action ${classAdditions}`}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
@@ -58,7 +60,6 @@ class IconAction extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SortableElement
|
|
||||||
class LayerListItem extends React.Component {
|
class LayerListItem extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
layerId: PropTypes.string.isRequired,
|
layerId: PropTypes.string.isRequired,
|
||||||
@@ -92,6 +93,8 @@ class LayerListItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const visibilityAction = this.props.visibility === 'visible' ? 'show' : 'hide';
|
||||||
|
|
||||||
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.layerId)}
|
||||||
@@ -101,26 +104,31 @@ class LayerListItem extends React.Component {
|
|||||||
"maputnik-layer-list-item-selected": this.props.isSelected,
|
"maputnik-layer-list-item-selected": this.props.isSelected,
|
||||||
[this.props.className]: true,
|
[this.props.className]: true,
|
||||||
})}>
|
})}>
|
||||||
<LayerTypeDragHandle type={this.props.layerType} />
|
<DraggableLabel {...this.props} />
|
||||||
<span className="maputnik-layer-list-item-id">{this.props.layerId}</span>
|
|
||||||
<span style={{flexGrow: 1}} />
|
<span style={{flexGrow: 1}} />
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
|
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
|
||||||
action={'delete'}
|
action={'delete'}
|
||||||
|
classBlockName="delete"
|
||||||
onClick={e => this.props.onLayerDestroy(this.props.layerId)}
|
onClick={e => this.props.onLayerDestroy(this.props.layerId)}
|
||||||
/>
|
/>
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
|
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
|
||||||
action={'duplicate'}
|
action={'duplicate'}
|
||||||
|
classBlockName="duplicate"
|
||||||
onClick={e => this.props.onLayerCopy(this.props.layerId)}
|
onClick={e => this.props.onLayerCopy(this.props.layerId)}
|
||||||
/>
|
/>
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
|
wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
|
||||||
action={this.props.visibility === 'visible' ? 'hide' : 'show'}
|
action={visibilityAction}
|
||||||
|
classBlockName="visibility"
|
||||||
|
classBlockModifier={visibilityAction}
|
||||||
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)}
|
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayerListItem;
|
const LayerListItemSortable = SortableElement((props) => <LayerListItem {...props} />);
|
||||||
|
|
||||||
|
export default LayerListItemSortable;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
|
||||||
import AutocompleteInput from '../inputs/AutocompleteInput'
|
import AutocompleteInput from '../inputs/AutocompleteInput'
|
||||||
|
|
||||||
class LayerSourceBlock extends React.Component {
|
class LayerSourceBlock extends React.Component {
|
||||||
@@ -20,7 +19,7 @@ class LayerSourceBlock extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"Source"} doc={styleSpec.latest.layer.source.doc}
|
return <InputBlock label={"Source"} doc={latest.layer.source.doc}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
>
|
>
|
||||||
<AutocompleteInput
|
<AutocompleteInput
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
|
||||||
import AutocompleteInput from '../inputs/AutocompleteInput'
|
import AutocompleteInput from '../inputs/AutocompleteInput'
|
||||||
|
|
||||||
class LayerSourceLayer extends React.Component {
|
class LayerSourceLayer extends React.Component {
|
||||||
@@ -21,7 +20,7 @@ class LayerSourceLayer extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"Source Layer"} doc={styleSpec.latest.layer['source-layer'].doc}
|
return <InputBlock label={"Source Layer"} doc={latest.layer['source-layer'].doc}
|
||||||
data-wd-key="layer-source-layer"
|
data-wd-key="layer-source-layer"
|
||||||
>
|
>
|
||||||
<AutocompleteInput
|
<AutocompleteInput
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ class LayerTypeBlock extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"Type"} doc={styleSpec.latest.layer.type.doc}
|
return <InputBlock label={"Type"} doc={latest.layer.type.doc}
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
>
|
>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
|
|
||||||
@@ -12,15 +12,15 @@ class MaxZoomBlock extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"Max Zoom"} doc={styleSpec.latest.layer.maxzoom.doc}
|
return <InputBlock label={"Max Zoom"} doc={latest.layer.maxzoom.doc}
|
||||||
data-wd-key="max-zoom"
|
data-wd-key="max-zoom"
|
||||||
>
|
>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
min={styleSpec.latest.layer.maxzoom.minimum}
|
min={latest.layer.maxzoom.minimum}
|
||||||
max={styleSpec.latest.layer.maxzoom.maximum}
|
max={latest.layer.maxzoom.maximum}
|
||||||
default={styleSpec.latest.layer.maxzoom.maximum}
|
default={latest.layer.maxzoom.maximum}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
|
|
||||||
@@ -12,15 +12,15 @@ class MinZoomBlock extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"Min Zoom"} doc={styleSpec.latest.layer.minzoom.doc}
|
return <InputBlock label={"Min Zoom"} doc={latest.layer.minzoom.doc}
|
||||||
data-wd-key="min-zoom"
|
data-wd-key="min-zoom"
|
||||||
>
|
>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
min={styleSpec.latest.layer.minzoom.minimum}
|
min={latest.layer.minzoom.minimum}
|
||||||
max={styleSpec.latest.layer.minzoom.maximum}
|
max={latest.layer.minzoom.maximum}
|
||||||
default={styleSpec.latest.layer.minzoom.minimum}
|
default={latest.layer.minzoom.minimum}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
|
||||||
import StringInput from '../inputs/StringInput'
|
|
||||||
import LayerIcon from '../icons/LayerIcon'
|
import LayerIcon from '../icons/LayerIcon'
|
||||||
|
import {latest, expression, function as styleFunction} from '@mapbox/mapbox-gl-style-spec'
|
||||||
|
|
||||||
function groupFeaturesBySourceLayer(features) {
|
function groupFeaturesBySourceLayer(features) {
|
||||||
const sources = {}
|
const sources = {}
|
||||||
@@ -30,7 +29,64 @@ function groupFeaturesBySourceLayer(features) {
|
|||||||
class FeatureLayerPopup extends React.Component {
|
class FeatureLayerPopup extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onLayerSelect: PropTypes.func.isRequired,
|
onLayerSelect: PropTypes.func.isRequired,
|
||||||
features: PropTypes.array
|
features: PropTypes.array,
|
||||||
|
zoom: PropTypes.number,
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFeatureColor(feature, zoom) {
|
||||||
|
// Guard because openlayers won't have this
|
||||||
|
if (!feature.layer.paint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paintProps = feature.layer.paint;
|
||||||
|
let propName;
|
||||||
|
|
||||||
|
if(paintProps.hasOwnProperty("text-color") && paintProps["text-color"]) {
|
||||||
|
propName = "text-color";
|
||||||
|
}
|
||||||
|
else if (paintProps.hasOwnProperty("fill-color") && paintProps["fill-color"]) {
|
||||||
|
propName = "fill-color";
|
||||||
|
}
|
||||||
|
else if (paintProps.hasOwnProperty("line-color") && paintProps["line-color"]) {
|
||||||
|
propName = "line-color";
|
||||||
|
}
|
||||||
|
else if (paintProps.hasOwnProperty("fill-extrusion-color") && paintProps["fill-extrusion-color"]) {
|
||||||
|
propName = "fill-extrusion-color";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(propName) {
|
||||||
|
const propertySpec = latest["paint_"+feature.layer.type][propName];
|
||||||
|
|
||||||
|
let color = feature.layer.paint[propName];
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// Default color
|
||||||
|
return "black";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is quite complex, just incase there's an edgecase we're missing
|
||||||
|
// always return black if we get an unexpected error.
|
||||||
|
catch (err) {
|
||||||
|
console.error("Unable to get feature color, error:", err);
|
||||||
|
return "black";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -38,21 +94,33 @@ class FeatureLayerPopup extends React.Component {
|
|||||||
|
|
||||||
const items = Object.keys(sources).map(vectorLayerId => {
|
const items = Object.keys(sources).map(vectorLayerId => {
|
||||||
const layers = sources[vectorLayerId].map((feature, idx) => {
|
const layers = sources[vectorLayerId].map((feature, idx) => {
|
||||||
return <label
|
const featureColor = this._getFeatureColor(feature, this.props.zoom);
|
||||||
key={idx}
|
|
||||||
className="maputnik-popup-layer"
|
return <div
|
||||||
onClick={() => {
|
key={idx}
|
||||||
this.props.onLayerSelect(feature.layer.id)
|
className="maputnik-popup-layer"
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<LayerIcon type={feature.layer.type} style={{
|
<div
|
||||||
width: 14,
|
className="maputnik-popup-layer__swatch"
|
||||||
height: 14,
|
style={{background: featureColor}}
|
||||||
paddingRight: 3
|
></div>
|
||||||
}}/>
|
<label
|
||||||
{feature.layer.id}
|
className="maputnik-popup-layer__label"
|
||||||
{feature.counter && <span> × {feature.counter}</span>}
|
onClick={() => {
|
||||||
</label>
|
this.props.onLayerSelect(feature.layer.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feature.layer.type &&
|
||||||
|
<LayerIcon type={feature.layer.type} style={{
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
paddingRight: 3
|
||||||
|
}}/>
|
||||||
|
}
|
||||||
|
{feature.layer.id}
|
||||||
|
{feature.counter && <span> × {feature.counter}</span>}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
})
|
})
|
||||||
return <div key={vectorLayerId}>
|
return <div key={vectorLayerId}>
|
||||||
<div className="maputnik-popup-layer-id">{vectorLayerId}</div>
|
<div className="maputnik-popup-layer-id">{vectorLayerId}</div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function renderProperties(feature) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderFeature(feature) {
|
function renderFeature(feature) {
|
||||||
return <div key={feature.id}>
|
return <div key={`${feature.sourceLayer}-${feature.id}`}>
|
||||||
<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'}} />
|
||||||
@@ -43,7 +43,7 @@ function removeDuplicatedFeatures(features) {
|
|||||||
if(featureIndex === -1) {
|
if(featureIndex === -1) {
|
||||||
uniqueFeatures.push(feature)
|
uniqueFeatures.push(feature)
|
||||||
} else {
|
} else {
|
||||||
if(uniqueFeatures[featureIndex].hasOwnProperty('counter')) {
|
if(uniqueFeatures[featureIndex].hasOwnProperty('inspectModeCounter')) {
|
||||||
uniqueFeatures[featureIndex].inspectModeCounter++
|
uniqueFeatures[featureIndex].inspectModeCounter++
|
||||||
} else {
|
} else {
|
||||||
uniqueFeatures[featureIndex].inspectModeCounter = 2
|
uniqueFeatures[featureIndex].inspectModeCounter = 2
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import MapboxGl from 'mapbox-gl'
|
|||||||
import MapboxInspect from 'mapbox-gl-inspect'
|
import MapboxInspect from 'mapbox-gl-inspect'
|
||||||
import FeatureLayerPopup from './FeatureLayerPopup'
|
import FeatureLayerPopup from './FeatureLayerPopup'
|
||||||
import FeaturePropertyPopup from './FeaturePropertyPopup'
|
import FeaturePropertyPopup from './FeaturePropertyPopup'
|
||||||
import style from '../../libs/style.js'
|
|
||||||
import tokens from '../../config/tokens.json'
|
import tokens from '../../config/tokens.json'
|
||||||
import colors from 'mapbox-gl-inspect/lib/colors'
|
import colors from 'mapbox-gl-inspect/lib/colors'
|
||||||
import Color from 'color'
|
import Color from 'color'
|
||||||
@@ -15,10 +14,13 @@ import 'mapbox-gl/dist/mapbox-gl.css'
|
|||||||
import '../../mapboxgl.css'
|
import '../../mapboxgl.css'
|
||||||
import '../../libs/mapbox-rtl'
|
import '../../libs/mapbox-rtl'
|
||||||
|
|
||||||
function renderPropertyPopup(features) {
|
|
||||||
var mountNode = document.createElement('div');
|
const IS_SUPPORTED = MapboxGl.supported();
|
||||||
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode)
|
|
||||||
return mountNode.innerHTML;
|
function renderPopup(popup, mountNode) {
|
||||||
|
ReactDOM.render(popup, mountNode);
|
||||||
|
var content = mountNode.innerHTML;
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
|
||||||
@@ -66,6 +68,7 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
onDataChange: () => {},
|
onDataChange: () => {},
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
mapboxAccessToken: tokens.mapbox,
|
mapboxAccessToken: tokens.mapbox,
|
||||||
|
options: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -74,27 +77,40 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
map: null,
|
map: null,
|
||||||
inspect: null,
|
inspect: null,
|
||||||
isPopupOpen: false,
|
|
||||||
popupX: 0,
|
|
||||||
popupY: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
updateMapFromProps(props) {
|
||||||
|
if(!IS_SUPPORTED) return;
|
||||||
|
|
||||||
if(!this.state.map) return
|
if(!this.state.map) return
|
||||||
const metadata = nextProps.mapStyle.metadata || {}
|
const metadata = props.mapStyle.metadata || {}
|
||||||
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
|
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
|
||||||
|
|
||||||
if(!nextProps.inspectModeEnabled) {
|
if(!props.inspectModeEnabled) {
|
||||||
//Mapbox GL now does diffing natively so we don't need to calculate
|
//Mapbox GL now does diffing natively so we don't need to calculate
|
||||||
//the necessary operations ourselves!
|
//the necessary operations ourselves!
|
||||||
this.state.map.setStyle(nextProps.mapStyle, { diff: true})
|
this.state.map.setStyle(props.mapStyle, { diff: true})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
let should = false;
|
||||||
|
try {
|
||||||
|
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
|
||||||
|
} catch(e) {
|
||||||
|
// no biggie, carry on
|
||||||
|
}
|
||||||
|
return should;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
if(!IS_SUPPORTED) return;
|
||||||
|
|
||||||
const map = this.state.map;
|
const map = this.state.map;
|
||||||
|
|
||||||
|
this.updateMapFromProps(this.props);
|
||||||
|
|
||||||
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
|
||||||
this.state.inspect.toggleInspector()
|
this.state.inspect.toggleInspector()
|
||||||
}
|
}
|
||||||
@@ -102,10 +118,16 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
this.state.inspect.render()
|
this.state.inspect.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
map.showTileBoundaries = this.props.options.showTileBoundaries;
|
if (map) {
|
||||||
|
map.showTileBoundaries = this.props.options.showTileBoundaries;
|
||||||
|
map.showCollisionBoxes = this.props.options.showCollisionBoxes;
|
||||||
|
map.showOverdrawInspector = this.props.options.showOverdrawInspector;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
if(!IS_SUPPORTED) return;
|
||||||
|
|
||||||
const mapOpts = {
|
const mapOpts = {
|
||||||
...this.props.options,
|
...this.props.options,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
@@ -116,13 +138,17 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
const map = new MapboxGl.Map(mapOpts);
|
const map = new MapboxGl.Map(mapOpts);
|
||||||
|
|
||||||
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
||||||
|
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
|
||||||
|
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
|
||||||
|
|
||||||
const zoom = new ZoomControl;
|
const zoom = new ZoomControl;
|
||||||
map.addControl(zoom, 'top-right');
|
map.addControl(zoom, 'top-right');
|
||||||
|
|
||||||
const nav = new MapboxGl.NavigationControl();
|
const nav = new MapboxGl.NavigationControl({visualizePitch:true});
|
||||||
map.addControl(nav, 'top-right');
|
map.addControl(nav, 'top-right');
|
||||||
|
|
||||||
|
const tmpNode = document.createElement('div');
|
||||||
|
|
||||||
const inspect = new MapboxInspect({
|
const inspect = new MapboxInspect({
|
||||||
popup: new MapboxGl.Popup({
|
popup: new MapboxGl.Popup({
|
||||||
closeOnClick: false
|
closeOnClick: false
|
||||||
@@ -138,18 +164,23 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
|
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
|
||||||
renderPopup: features => {
|
renderPopup: features => {
|
||||||
if(this.props.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled) {
|
||||||
return renderPropertyPopup(features)
|
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
|
||||||
} else {
|
} else {
|
||||||
var mountNode = document.createElement('div');
|
return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} zoom={this.state.zoom} />, tmpNode);
|
||||||
ReactDOM.render(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} />, mountNode)
|
|
||||||
return mountNode
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
map.addControl(inspect)
|
map.addControl(inspect)
|
||||||
|
|
||||||
map.on("style.load", () => {
|
map.on("style.load", () => {
|
||||||
this.setState({ map, inspect });
|
this.setState({
|
||||||
|
map,
|
||||||
|
inspect,
|
||||||
|
zoom: map.getZoom()
|
||||||
|
});
|
||||||
|
if(this.props.inspectModeEnabled) {
|
||||||
|
inspect.toggleInspector();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
map.on("data", e => {
|
map.on("data", e => {
|
||||||
@@ -158,12 +189,29 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
map: this.state.map
|
map: this.state.map
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
map.on("zoom", e => {
|
||||||
|
this.setState({
|
||||||
|
zoom: map.getZoom()
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div
|
if(IS_SUPPORTED) {
|
||||||
className="maputnik-map"
|
return <div
|
||||||
ref={x => this.container = x}
|
className="maputnik-map__map"
|
||||||
></div>
|
ref={x => this.container = x}
|
||||||
|
></div>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return <div
|
||||||
|
className="maputnik-map maputnik-map--error"
|
||||||
|
>
|
||||||
|
<div className="maputnik-map__error-message">
|
||||||
|
Error: Cannot load MapboxGL, WebGL is either unsupported or disabled
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import style from '../../libs/style.js'
|
|
||||||
import isEqual from 'lodash.isequal'
|
|
||||||
import { loadJSON } from '../../libs/urlopen'
|
|
||||||
import 'ol/ol.css'
|
|
||||||
|
|
||||||
|
|
||||||
class OpenLayers3Map extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
onDataChange: PropTypes.func,
|
|
||||||
mapStyle: PropTypes.object.isRequired,
|
|
||||||
accessToken: PropTypes.string,
|
|
||||||
style: PropTypes.object,
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
onMapLoaded: () => {},
|
|
||||||
onDataChange: () => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
this.map = null
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStyle(newMapStyle) {
|
|
||||||
const olms = require('ol-mapbox-style');
|
|
||||||
const styleFunc = olms.apply(this.map, newMapStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
||||||
require.ensure(["ol", "ol-mapbox-style"], () => {
|
|
||||||
if(!this.map) return
|
|
||||||
this.updateStyle(nextProps.mapStyle)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
//Load OpenLayers dynamically once we need it
|
|
||||||
//TODO: Make this more convenient
|
|
||||||
require.ensure(["ol", "ol/map", "ol/view", "ol/control/zoom", "ol-mapbox-style"], ()=> {
|
|
||||||
console.log('Loaded OpenLayers3 renderer')
|
|
||||||
|
|
||||||
const olMap = require('ol/map').default
|
|
||||||
const olView = require('ol/view').default
|
|
||||||
const olZoom = require('ol/control/zoom').default
|
|
||||||
|
|
||||||
const map = new olMap({
|
|
||||||
target: this.container,
|
|
||||||
layers: [],
|
|
||||||
view: new olView({
|
|
||||||
zoom: 2,
|
|
||||||
center: [52.5, -78.4]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
map.addControl(new olZoom())
|
|
||||||
this.map = map
|
|
||||||
this.updateStyle(this.props.mapStyle)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <div
|
|
||||||
ref={x => this.container = x}
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: 40,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
height: 'calc(100% - 40px)',
|
|
||||||
width: "75%",
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
...this.props.style,
|
|
||||||
}}>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OpenLayers3Map
|
|
||||||
167
src/components/map/OpenLayersMap.jsx
Normal file
167
src/components/map/OpenLayersMap.jsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {throttle} from 'lodash';
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { loadJSON } from '../../libs/urlopen'
|
||||||
|
|
||||||
|
import FeatureLayerPopup from './FeatureLayerPopup';
|
||||||
|
|
||||||
|
import 'ol/ol.css'
|
||||||
|
import {apply} from 'ol-mapbox-style';
|
||||||
|
import {Map, View, Proj, Overlay} from 'ol';
|
||||||
|
|
||||||
|
import {toLonLat} from 'ol/proj';
|
||||||
|
import {toStringHDMS} from 'ol/coordinate';
|
||||||
|
|
||||||
|
|
||||||
|
function renderCoords (coords) {
|
||||||
|
if (!coords || coords.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return <span className="maputnik-coords">
|
||||||
|
{coords.map((coord) => String(coord).padStart(7, "\u00A0")).join(', ')}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class OpenLayersMap extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
onDataChange: PropTypes.func,
|
||||||
|
mapStyle: PropTypes.object.isRequired,
|
||||||
|
accessToken: PropTypes.string,
|
||||||
|
style: PropTypes.object,
|
||||||
|
onLayerSelect: PropTypes.func.isRequired,
|
||||||
|
debugToolbox: PropTypes.bool.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onMapLoaded: () => {},
|
||||||
|
onDataChange: () => {},
|
||||||
|
onLayerSelect: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
zoom: 0,
|
||||||
|
rotation: 0,
|
||||||
|
cursor: [],
|
||||||
|
center: [],
|
||||||
|
};
|
||||||
|
this.updateStyle = throttle(this._updateStyle.bind(this), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateStyle(newMapStyle) {
|
||||||
|
if(!this.map) return;
|
||||||
|
|
||||||
|
// See <https://github.com/openlayers/ol-mapbox-style/issues/215#issuecomment-493198815>
|
||||||
|
this.map.getLayers().clear();
|
||||||
|
apply(this.map, newMapStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.mapStyle !== prevProps.mapStyle) {
|
||||||
|
this.updateStyle(this.props.mapStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.overlay = new Overlay({
|
||||||
|
element: this.popupContainer,
|
||||||
|
autoPan: true,
|
||||||
|
autoPanAnimation: {
|
||||||
|
duration: 250
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
target: this.container,
|
||||||
|
overlays: [this.overlay],
|
||||||
|
view: new View({
|
||||||
|
zoom: 1,
|
||||||
|
center: [180, -90],
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
map.on('pointermove', (evt) => {
|
||||||
|
var coords = toLonLat(evt.coordinate);
|
||||||
|
this.setState({
|
||||||
|
cursor: [
|
||||||
|
coords[0].toFixed(2),
|
||||||
|
coords[1].toFixed(2)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
map.on('postrender', (evt) => {
|
||||||
|
const center = toLonLat(map.getView().getCenter());
|
||||||
|
this.setState({
|
||||||
|
center: [
|
||||||
|
center[0].toFixed(2),
|
||||||
|
center[1].toFixed(2),
|
||||||
|
],
|
||||||
|
rotation: map.getView().getRotation().toFixed(2),
|
||||||
|
zoom: map.getView().getZoom().toFixed(2)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.map = map;
|
||||||
|
this.updateStyle(this.props.mapStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeOverlay = (e) => {
|
||||||
|
e.target.blur();
|
||||||
|
this.overlay.setPosition(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div className="maputnik-ol-container">
|
||||||
|
<div
|
||||||
|
ref={x => this.popupContainer = x}
|
||||||
|
style={{background: "black"}}
|
||||||
|
className="maputnik-popup"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="mapboxgl-popup-close-button"
|
||||||
|
onClick={this.closeOverlay}
|
||||||
|
aria-label="Close popup"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
<FeatureLayerPopup
|
||||||
|
features={this.state.selectedFeatures || []}
|
||||||
|
onLayerSelect={this.props.onLayerSelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="maputnik-ol-zoom">
|
||||||
|
Zoom level: {this.state.zoom}
|
||||||
|
</div>
|
||||||
|
{this.props.debugToolbox &&
|
||||||
|
<div className="maputnik-ol-debug">
|
||||||
|
<div>
|
||||||
|
<label>cursor: </label>
|
||||||
|
<span>{renderCoords(this.state.cursor)}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>center: </label>
|
||||||
|
<span>{renderCoords(this.state.center)}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>rotation: </label>
|
||||||
|
<span>{this.state.rotation}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
className="maputnik-ol"
|
||||||
|
ref={x => this.container = x}
|
||||||
|
style={{
|
||||||
|
...this.props.style,
|
||||||
|
}}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,8 +2,6 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
|
||||||
import StringInput from '../inputs/StringInput'
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
import LayerTypeBlock from '../layers/LayerTypeBlock'
|
import LayerTypeBlock from '../layers/LayerTypeBlock'
|
||||||
@@ -22,7 +20,7 @@ class AddModal extends React.Component {
|
|||||||
sources: PropTypes.object.isRequired,
|
sources: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
addLayer() {
|
addLayer = () => {
|
||||||
const changedLayers = this.props.layers.slice(0)
|
const changedLayers = this.props.layers.slice(0)
|
||||||
const layer = {
|
const layer = {
|
||||||
id: this.state.id,
|
id: this.state.id,
|
||||||
@@ -55,10 +53,10 @@ class AddModal extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillUpdate(nextProps, nextState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
// Check if source is valid for new type
|
// Check if source is valid for new type
|
||||||
const oldType = this.state.type;
|
const oldType = prevState.type;
|
||||||
const newType = nextState.type;
|
const newType = this.state.type;
|
||||||
|
|
||||||
const availableSourcesOld = this.getSources(oldType);
|
const availableSourcesOld = this.getSources(oldType);
|
||||||
const availableSourcesNew = this.getSources(newType);
|
const availableSourcesNew = this.getSources(newType);
|
||||||
@@ -66,11 +64,11 @@ class AddModal extends React.Component {
|
|||||||
if(
|
if(
|
||||||
// Type has changed
|
// Type has changed
|
||||||
oldType !== newType
|
oldType !== newType
|
||||||
&& this.state.source !== ""
|
&& prevState.source !== ""
|
||||||
// Was a valid source previously
|
// Was a valid source previously
|
||||||
&& availableSourcesOld.indexOf(this.state.source) > -1
|
&& availableSourcesOld.indexOf(prevState.source) > -1
|
||||||
// And is not a valid source now
|
// And is not a valid source now
|
||||||
&& availableSourcesNew.indexOf(nextState.source) < 0
|
&& availableSourcesNew.indexOf(this.state.source) < 0
|
||||||
) {
|
) {
|
||||||
// Clear the source
|
// Clear the source
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -93,10 +91,19 @@ class AddModal extends React.Component {
|
|||||||
"line",
|
"line",
|
||||||
"symbol",
|
"symbol",
|
||||||
"circle",
|
"circle",
|
||||||
"fill-extrusion"
|
"fill-extrusion",
|
||||||
|
"heatmap"
|
||||||
],
|
],
|
||||||
raster: [
|
raster: [
|
||||||
"raster"
|
"raster"
|
||||||
|
],
|
||||||
|
geojson: [
|
||||||
|
"fill",
|
||||||
|
"line",
|
||||||
|
"symbol",
|
||||||
|
"circle",
|
||||||
|
"fill-extrusion",
|
||||||
|
"heatmap"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +158,7 @@ class AddModal extends React.Component {
|
|||||||
}
|
}
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-add-layer-button"
|
className="maputnik-add-layer-button"
|
||||||
onClick={this.addLayer.bind(this)}
|
onClick={this.addLayer}
|
||||||
data-wd-key="add-layer"
|
data-wd-key="add-layer"
|
||||||
>
|
>
|
||||||
Add Layer
|
Add Layer
|
||||||
|
|||||||
53
src/components/modals/DebugModal.js
Normal file
53
src/components/modals/DebugModal.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
|
||||||
|
class DebugModal extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
renderer: PropTypes.string.isRequired,
|
||||||
|
onChangeMaboxGlDebug: PropTypes.func.isRequired,
|
||||||
|
onChangeOpenlayersDebug: PropTypes.func.isRequired,
|
||||||
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
|
mapboxGlDebugOptions: PropTypes.object,
|
||||||
|
openlayersDebugOptions: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Modal
|
||||||
|
data-wd-key="debug-modal"
|
||||||
|
isOpen={this.props.isOpen}
|
||||||
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
|
title={'Debug'}
|
||||||
|
>
|
||||||
|
<div className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||||
|
{this.props.renderer === 'mbgljs' &&
|
||||||
|
<ul>
|
||||||
|
{Object.entries(this.props.mapboxGlDebugOptions).map(([key, val]) => {
|
||||||
|
return <li key={key}>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={val} onClick={(e) => this.props.onChangeMaboxGlDebug(key, e.target.checked)} /> {key}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
{this.props.renderer === 'ol' &&
|
||||||
|
<ul>
|
||||||
|
{Object.entries(this.props.openlayersDebugOptions).map(([key, val]) => {
|
||||||
|
return <li key={key}>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={val} onClick={(e) => this.props.onChangeOpenlayersDebug(key, e.target.checked)} /> {key}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DebugModal;
|
||||||
@@ -1,216 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import Slugify from 'slugify'
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {format} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import CheckboxInput from '../inputs/CheckboxInput'
|
import CheckboxInput from '../inputs/CheckboxInput'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import MdFileDownload from 'react-icons/lib/md/file-download'
|
import {MdFileDownload} from 'react-icons/md'
|
||||||
import TiClipboard from 'react-icons/lib/ti/clipboard'
|
import style from '../../libs/style'
|
||||||
import style from '../../libs/style.js'
|
|
||||||
import GitHub from 'github-api'
|
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
|
||||||
|
|
||||||
|
|
||||||
class Gist extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
mapStyle: PropTypes.object.isRequired,
|
|
||||||
onStyleChanged: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
preview: false,
|
|
||||||
public: false,
|
|
||||||
saving: false,
|
|
||||||
latestGist: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
||||||
this.setState({
|
|
||||||
...this.state,
|
|
||||||
preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onSave() {
|
|
||||||
this.setState({
|
|
||||||
...this.state,
|
|
||||||
saving: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const preview = this.state.preview;
|
|
||||||
|
|
||||||
const mapboxToken = (this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token'];
|
|
||||||
|
|
||||||
const mapStyleStr = preview ?
|
|
||||||
styleSpec.format(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
|
|
||||||
styleSpec.format(stripAccessTokens(this.props.mapStyle));
|
|
||||||
const styleTitle = this.props.mapStyle.name || 'Style';
|
|
||||||
const htmlStr = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>`+styleTitle+` Preview</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.css" />
|
|
||||||
<script src="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.js"></script>
|
|
||||||
<style>
|
|
||||||
body { margin:0; padding:0; }
|
|
||||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id='map'></div>
|
|
||||||
<script>
|
|
||||||
mapboxgl.accessToken = '${mapboxToken}';
|
|
||||||
var map = new mapboxgl.Map({
|
|
||||||
container: 'map',
|
|
||||||
style: 'style.json',
|
|
||||||
attributionControl: true,
|
|
||||||
hash: true
|
|
||||||
});
|
|
||||||
map.addControl(new mapboxgl.NavigationControl());
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
const files = {
|
|
||||||
"style.json": {
|
|
||||||
content: mapStyleStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(preview) {
|
|
||||||
files["index.html"] = {
|
|
||||||
content: htmlStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const gh = new GitHub();
|
|
||||||
let gist = gh.getGist(); // not a gist yet
|
|
||||||
gist.create({
|
|
||||||
public: this.state.public,
|
|
||||||
description: styleTitle,
|
|
||||||
files: files
|
|
||||||
}).then(function({data}) {
|
|
||||||
return gist.read();
|
|
||||||
}).then(function({data}) {
|
|
||||||
this.setState({
|
|
||||||
...this.state,
|
|
||||||
latestGist: data,
|
|
||||||
saving: false,
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
onPreviewChange(value) {
|
|
||||||
this.setState({
|
|
||||||
...this.state,
|
|
||||||
preview: value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onPublicChange(value) {
|
|
||||||
this.setState({
|
|
||||||
...this.state,
|
|
||||||
public: value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
changeMetadataProperty(property, value) {
|
|
||||||
const changedStyle = {
|
|
||||||
...this.props.mapStyle,
|
|
||||||
metadata: {
|
|
||||||
...this.props.mapStyle.metadata,
|
|
||||||
[property]: value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onStyleChanged(changedStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPreviewLink() {
|
|
||||||
const gist = this.state.latestGist;
|
|
||||||
const user = gist.user || 'anonymous';
|
|
||||||
const preview = !!gist.files['index.html'];
|
|
||||||
if(preview) {
|
|
||||||
return <span><a target="_blank" rel="noopener noreferrer" href={"https://bl.ocks.org/"+user+"/"+gist.id}>Preview</a>,{' '}</span>
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLatestGist() {
|
|
||||||
const gist = this.state.latestGist;
|
|
||||||
const saving = this.state.saving;
|
|
||||||
if(saving) {
|
|
||||||
return <p>Saving...</p>
|
|
||||||
} else if(gist) {
|
|
||||||
const user = gist.user || 'anonymous';
|
|
||||||
const rawGistLink = "https://gist.githubusercontent.com/" + user + "/" + gist.id + "/raw/" + gist.history[0].version + "/style.json"
|
|
||||||
const maputnikStyleLink = "https://maputnik.github.io/editor/?style=" + rawGistLink
|
|
||||||
return <div className="maputnik-render-gist">
|
|
||||||
<p>
|
|
||||||
Latest saved gist:{' '}
|
|
||||||
{this.renderPreviewLink(this)}
|
|
||||||
<a target="_blank" rel="noopener noreferrer" href={"https://gist.github.com/" + user + "/" + gist.id}>Source</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<CopyToClipboard text={maputnikStyleLink}>
|
|
||||||
<span>Share this style: <Button><TiClipboard size={18} /></Button></span>
|
|
||||||
</CopyToClipboard>
|
|
||||||
<StringInput value={maputnikStyleLink} />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <div className="maputnik-export-gist">
|
|
||||||
<Button onClick={this.onSave.bind(this)}>
|
|
||||||
<MdFileDownload />
|
|
||||||
Save to Gist (anonymous)
|
|
||||||
</Button>
|
|
||||||
<div className="maputnik-modal-sub-section">
|
|
||||||
<CheckboxInput
|
|
||||||
value={this.state.public}
|
|
||||||
name='gist-style-public'
|
|
||||||
onChange={this.onPublicChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span> Public gist</span>
|
|
||||||
</div>
|
|
||||||
<div className="maputnik-modal-sub-section">
|
|
||||||
<CheckboxInput
|
|
||||||
value={this.state.preview}
|
|
||||||
name='gist-style-preview'
|
|
||||||
onChange={this.onPreviewChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span> Include preview</span>
|
|
||||||
</div>
|
|
||||||
{this.state.preview ?
|
|
||||||
<div>
|
|
||||||
<InputBlock
|
|
||||||
label={"OpenMapTiles Access Token: "}>
|
|
||||||
<StringInput
|
|
||||||
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
|
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}/>
|
|
||||||
</InputBlock>
|
|
||||||
<InputBlock
|
|
||||||
label={"Mapbox Access Token: "}>
|
|
||||||
<StringInput
|
|
||||||
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
|
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}/>
|
|
||||||
</InputBlock>
|
|
||||||
<a target="_blank" rel="noopener noreferrer" href="https://openmaptiles.com/hosting/">Get your free access token</a>
|
|
||||||
</div>
|
|
||||||
: null}
|
|
||||||
{this.renderLatestGist()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripAccessTokens(mapStyle) {
|
function stripAccessTokens(mapStyle) {
|
||||||
const changedMetadata = { ...mapStyle.metadata }
|
const changedMetadata = { ...mapStyle.metadata }
|
||||||
@@ -235,10 +37,33 @@ class ExportModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadStyle() {
|
downloadStyle() {
|
||||||
const blob = new Blob([styleSpec.format(stripAccessTokens(this.props.mapStyle))], {type: "application/json;charset=utf-8"});
|
const tokenStyle = format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle)));
|
||||||
saveAs(blob, this.props.mapStyle.id + ".json");
|
|
||||||
|
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
|
||||||
|
let exportName;
|
||||||
|
if(this.props.mapStyle.name) {
|
||||||
|
exportName = Slugify(this.props.mapStyle.name, {
|
||||||
|
replacement: '_',
|
||||||
|
lower: true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
exportName = this.props.mapStyle.id
|
||||||
|
}
|
||||||
|
saveAs(blob, exportName + ".json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeMetadataProperty(property, value) {
|
||||||
|
const changedStyle = {
|
||||||
|
...this.props.mapStyle,
|
||||||
|
metadata: {
|
||||||
|
...this.props.mapStyle.metadata,
|
||||||
|
[property]: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.props.onStyleChanged(changedStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="export-modal"
|
data-wd-key="export-modal"
|
||||||
@@ -252,16 +77,34 @@ class ExportModal extends React.Component {
|
|||||||
<p>
|
<p>
|
||||||
Download a JSON style to your computer.
|
Download a JSON style to your computer.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<InputBlock label={"MapTiler Access Token: "}>
|
||||||
|
<StringInput
|
||||||
|
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
|
||||||
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
<InputBlock label={"Mapbox Access Token: "}>
|
||||||
|
<StringInput
|
||||||
|
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
|
||||||
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
<InputBlock label={"Thunderforest Access Token: "}>
|
||||||
|
<StringInput
|
||||||
|
value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']}
|
||||||
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
</p>
|
||||||
|
|
||||||
<Button onClick={this.downloadStyle.bind(this)}>
|
<Button onClick={this.downloadStyle.bind(this)}>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="maputnik-modal-section hide">
|
|
||||||
<h4>Save style</h4>
|
|
||||||
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ class LoadingModal extends React.Component {
|
|||||||
message: PropTypes.node.isRequired,
|
message: PropTypes.node.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
underlayOnClick(e) {
|
underlayOnClick(e) {
|
||||||
// This stops click events falling through to underlying modals.
|
// This stops click events falling through to underlying modals.
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import CloseIcon from 'react-icons/lib/md/close'
|
import {MdClose} from 'react-icons/md'
|
||||||
import AriaModal from 'react-aria-modal'
|
import AriaModal from 'react-aria-modal'
|
||||||
|
|
||||||
|
|
||||||
@@ -19,6 +19,17 @@ class Modal extends React.Component {
|
|||||||
underlayClickExits: true
|
underlayClickExits: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See <https://github.com/maputnik/editor/issues/416>
|
||||||
|
onClose = () => {
|
||||||
|
if (document.activeElement) {
|
||||||
|
document.activeElement.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
this.props.onOpenToggle(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getApplicationNode() {
|
getApplicationNode() {
|
||||||
return document.getElementById('app');
|
return document.getElementById('app');
|
||||||
}
|
}
|
||||||
@@ -32,7 +43,7 @@ class Modal extends React.Component {
|
|||||||
getApplicationNode={this.getApplicationNode}
|
getApplicationNode={this.getApplicationNode}
|
||||||
data-wd-key={this.props["data-wd-key"]}
|
data-wd-key={this.props["data-wd-key"]}
|
||||||
verticallyCenter={true}
|
verticallyCenter={true}
|
||||||
onExit={() => this.props.onOpenToggle(false)}
|
onExit={this.onClose}
|
||||||
>
|
>
|
||||||
<div className="maputnik-modal"
|
<div className="maputnik-modal"
|
||||||
data-wd-key={this.props["data-wd-key"]}
|
data-wd-key={this.props["data-wd-key"]}
|
||||||
@@ -41,10 +52,10 @@ class Modal extends React.Component {
|
|||||||
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
||||||
<span className="maputnik-modal-header-space"></span>
|
<span className="maputnik-modal-header-space"></span>
|
||||||
<button className="maputnik-modal-header-toggle"
|
<button className="maputnik-modal-header-toggle"
|
||||||
onClick={() => this.props.onOpenToggle(false)}
|
onClick={this.onClose}
|
||||||
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<MdClose />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<div className="maputnik-modal-scroller">
|
<div className="maputnik-modal-scroller">
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import LoadingModal from './LoadingModal'
|
|||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import FileReaderInput from 'react-file-reader-input'
|
import FileReaderInput from 'react-file-reader-input'
|
||||||
import request from 'request'
|
|
||||||
|
|
||||||
import FileUploadIcon from 'react-icons/lib/md/file-upload'
|
import {MdFileUpload} from 'react-icons/md'
|
||||||
import AddIcon from 'react-icons/lib/md/add-circle-outline'
|
import {MdAddCircleOutline} from 'react-icons/md'
|
||||||
|
|
||||||
import style from '../../libs/style.js'
|
import style from '../../libs/style.js'
|
||||||
import publicStyles from '../../config/styles.json'
|
import publicStyles from '../../config/styles.json'
|
||||||
@@ -30,7 +29,7 @@ class PublicStyle extends React.Component {
|
|||||||
<header className="maputnik-public-style-header">
|
<header className="maputnik-public-style-header">
|
||||||
<h4>{this.props.title}</h4>
|
<h4>{this.props.title}</h4>
|
||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<AddIcon />
|
<MdAddCircleOutline />
|
||||||
</header>
|
</header>
|
||||||
<div
|
<div
|
||||||
className="maputnik-public-style-thumbnail"
|
className="maputnik-public-style-thumbnail"
|
||||||
@@ -52,7 +51,9 @@ class OpenModal extends React.Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
this.state = {
|
||||||
|
styleUrl: ""
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clearError() {
|
clearError() {
|
||||||
@@ -74,42 +75,59 @@ class OpenModal extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onStyleSelect(styleUrl) {
|
onStyleSelect = (styleUrl) => {
|
||||||
this.clearError();
|
this.clearError();
|
||||||
|
|
||||||
const reqOpts = {
|
let canceled;
|
||||||
url: styleUrl,
|
|
||||||
withCredentials: false,
|
const activeRequest = fetch(styleUrl, {
|
||||||
}
|
mode: 'cors',
|
||||||
|
credentials: "same-origin"
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((body) => {
|
||||||
|
if(canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const activeRequest = request(reqOpts, (error, response, body) => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
activeRequest: null,
|
activeRequest: null,
|
||||||
activeRequestUrl: null
|
activeRequestUrl: null
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!error && response.statusCode == 200) {
|
const mapStyle = style.ensureStyleValidity(body)
|
||||||
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
|
console.log('Loaded style ', mapStyle.id)
|
||||||
console.log('Loaded style ', mapStyle.id)
|
this.props.onStyleOpen(mapStyle)
|
||||||
this.props.onStyleOpen(mapStyle)
|
this.onOpenToggle()
|
||||||
this.onOpenToggle()
|
})
|
||||||
} else {
|
.catch((err) => {
|
||||||
console.warn('Could not open the style URL', styleUrl)
|
this.setState({
|
||||||
}
|
error: `Failed to load: '${styleUrl}'`,
|
||||||
|
activeRequest: null,
|
||||||
|
activeRequestUrl: null
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
console.warn('Could not open the style URL', styleUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
activeRequest: activeRequest,
|
activeRequest: {
|
||||||
activeRequestUrl: reqOpts.url
|
abort: function() {
|
||||||
|
canceled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activeRequestUrl: styleUrl
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenUrl() {
|
onOpenUrl = () => {
|
||||||
const url = this.styleUrlElement.value;
|
const url = this.styleUrlElement.value;
|
||||||
this.onStyleSelect(url);
|
this.onStyleSelect(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpload(_, files) {
|
onUpload = (_, files) => {
|
||||||
const [e, file] = files[0];
|
const [e, file] = files[0];
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
@@ -135,10 +153,19 @@ class OpenModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onOpenToggle() {
|
onOpenToggle() {
|
||||||
|
this.setState({
|
||||||
|
styleUrl: ""
|
||||||
|
});
|
||||||
this.clearError();
|
this.clearError();
|
||||||
this.props.onOpenToggle();
|
this.props.onOpenToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeUrl = () => {
|
||||||
|
this.setState({
|
||||||
|
styleUrl: this.styleUrlElement.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const styleOptions = publicStyles.map(style => {
|
const styleOptions = publicStyles.map(style => {
|
||||||
return <PublicStyle
|
return <PublicStyle
|
||||||
@@ -146,7 +173,7 @@ class OpenModal extends React.Component {
|
|||||||
url={style.url}
|
url={style.url}
|
||||||
title={style.title}
|
title={style.title}
|
||||||
thumbnailUrl={style.thumbnail}
|
thumbnailUrl={style.thumbnail}
|
||||||
onSelect={this.onStyleSelect.bind(this)}
|
onSelect={this.onStyleSelect}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -160,49 +187,66 @@ class OpenModal extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Modal
|
return (
|
||||||
data-wd-key="open-modal"
|
<div>
|
||||||
isOpen={this.props.isOpen}
|
<Modal
|
||||||
onOpenToggle={() => this.onOpenToggle()}
|
data-wd-key="open-modal"
|
||||||
title={'Open Style'}
|
isOpen={this.props.isOpen}
|
||||||
>
|
onOpenToggle={() => this.onOpenToggle()}
|
||||||
{errorElement}
|
title={'Open Style'}
|
||||||
<section className="maputnik-modal-section">
|
>
|
||||||
<h2>Upload Style</h2>
|
{errorElement}
|
||||||
<p>Upload a JSON style from your computer.</p>
|
<section className="maputnik-modal-section">
|
||||||
<FileReaderInput onChange={this.onUpload.bind(this)} tabIndex="-1">
|
<h2>Upload Style</h2>
|
||||||
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
|
<p>Upload a JSON style from your computer.</p>
|
||||||
</FileReaderInput>
|
<FileReaderInput onChange={this.onUpload} tabIndex="-1">
|
||||||
</section>
|
<Button className="maputnik-upload-button"><MdFileUpload /> Upload</Button>
|
||||||
|
</FileReaderInput>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h2>Load from URL</h2>
|
<h2>Load from URL</h2>
|
||||||
<p>
|
<p>
|
||||||
Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>.
|
Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>.
|
||||||
</p>
|
</p>
|
||||||
<input data-wd-key="open-modal.url.input" type="text" ref={(input) => this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/>
|
<input
|
||||||
<div>
|
data-wd-key="open-modal.url.input"
|
||||||
<Button data-wd-key="open-modal.url.button" className="maputnik-big-button" onClick={this.onOpenUrl.bind(this)}>Open URL</Button>
|
type="text"
|
||||||
</div>
|
ref={(input) => this.styleUrlElement = input}
|
||||||
</section>
|
className="maputnik-input"
|
||||||
|
placeholder="Enter URL..."
|
||||||
|
value={this.state.styleUrl}
|
||||||
|
onChange={this.onChangeUrl}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
data-wd-key="open-modal.url.button"
|
||||||
|
className="maputnik-big-button"
|
||||||
|
onClick={this.onOpenUrl}
|
||||||
|
disabled={this.state.styleUrl.length < 1}
|
||||||
|
>Open URL</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="maputnik-modal-section maputnik-modal-section--shrink">
|
<section className="maputnik-modal-section maputnik-modal-section--shrink">
|
||||||
<h2>Gallery Styles</h2>
|
<h2>Gallery Styles</h2>
|
||||||
<p>
|
<p>
|
||||||
Open one of the publicly available styles to start from.
|
Open one of the publicly available styles to start from.
|
||||||
</p>
|
</p>
|
||||||
<div className="maputnik-style-gallery-container">
|
<div className="maputnik-style-gallery-container">
|
||||||
{styleOptions}
|
{styleOptions}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<LoadingModal
|
<LoadingModal
|
||||||
isOpen={!!this.state.activeRequest}
|
isOpen={!!this.state.activeRequest}
|
||||||
title={'Loading style'}
|
title={'Loading style'}
|
||||||
onCancel={(e) => this.onCancelActiveRequest(e)}
|
onCancel={(e) => this.onCancelActiveRequest(e)}
|
||||||
message={"Loading: "+this.state.activeRequestUrl}
|
message={"Loading: "+this.state.activeRequestUrl}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
@@ -11,14 +11,11 @@ class SettingsModal extends React.Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
mapStyle: PropTypes.object.isRequired,
|
mapStyle: PropTypes.object.isRequired,
|
||||||
onStyleChanged: PropTypes.func.isRequired,
|
onStyleChanged: PropTypes.func.isRequired,
|
||||||
|
onChangeMetadataProperty: PropTypes.func.isRequired,
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
onOpenToggle: PropTypes.func.isRequired,
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeStyleProperty(property, value) {
|
changeStyleProperty(property, value) {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.props.mapStyle,
|
...this.props.mapStyle,
|
||||||
@@ -27,19 +24,9 @@ class SettingsModal extends React.Component {
|
|||||||
this.props.onStyleChanged(changedStyle)
|
this.props.onStyleChanged(changedStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeMetadataProperty(property, value) {
|
|
||||||
const changedStyle = {
|
|
||||||
...this.props.mapStyle,
|
|
||||||
metadata: {
|
|
||||||
...this.props.mapStyle.metadata,
|
|
||||||
[property]: value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onStyleChanged(changedStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const metadata = this.props.mapStyle.metadata || {}
|
const metadata = this.props.mapStyle.metadata || {}
|
||||||
|
const {onChangeMetadataProperty} = this.props;
|
||||||
const inputProps = { }
|
const inputProps = { }
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal-settings"
|
data-wd-key="modal-settings"
|
||||||
@@ -48,7 +35,7 @@ class SettingsModal extends React.Component {
|
|||||||
title={'Style Settings'}
|
title={'Style Settings'}
|
||||||
>
|
>
|
||||||
<div style={{minWidth: 350}}>
|
<div style={{minWidth: 350}}>
|
||||||
<InputBlock label={"Name"} doc={styleSpec.latest.$root.name.doc}>
|
<InputBlock label={"Name"} doc={latest.$root.name.doc}>
|
||||||
<StringInput {...inputProps}
|
<StringInput {...inputProps}
|
||||||
data-wd-key="modal-settings.name"
|
data-wd-key="modal-settings.name"
|
||||||
value={this.props.mapStyle.name}
|
value={this.props.mapStyle.name}
|
||||||
@@ -62,7 +49,7 @@ class SettingsModal extends React.Component {
|
|||||||
onChange={this.changeStyleProperty.bind(this, "owner")}
|
onChange={this.changeStyleProperty.bind(this, "owner")}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
<InputBlock label={"Sprite URL"} doc={styleSpec.latest.$root.sprite.doc}>
|
<InputBlock label={"Sprite URL"} doc={latest.$root.sprite.doc}>
|
||||||
<StringInput {...inputProps}
|
<StringInput {...inputProps}
|
||||||
data-wd-key="modal-settings.sprite"
|
data-wd-key="modal-settings.sprite"
|
||||||
value={this.props.mapStyle.sprite}
|
value={this.props.mapStyle.sprite}
|
||||||
@@ -70,7 +57,7 @@ class SettingsModal extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
||||||
<InputBlock label={"Glyphs URL"} doc={styleSpec.latest.$root.glyphs.doc}>
|
<InputBlock label={"Glyphs URL"} doc={latest.$root.glyphs.doc}>
|
||||||
<StringInput {...inputProps}
|
<StringInput {...inputProps}
|
||||||
data-wd-key="modal-settings.glyphs"
|
data-wd-key="modal-settings.glyphs"
|
||||||
value={this.props.mapStyle.glyphs}
|
value={this.props.mapStyle.glyphs}
|
||||||
@@ -82,15 +69,23 @@ class SettingsModal extends React.Component {
|
|||||||
<StringInput {...inputProps}
|
<StringInput {...inputProps}
|
||||||
data-wd-key="modal-settings.maputnik:mapbox_access_token"
|
data-wd-key="modal-settings.maputnik:mapbox_access_token"
|
||||||
value={metadata['maputnik:mapbox_access_token']}
|
value={metadata['maputnik:mapbox_access_token']}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
||||||
<InputBlock label={"OpenMapTiles Access Token"} doc={"Public access token for the OpenMapTiles CDN."}>
|
<InputBlock label={"MapTiler Access Token"} doc={"Public access token for MapTiler Cloud."}>
|
||||||
<StringInput {...inputProps}
|
<StringInput {...inputProps}
|
||||||
data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
|
data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
|
||||||
value={metadata['maputnik:openmaptiles_access_token']}
|
value={metadata['maputnik:openmaptiles_access_token']}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
||||||
|
/>
|
||||||
|
</InputBlock>
|
||||||
|
|
||||||
|
<InputBlock label={"Thunderforest Access Token"} doc={"Public access token for Thunderforest services."}>
|
||||||
|
<StringInput {...inputProps}
|
||||||
|
data-wd-key="modal-settings.maputnik:thunderforest_access_token"
|
||||||
|
value={metadata['maputnik:thunderforest_access_token']}
|
||||||
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
||||||
@@ -99,12 +94,13 @@ class SettingsModal extends React.Component {
|
|||||||
data-wd-key="modal-settings.maputnik:renderer"
|
data-wd-key="modal-settings.maputnik:renderer"
|
||||||
options={[
|
options={[
|
||||||
['mbgljs', 'MapboxGL JS'],
|
['mbgljs', 'MapboxGL JS'],
|
||||||
['ol3', 'Open Layers 3'],
|
['ol', 'Open Layers (experimental)'],
|
||||||
]}
|
]}
|
||||||
value={metadata['maputnik:renderer'] || 'mbgljs'}
|
value={metadata['maputnik:renderer'] || 'mbgljs'}
|
||||||
onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')}
|
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import Button from '../Button'
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
|
||||||
@@ -11,10 +10,6 @@ class ShortcutsModal extends React.Component {
|
|||||||
onOpenToggle: PropTypes.func.isRequired,
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const help = [
|
const help = [
|
||||||
{
|
{
|
||||||
@@ -45,6 +40,10 @@ class ShortcutsModal extends React.Component {
|
|||||||
key: "m",
|
key: "m",
|
||||||
text: "Focus map"
|
text: "Focus map"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "!",
|
||||||
|
text: "Debug modal"
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
@@ -12,8 +12,7 @@ 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 AddIcon from 'react-icons/lib/md/add-circle-outline'
|
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
||||||
import DeleteIcon from 'react-icons/lib/md/delete'
|
|
||||||
|
|
||||||
class PublicSource extends React.Component {
|
class PublicSource extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -34,7 +33,7 @@ class PublicSource extends React.Component {
|
|||||||
<p className="maputnik-public-source-id">#{this.props.id}</p>
|
<p className="maputnik-public-source-id">#{this.props.id}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<AddIcon />
|
<MdAddCircleOutline />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -76,7 +75,7 @@ class ActiveSourceTypeEditor extends React.Component {
|
|||||||
onClick={()=> this.props.onDelete(this.props.sourceId)}
|
onClick={()=> this.props.onDelete(this.props.sourceId)}
|
||||||
style={{backgroundColor: 'transparent'}}
|
style={{backgroundColor: 'transparent'}}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<MdDelete />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-active-source-type-editor-content">
|
<div className="maputnik-active-source-type-editor-content">
|
||||||
@@ -153,7 +152,7 @@ class AddSource extends React.Component {
|
|||||||
onChange={v => this.setState({ sourceId: v})}
|
onChange={v => this.setState({ sourceId: v})}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
<InputBlock label={"Source Type"} doc={styleSpec.latest.source_vector.type.doc}>
|
<InputBlock label={"Source Type"} doc={latest.source_vector.type.doc}>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
options={[
|
options={[
|
||||||
['geojson', 'GeoJSON'],
|
['geojson', 'GeoJSON'],
|
||||||
@@ -236,9 +235,12 @@ class SourcesModal extends React.Component {
|
|||||||
<p>
|
<p>
|
||||||
Add one of the publicly available sources to your style.
|
Add one of the publicly available sources to your style.
|
||||||
</p>
|
</p>
|
||||||
<div 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">
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ class SurveyModal extends React.Component {
|
|||||||
onOpenToggle: PropTypes.func.isRequired,
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) { super(props); }
|
|
||||||
|
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
|
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
@@ -16,7 +16,7 @@ class TileJSONSourceEditor extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div>
|
return <div>
|
||||||
<InputBlock label={"TileJSON URL"} doc={styleSpec.latest.source_vector.url.doc}>
|
<InputBlock label={"TileJSON URL"} doc={latest.source_vector.url.doc}>
|
||||||
<StringInput
|
<StringInput
|
||||||
value={this.props.source.url}
|
value={this.props.source.url}
|
||||||
onChange={url => this.props.onChange({
|
onChange={url => this.props.onChange({
|
||||||
@@ -50,7 +50,7 @@ class TileURLSourceEditor extends React.Component {
|
|||||||
const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th']
|
const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th']
|
||||||
const tiles = this.props.source.tiles || []
|
const tiles = this.props.source.tiles || []
|
||||||
return tiles.map((tileUrl, tileIndex) => {
|
return tiles.map((tileUrl, tileIndex) => {
|
||||||
return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={styleSpec.latest.source_vector.tiles.doc}>
|
return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={latest.source_vector.tiles.doc}>
|
||||||
<StringInput
|
<StringInput
|
||||||
value={tileUrl}
|
value={tileUrl}
|
||||||
onChange={this.changeTileUrl.bind(this, tileIndex)}
|
onChange={this.changeTileUrl.bind(this, tileIndex)}
|
||||||
@@ -62,7 +62,7 @@ class TileURLSourceEditor extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return <div>
|
return <div>
|
||||||
{this.renderTileUrls()}
|
{this.renderTileUrls()}
|
||||||
<InputBlock label={"Min Zoom"} doc={styleSpec.latest.source_vector.minzoom.doc}>
|
<InputBlock label={"Min Zoom"} doc={latest.source_vector.minzoom.doc}>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={this.props.source.minzoom || 0}
|
value={this.props.source.minzoom || 0}
|
||||||
onChange={minzoom => this.props.onChange({
|
onChange={minzoom => this.props.onChange({
|
||||||
@@ -71,7 +71,7 @@ class TileURLSourceEditor extends React.Component {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
<InputBlock label={"Max Zoom"} doc={styleSpec.latest.source_vector.maxzoom.doc}>
|
<InputBlock label={"Max Zoom"} doc={latest.source_vector.maxzoom.doc}>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={this.props.source.maxzoom || 22}
|
value={this.props.source.maxzoom || 22}
|
||||||
onChange={maxzoom => this.props.onChange({
|
onChange={maxzoom => this.props.onChange({
|
||||||
@@ -93,7 +93,7 @@ class GeoJSONSourceEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"GeoJSON Data"} doc={styleSpec.latest.source_geojson.data.doc}>
|
return <InputBlock label={"GeoJSON Data"} doc={latest.source_geojson.data.doc}>
|
||||||
<StringInput
|
<StringInput
|
||||||
value={this.props.source.data}
|
value={this.props.source.data}
|
||||||
onChange={data => this.props.onChange({
|
onChange={data => this.props.onChange({
|
||||||
@@ -125,14 +125,14 @@ class SourceTypeEditor extends React.Component {
|
|||||||
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
|
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
|
||||||
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
|
||||||
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
|
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
|
||||||
<InputBlock label={"Encoding"} doc={styleSpec.latest.source_raster_dem.encoding.doc}>
|
<InputBlock label={"Encoding"} doc={latest.source_raster_dem.encoding.doc}>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
options={Object.keys(styleSpec.latest.source_raster_dem.encoding.values)}
|
options={Object.keys(latest.source_raster_dem.encoding.values)}
|
||||||
onChange={encoding => this.props.onChange({
|
onChange={encoding => this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
encoding: encoding
|
encoding: encoding
|
||||||
})}
|
})}
|
||||||
value={this.props.source.encoding || styleSpec.latest.source_raster_dem.encoding.default}
|
value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
</TileURLSourceEditor>
|
</TileURLSourceEditor>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
"version": 8,
|
"version": 8,
|
||||||
"name": "Empty Style",
|
"name": "Empty Style",
|
||||||
"metadata": {
|
"metadata": {},
|
||||||
"mapbox:autocomposite": false,
|
"sources": {},
|
||||||
"mapbox:type": "template",
|
"sprite": "",
|
||||||
"maputnik:renderer": "mbgljs",
|
"glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
|
||||||
"openmaptiles:version": "3.x"
|
|
||||||
},
|
|
||||||
"sources": { },
|
|
||||||
"glyphs": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
|
|
||||||
"sprites": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
|
|
||||||
"layers": []
|
"layers": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,8 @@
|
|||||||
"fill-extrusion-translate-anchor",
|
"fill-extrusion-translate-anchor",
|
||||||
"fill-extrusion-pattern",
|
"fill-extrusion-pattern",
|
||||||
"fill-extrusion-height",
|
"fill-extrusion-height",
|
||||||
"fill-extrusion-base"
|
"fill-extrusion-base",
|
||||||
|
"fill-extrusion-vertical-gradient"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -105,7 +106,8 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
"symbol-placement",
|
"symbol-placement",
|
||||||
"symbol-spacing",
|
"symbol-spacing",
|
||||||
"symbol-avoid-edges"
|
"symbol-avoid-edges",
|
||||||
|
"symbol-z-order"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -137,6 +139,7 @@
|
|||||||
"title": "Icon layout properties",
|
"title": "Icon layout properties",
|
||||||
"type": "properties",
|
"type": "properties",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
"icon-image",
|
||||||
"icon-allow-overlap",
|
"icon-allow-overlap",
|
||||||
"icon-ignore-placement",
|
"icon-ignore-placement",
|
||||||
"icon-optional",
|
"icon-optional",
|
||||||
@@ -144,7 +147,6 @@
|
|||||||
"icon-size",
|
"icon-size",
|
||||||
"icon-text-fit",
|
"icon-text-fit",
|
||||||
"icon-text-fit-padding",
|
"icon-text-fit-padding",
|
||||||
"icon-image",
|
|
||||||
"icon-rotate",
|
"icon-rotate",
|
||||||
"icon-padding",
|
"icon-padding",
|
||||||
"icon-keep-upright",
|
"icon-keep-upright",
|
||||||
@@ -193,7 +195,8 @@
|
|||||||
"raster-brightness-max",
|
"raster-brightness-max",
|
||||||
"raster-saturation",
|
"raster-saturation",
|
||||||
"raster-contrast",
|
"raster-contrast",
|
||||||
"raster-fade-duration"
|
"raster-fade-duration",
|
||||||
|
"raster-resampling"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,55 +2,67 @@
|
|||||||
{
|
{
|
||||||
"id": "klokantech-basic",
|
"id": "klokantech-basic",
|
||||||
"title": "Klokantech Basic",
|
"title": "Klokantech Basic",
|
||||||
"url": "https://rawgit.com/openmaptiles/klokantech-basic-gl-style/master/style.json",
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/klokantech-basic-gl-style@v1.8/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/klokantech-basic.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/klokantech-basic.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dark-matter",
|
"id": "dark-matter",
|
||||||
"title": "Dark Matter",
|
"title": "Dark Matter",
|
||||||
"url": "https://rawgit.com/openmaptiles/dark-matter-gl-style/master/style.json",
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@v1.7/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "positron",
|
"id": "positron",
|
||||||
"title": "Positron",
|
"title": "Positron",
|
||||||
"url": "https://rawgit.com/openmaptiles/positron-gl-style/master/style.json",
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/positron-gl-style@v1.7/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/positron.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/positron.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "osm-bright",
|
"id": "osm-bright",
|
||||||
"title": "OSM Bright",
|
"title": "OSM Bright",
|
||||||
"url": "https://rawgit.com/openmaptiles/osm-bright-gl-style/master/style.json",
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/osm-bright-gl-style@v1.8/style.json",
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/osm-bright.png"
|
"thumbnail": "https://maputnik.github.io/thumbnails/osm-bright.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "toner-gl-style",
|
||||||
|
"title": "Toner",
|
||||||
|
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/toner-gl-style@c289223/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/toner.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "osm-liberty",
|
"id": "osm-liberty",
|
||||||
"title": "OSM Liberty",
|
"title": "OSM Liberty",
|
||||||
"url": "https://rawgit.com/maputnik/osm-liberty/gh-pages/style.json",
|
"url": "https://maputnik.github.io/osm-liberty/style.json",
|
||||||
"thumbnail": "https://cdn.rawgit.com/maputnik/osm-liberty/gh-pages/thumbnail.png"
|
"thumbnail": "https://maputnik.github.io/osm-liberty/thumbnail.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "os-zoomstack-outdoor",
|
||||||
|
"title": "Zoomstack Outdoor",
|
||||||
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-outdoor/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-outdoor.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "os-zoomstack-road",
|
||||||
|
"title": "Zoomstack Road",
|
||||||
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-road/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-road.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "os-zoomstack-light",
|
||||||
|
"title": "Zoomstack Light",
|
||||||
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-light/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-light.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "os-zoomstack-night",
|
||||||
|
"title": "Zoomstack Night",
|
||||||
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-night/style.json",
|
||||||
|
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-night.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "empty-style",
|
"id": "empty-style",
|
||||||
"title": "Empty Style",
|
"title": "Empty Style",
|
||||||
"url": "https://rawgit.com/maputnik/editor/master/src/config/empty-style.json",
|
"url": "https://cdn.jsdelivr.net/gh/maputnik/editor@9cf74ca405d2be0608b57db8109cf3a6af5b9f49/src/config/empty-style.json",
|
||||||
"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII="
|
"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII="
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mapbox-satellite",
|
|
||||||
"title": "Mapbox Satellite",
|
|
||||||
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/satellite-v9.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/mapbox-satellite.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mapbox-bright",
|
|
||||||
"title": "Mapbox Bright",
|
|
||||||
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/bright-v9.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/mapbox-bright.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mapbox-basic",
|
|
||||||
"title": "Mapbox Basic",
|
|
||||||
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/basic-v9.json",
|
|
||||||
"thumbnail": "https://maputnik.github.io/thumbnails/mapbox-basic.png"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
{
|
{
|
||||||
"mapbox-streets": {
|
|
||||||
"type": "vector",
|
|
||||||
"url": "mapbox://mapbox.mapbox-streets-v7",
|
|
||||||
"title": "Mapbox Streets"
|
|
||||||
},
|
|
||||||
"openmaptiles": {
|
"openmaptiles": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "https://free.tilehosting.com/data/v3.json?key={key}",
|
"url": "https://api.maptiler.com/tiles/v3/tiles.json?key={key}",
|
||||||
"title": "OpenMapTiles"
|
"title": "OpenMapTiles"
|
||||||
|
},
|
||||||
|
"thunderforest_transport": {
|
||||||
|
"type": "vector",
|
||||||
|
"url": "https://tile.thunderforest.com/thunderforest.transport-v1.json?apikey={key}",
|
||||||
|
"title": "Thunderforest Transport (heavy)"
|
||||||
|
},
|
||||||
|
"thunderforest_outdoors": {
|
||||||
|
"type": "vector",
|
||||||
|
"url": "https://tile.thunderforest.com/thunderforest.outdoors-v1.json?apikey={key}",
|
||||||
|
"title": "Thunderforest Outdoors (heavy)"
|
||||||
|
},
|
||||||
|
"open_zoomstack": {
|
||||||
|
"type": "vector",
|
||||||
|
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/data/vector/open-zoomstack/config.json",
|
||||||
|
"title": "OS Open Zoomstack"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w",
|
"mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w",
|
||||||
"openmaptiles": "Og58UhhtiiTaLVlPtPgs"
|
"openmaptiles": "KDhMfHvorAFkFe64wlZb",
|
||||||
|
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { IconContext } from "react-icons";
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
@@ -5,4 +6,9 @@ import './favicon.ico'
|
|||||||
import './styles/index.scss'
|
import './styles/index.scss'
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
|
|
||||||
ReactDOM.render(<App/>, document.querySelector("#app"));
|
ReactDOM.render(
|
||||||
|
<IconContext.Provider value={{className: 'react-icons'}}>
|
||||||
|
<App/>
|
||||||
|
</IconContext.Provider>,
|
||||||
|
document.querySelector("#app")
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import lodash from 'lodash'
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
|
|
||||||
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
|
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
|
||||||
const reducedMotionEnabled = lodash.throttle(() => {
|
const reducedMotionEnabled = throttle(() => {
|
||||||
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,37 @@
|
|||||||
import request from 'request'
|
|
||||||
import style from './style.js'
|
import style from './style.js'
|
||||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||||
|
|
||||||
const host = 'localhost'
|
|
||||||
const port = '8000'
|
|
||||||
const localUrl = `http://${host}:${port}`
|
|
||||||
const websocketUrl = `ws://${host}:${port}/ws`
|
|
||||||
|
|
||||||
|
|
||||||
export class ApiStyleStore {
|
export class ApiStyleStore {
|
||||||
|
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
this.onLocalStyleChange = opts.onLocalStyleChange || (() => {})
|
this.onLocalStyleChange = opts.onLocalStyleChange || (() => {})
|
||||||
|
const port = opts.port || '8000'
|
||||||
|
const host = opts.host || 'localhost'
|
||||||
|
this.localUrl = `http://${host}:${port}`
|
||||||
|
this.websocketUrl = `ws://${host}:${port}/ws`
|
||||||
|
this.init = this.init.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(cb) {
|
init(cb) {
|
||||||
request(localUrl + '/styles', (error, response, body) => {
|
fetch(this.localUrl + '/styles', {
|
||||||
if (!error && body && response.statusCode == 200) {
|
mode: 'cors',
|
||||||
const styleIds = JSON.parse(body)
|
})
|
||||||
this.latestStyleId = styleIds[0]
|
.then((response) => {
|
||||||
this.notifyLocalChanges()
|
return response.json();
|
||||||
cb(null)
|
})
|
||||||
} else {
|
.then((body) => {
|
||||||
cb(new Error('Can not connect to style API'))
|
const styleIds = body;
|
||||||
}
|
this.latestStyleId = styleIds[0]
|
||||||
|
this.notifyLocalChanges()
|
||||||
|
cb(null)
|
||||||
|
})
|
||||||
|
.catch(function(e) {
|
||||||
|
cb(new Error('Can not connect to style API'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyLocalChanges() {
|
notifyLocalChanges() {
|
||||||
const connection = new ReconnectingWebSocket(websocketUrl)
|
const connection = new ReconnectingWebSocket(this.websocketUrl)
|
||||||
connection.onmessage = e => {
|
connection.onmessage = e => {
|
||||||
if(!e.data) return
|
if(!e.data) return
|
||||||
console.log('Received style update from API')
|
console.log('Received style update from API')
|
||||||
@@ -44,8 +48,14 @@ export class ApiStyleStore {
|
|||||||
|
|
||||||
latestStyle(cb) {
|
latestStyle(cb) {
|
||||||
if(this.latestStyleId) {
|
if(this.latestStyleId) {
|
||||||
request(localUrl + '/styles/' + this.latestStyleId, (error, response, body) => {
|
fetch(this.localUrl + '/styles/' + this.latestStyleId, {
|
||||||
cb(style.ensureStyleValidity(JSON.parse(body)))
|
mode: 'cors',
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(body) {
|
||||||
|
cb(style.ensureStyleValidity(body))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No latest style available. You need to init the api backend first.')
|
throw new Error('No latest style available. You need to init the api backend first.')
|
||||||
@@ -55,11 +65,15 @@ export class ApiStyleStore {
|
|||||||
// Save current style replacing previous version
|
// Save current style replacing previous version
|
||||||
save(mapStyle) {
|
save(mapStyle) {
|
||||||
const id = mapStyle.id
|
const id = mapStyle.id
|
||||||
request.put({
|
fetch(this.localUrl + '/styles/' + id, {
|
||||||
url: localUrl + '/styles/' + id,
|
method: "PUT",
|
||||||
json: true,
|
mode: 'cors',
|
||||||
body: mapStyle
|
headers: {
|
||||||
}, (error, response, body) => {
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(mapStyle)
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
if(error) console.error(error)
|
if(error) console.error(error)
|
||||||
})
|
})
|
||||||
return mapStyle
|
return mapStyle
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {diff} from '@mapbox/mapbox-gl-style-spec'
|
||||||
|
|
||||||
export function diffMessages(beforeStyle, afterStyle) {
|
export function diffMessages(beforeStyle, afterStyle) {
|
||||||
const changes = styleSpec.diff(beforeStyle, afterStyle)
|
const changes = diff(beforeStyle, afterStyle)
|
||||||
return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' '))
|
return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' '))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
export const combiningFilterOps = ['all', 'any', 'none']
|
export const combiningFilterOps = ['all', 'any', 'none']
|
||||||
export const setFilterOps = ['in', '!in']
|
export const setFilterOps = ['in', '!in']
|
||||||
export const otherFilterOps = Object
|
export const otherFilterOps = Object
|
||||||
.keys(styleSpec.latest.filter_operator.values)
|
.keys(latest.filter_operator.values)
|
||||||
.filter(op => combiningFilterOps.indexOf(op) < 0)
|
.filter(op => combiningFilterOps.indexOf(op) < 0)
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import {latest} from '@mapbox/mapbox-gl-style-spec'
|
||||||
|
|
||||||
export function changeType(layer, newType) {
|
export function changeType(layer, newType) {
|
||||||
const changedPaintProps = { ...layer.paint }
|
const changedPaintProps = { ...layer.paint }
|
||||||
Object.keys(changedPaintProps).forEach(propertyName => {
|
Object.keys(changedPaintProps).forEach(propertyName => {
|
||||||
if(!(propertyName in styleSpec.latest['paint_' + newType])) {
|
if(!(propertyName in latest['paint_' + newType])) {
|
||||||
delete changedPaintProps[propertyName]
|
delete changedPaintProps[propertyName]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const changedLayoutProps = { ...layer.layout }
|
const changedLayoutProps = { ...layer.layout }
|
||||||
Object.keys(changedLayoutProps).forEach(propertyName => {
|
Object.keys(changedLayoutProps).forEach(propertyName => {
|
||||||
if(!(propertyName in styleSpec.latest['layout_' + newType])) {
|
if(!(propertyName in latest['layout_' + newType])) {
|
||||||
delete changedLayoutProps[propertyName]
|
delete changedLayoutProps[propertyName]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -31,7 +31,11 @@ export function changeProperty(layer, group, property, newValue) {
|
|||||||
if(newValue === undefined) {
|
if(newValue === undefined) {
|
||||||
if(group) {
|
if(group) {
|
||||||
const newLayer = {
|
const newLayer = {
|
||||||
...layer
|
...layer,
|
||||||
|
// Change object so the diff works in ./src/components/map/MapboxGlMap.jsx
|
||||||
|
[group]: {
|
||||||
|
...layer[group]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
delete newLayer[group][property];
|
delete newLayer[group][property];
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import MapboxGl from 'mapbox-gl/dist/mapbox-gl.js'
|
import MapboxGl from 'mapbox-gl'
|
||||||
|
|
||||||
// Load mapbox-gl-rtl-text using object urls without needing http://localhost for AJAX.
|
// Load mapbox-gl-rtl-text using object urls without needing http://localhost for AJAX.
|
||||||
const data = require("base64-loader?mimetype=text/javascript!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js");
|
const data = require("raw-loader?mimetype=text/javascript!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js");
|
||||||
|
|
||||||
const blob = new window.Blob([window.atob(data)]);
|
const blob = new window.Blob([data], {
|
||||||
const objectUrl = window.URL.createObjectURL(blob, {
|
|
||||||
type: "text/javascript"
|
type: "text/javascript"
|
||||||
});
|
});
|
||||||
|
const objectUrl = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
MapboxGl.setRTLTextPlugin(objectUrl);
|
MapboxGl.setRTLTextPlugin(objectUrl);
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
import request from 'request'
|
|
||||||
import npmurl from 'url'
|
import npmurl from 'url'
|
||||||
|
|
||||||
function loadJSON(url, defaultValue, cb) {
|
function loadJSON(url, defaultValue, cb) {
|
||||||
request({
|
fetch(url, {
|
||||||
url: url,
|
mode: 'cors',
|
||||||
withCredentials: false,
|
credentials: "same-origin"
|
||||||
}, (error, response, body) => {
|
})
|
||||||
if (!error && body && response.statusCode == 200) {
|
.then(function(response) {
|
||||||
try {
|
return response.json();
|
||||||
cb(JSON.parse(body))
|
})
|
||||||
} catch(err) {
|
.then(function(body) {
|
||||||
console.error(err)
|
cb(body)
|
||||||
cb(defaultValue)
|
})
|
||||||
}
|
.catch(function() {
|
||||||
} else {
|
console.warn('Can not metadata for ' + url)
|
||||||
console.warn('Can not metadata for ' + url)
|
cb(defaultValue)
|
||||||
cb(defaultValue)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import deref from '@mapbox/mapbox-gl-style-spec/deref'
|
import deref from '@mapbox/mapbox-gl-style-spec/deref'
|
||||||
import tokens from '../config/tokens.json'
|
import tokens from '../config/tokens.json'
|
||||||
|
|
||||||
@@ -54,18 +53,28 @@ function indexOfLayer(layers, layerId) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceAccessToken(mapStyle, opts={}) {
|
function getAccessToken(sourceName, mapStyle, opts) {
|
||||||
const omtSource = mapStyle.sources.openmaptiles
|
if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") {
|
||||||
if(!omtSource) return mapStyle
|
sourceName = "thunderforest"
|
||||||
if(!omtSource.hasOwnProperty("url")) return mapStyle
|
}
|
||||||
|
|
||||||
const metadata = mapStyle.metadata || {}
|
const metadata = mapStyle.metadata || {}
|
||||||
let accessToken = metadata['maputnik:openmaptiles_access_token'];
|
let accessToken = metadata[`maputnik:${sourceName}_access_token`]
|
||||||
|
|
||||||
if(opts.allowFallback && !accessToken) {
|
if(opts.allowFallback && !accessToken) {
|
||||||
accessToken = tokens.openmaptiles;
|
accessToken = tokens[sourceName]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceSourceAccessToken(mapStyle, sourceName, opts={}) {
|
||||||
|
const source = mapStyle.sources[sourceName]
|
||||||
|
if(!source) return mapStyle
|
||||||
|
if(!source.hasOwnProperty("url")) return mapStyle
|
||||||
|
|
||||||
|
const accessToken = getAccessToken(sourceName, mapStyle, opts)
|
||||||
|
|
||||||
if(!accessToken) {
|
if(!accessToken) {
|
||||||
// Early exit.
|
// Early exit.
|
||||||
return mapStyle;
|
return mapStyle;
|
||||||
@@ -73,16 +82,34 @@ function replaceAccessToken(mapStyle, opts={}) {
|
|||||||
|
|
||||||
const changedSources = {
|
const changedSources = {
|
||||||
...mapStyle.sources,
|
...mapStyle.sources,
|
||||||
openmaptiles: {
|
[sourceName]: {
|
||||||
...omtSource,
|
...source,
|
||||||
url: omtSource.url.replace('{key}', accessToken)
|
url: source.url.replace('{key}', accessToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...mapStyle,
|
...mapStyle,
|
||||||
glyphs: mapStyle.glyphs ? mapStyle.glyphs.replace('{key}', accessToken) : mapStyle.glyphs,
|
|
||||||
sources: changedSources
|
sources: changedSources
|
||||||
}
|
}
|
||||||
|
return changedStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceAccessTokens(mapStyle, opts={}) {
|
||||||
|
let changedStyle = mapStyle
|
||||||
|
|
||||||
|
Object.keys(mapStyle.sources).forEach((sourceName) => {
|
||||||
|
changedStyle = replaceSourceAccessToken(changedStyle, sourceName, opts);
|
||||||
|
})
|
||||||
|
|
||||||
|
if (mapStyle.glyphs && (mapStyle.glyphs.match(/\.tilehosting\.com/) || mapStyle.glyphs.match(/\.maptiler\.com/))) {
|
||||||
|
const newAccessToken = getAccessToken("openmaptiles", mapStyle, opts);
|
||||||
|
if (newAccessToken) {
|
||||||
|
changedStyle = {
|
||||||
|
...changedStyle,
|
||||||
|
glyphs: mapStyle.glyphs.replace('{key}', newAccessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return changedStyle
|
return changedStyle
|
||||||
}
|
}
|
||||||
@@ -92,5 +119,6 @@ export default {
|
|||||||
emptyStyle,
|
emptyStyle,
|
||||||
indexOfLayer,
|
indexOfLayer,
|
||||||
generateId,
|
generateId,
|
||||||
replaceAccessToken,
|
getAccessToken,
|
||||||
|
replaceAccessTokens,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { colorizeLayers } from './style.js'
|
|
||||||
import style from './style.js'
|
import style from './style.js'
|
||||||
import { loadStyleUrl } from './urlopen'
|
import { loadStyleUrl } from './urlopen'
|
||||||
import publicSources from '../config/styles.json'
|
import publicSources from '../config/styles.json'
|
||||||
import request from 'request'
|
|
||||||
|
|
||||||
const storagePrefix = "maputnik"
|
const storagePrefix = "maputnik"
|
||||||
const stylePrefix = 'style'
|
const stylePrefix = 'style'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import request from 'request'
|
|
||||||
import url from 'url'
|
import url from 'url'
|
||||||
|
import querystring from 'querystring'
|
||||||
import style from './style.js'
|
import style from './style.js'
|
||||||
|
|
||||||
export function initialStyleUrl() {
|
export function initialStyleUrl() {
|
||||||
@@ -9,34 +9,57 @@ export function initialStyleUrl() {
|
|||||||
|
|
||||||
export function loadStyleUrl(styleUrl, cb) {
|
export function loadStyleUrl(styleUrl, cb) {
|
||||||
console.log('Loading style', styleUrl)
|
console.log('Loading style', styleUrl)
|
||||||
request({
|
fetch(styleUrl, {
|
||||||
url: styleUrl,
|
mode: 'cors',
|
||||||
withCredentials: false,
|
credentials: "same-origin"
|
||||||
}, (error, response, body) => {
|
})
|
||||||
if (!error && response.statusCode == 200) {
|
.then(function(response) {
|
||||||
cb(style.ensureStyleValidity(JSON.parse(body)))
|
return response.json();
|
||||||
} else {
|
})
|
||||||
console.warn('Could not fetch default style', styleUrl)
|
.then(function(body) {
|
||||||
cb(style.emptyStyle)
|
cb(style.ensureStyleValidity(body))
|
||||||
}
|
})
|
||||||
|
.catch(function() {
|
||||||
|
console.warn('Could not fetch default style', styleUrl)
|
||||||
|
cb(style.emptyStyle)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeStyleQuerystring() {
|
||||||
|
const initialUrl = url.parse(window.location.href, true)
|
||||||
|
let qs = querystring.parse(window.location.search.slice(1))
|
||||||
|
delete qs["style"]
|
||||||
|
if(Object.getOwnPropertyNames(qs).length === 0) {
|
||||||
|
qs = ""
|
||||||
|
} else {
|
||||||
|
qs = "?" + querystring.stringify(qs)
|
||||||
|
}
|
||||||
|
let newUrlHash = initialUrl.hash
|
||||||
|
if(newUrlHash === null) {
|
||||||
|
newUrlHash = ""
|
||||||
|
}
|
||||||
|
const newUrl = initialUrl.protocol + "//" + initialUrl.host + initialUrl.pathname + qs + newUrlHash
|
||||||
|
window.history.replaceState({}, document.title, newUrl)
|
||||||
|
}
|
||||||
|
|
||||||
export function loadJSON(url, defaultValue, cb) {
|
export function loadJSON(url, defaultValue, cb) {
|
||||||
request({
|
fetch(url, {
|
||||||
url: url,
|
mode: 'cors',
|
||||||
withCredentials: false,
|
credentials: "same-origin"
|
||||||
}, (error, response, body) => {
|
})
|
||||||
if (!error && body && response.statusCode == 200) {
|
.then(function(response) {
|
||||||
try {
|
return response.json();
|
||||||
cb(JSON.parse(body))
|
})
|
||||||
} catch(err) {
|
.then(function(body) {
|
||||||
console.error(err)
|
try {
|
||||||
cb(defaultValue)
|
cb(body)
|
||||||
}
|
} catch(err) {
|
||||||
} else {
|
console.error(err)
|
||||||
console.error('Can not load JSON from ' + url)
|
|
||||||
cb(defaultValue)
|
cb(defaultValue)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.catch(function() {
|
||||||
|
console.error('Can not load JSON from ' + url)
|
||||||
|
cb(defaultValue)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,22 @@
|
|||||||
border-top-color: rgb(28, 31, 36);
|
border-top-color: rgb(28, 31, 36);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
|
||||||
|
border-bottom-color: rgb(28, 31, 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
|
||||||
|
border-bottom-color: rgb(28, 31, 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
|
||||||
|
border-top-color: rgb(28, 31, 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
|
||||||
|
border-top-color: rgb(28, 31, 36);
|
||||||
|
}
|
||||||
|
|
||||||
.mapboxgl-popup-content {
|
.mapboxgl-popup-content {
|
||||||
background-color: rgb(28, 31, 36);
|
background-color: rgb(28, 31, 36);
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
@@ -26,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mapboxgl-ctrl-zoom {
|
.mapboxgl-ctrl-zoom {
|
||||||
color: rgb(138, 138, 138);
|
color: #a4a4a4;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -46,15 +62,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in {
|
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in {
|
||||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%238e8e8e%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A")
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23a4a4a4%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A")
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out {
|
.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out {
|
||||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%238e8e8e%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A")
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23a4a4a4%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A")
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > span.arrow {
|
.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > .mapboxgl-ctrl-compass-arrow {
|
||||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%238e8e8e%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E")
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23a4a4a4%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23f0f0f0%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E")
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapboxgl-ctrl-inspect {
|
.mapboxgl-ctrl-inspect {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
src: url('../fonts/Roboto-Regular.ttf') format('truetype');
|
src: url('../fonts/Roboto-Regular.ttf') format('truetype');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
src: url('../fonts/Roboto-Medium.ttf') format('truetype');
|
src: url('../fonts/Roboto-Medium.ttf') format('truetype');
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@@ -18,6 +20,11 @@ html {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
// The UI is 100% height so prevent bounce scroll on OSX
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
// MAP
|
// MAP
|
||||||
.maputnik-map {
|
.maputnik-map__container {
|
||||||
|
display: flex;
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: $toolbar-height + $toolbar-offset;
|
top: $toolbar-height + $toolbar-offset;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
||||||
width: 75%;
|
width: calc(
|
||||||
|
100%
|
||||||
|
- 200px /* layer list */
|
||||||
|
- 350px /* layer editor */
|
||||||
|
);
|
||||||
|
|
||||||
|
&--error {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error-message {
|
||||||
|
margin: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-map__map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOC LABEL
|
// DOC LABEL
|
||||||
@@ -62,6 +82,12 @@
|
|||||||
background-color: lighten($color-midgray, 12);
|
background-color: lighten($color-midgray, 12);
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: darken($color-midgray, 5);
|
||||||
|
color: $color-midgray;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-big-button {
|
.maputnik-big-button {
|
||||||
@@ -150,3 +176,32 @@
|
|||||||
color: $color-red;
|
color: $color-red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-dialog {
|
||||||
|
&__buttons {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-state-menu {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&__menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999999;
|
||||||
|
background: $color-black;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-content: stretch;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,15 @@
|
|||||||
padding-bottom: $margin-5;
|
padding-bottom: $margin-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-item-handle {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
@@ -36,7 +45,7 @@
|
|||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 5px 10px;
|
padding: 5px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
max-height: 50px;
|
max-height: 50px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -58,22 +67,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-layer-list-icon-action {
|
||||||
|
background: initial;
|
||||||
|
border: none;
|
||||||
|
padding: 0 2px;
|
||||||
|
height: 15px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: darken($color-lowgray, 20);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
fill: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-layer-list-icon-action__visibility--hide {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-layer-list-item:hover,
|
.maputnik-layer-list-item:hover,
|
||||||
.maputnik-layer-list-item-selected {
|
.maputnik-layer-list-item-selected {
|
||||||
background-color: lighten($color-black, 2);
|
background-color: lighten($color-black, 2);
|
||||||
|
|
||||||
.maputnik-layer-list-icon-action {
|
.maputnik-layer-list-icon-action {
|
||||||
display: block;
|
display: block;
|
||||||
background: initial;
|
|
||||||
border: none;
|
|
||||||
padding: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maputnik-layer-list-icon-action svg {
|
svg {
|
||||||
fill: darken($color-lowgray, 0.5);
|
fill: darken($color-lowgray, 0.5);
|
||||||
|
|
||||||
&:hover {
|
|
||||||
fill: $color-white;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,6 +121,8 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-group-header {
|
&-group-header {
|
||||||
@@ -149,7 +172,7 @@
|
|||||||
// PROPERTY
|
// PROPERTY
|
||||||
.maputnik-default-property {
|
.maputnik-default-property {
|
||||||
.maputnik-input-block-label {
|
.maputnik-input-block-label {
|
||||||
color: darken($color-lowgray, 25%);
|
color: darken($color-lowgray, 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-string,
|
.maputnik-string,
|
||||||
@@ -158,7 +181,7 @@
|
|||||||
.maputnik-select,
|
.maputnik-select,
|
||||||
.maputnik-checkbox-wrapper {
|
.maputnik-checkbox-wrapper {
|
||||||
background-color: darken($color-gray, 2%);
|
background-color: darken($color-gray, 2%);
|
||||||
color: darken($color-lowgray, 25%);
|
color: darken($color-lowgray, 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-make-zoom-function svg {
|
.maputnik-make-zoom-function svg {
|
||||||
@@ -167,7 +190,7 @@
|
|||||||
|
|
||||||
.maputnik-multibutton .maputnik-button {
|
.maputnik-multibutton .maputnik-button {
|
||||||
background-color: darken($color-midgray, 10%);
|
background-color: darken($color-midgray, 10%);
|
||||||
color: darken($color-lowgray, 25%);
|
color: darken($color-lowgray, 20%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: lighten($color-midgray, 12);
|
background-color: lighten($color-midgray, 12);
|
||||||
@@ -184,6 +207,11 @@
|
|||||||
.more-menu {
|
.more-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
&__menu {
|
&__menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
@@ -218,3 +246,10 @@
|
|||||||
min-width: 28px;
|
min-width: 28px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone of the element which is sorted
|
||||||
|
.sortableHelper {
|
||||||
|
font-family: $font-family;
|
||||||
|
z-index: 9999;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//SCROLLING
|
//SCROLLING
|
||||||
.maputnik-scroll-container {
|
.maputnik-scroll-container {
|
||||||
overflow-x: visible;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
//OPENLAYERS
|
//OPENLAYERS
|
||||||
.maputnik-layout {
|
.maputnik-layout {
|
||||||
.ol-zoom {
|
.ol-zoom {
|
||||||
top: 10px;
|
top: 40px;
|
||||||
|
right: 10px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ol-rotate {
|
||||||
|
top: 94px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
left: auto;
|
left: auto;
|
||||||
}
|
}
|
||||||
@@ -20,3 +26,57 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.maputnik-ol {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-ol-popup {
|
||||||
|
background: $color-black;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-coords {
|
||||||
|
font-family: monospace;
|
||||||
|
&:before {
|
||||||
|
content: '[';
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: ']';
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-ol-debug {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: smaller;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
background: rgb(28, 31, 36);
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-ol-zoom {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
background: #1c1f24;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
color: $color-lowgray;
|
||||||
|
z-index: 9999;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-ol-container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|||||||
@@ -125,6 +125,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//SOURCE MODAL
|
//SOURCE MODAL
|
||||||
|
.maputnik-public-sources {
|
||||||
|
margin-bottom: 1.5%;
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-public-source {
|
.maputnik-public-source {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
margin-top: 1.5%;
|
margin-top: 1.5%;
|
||||||
@@ -150,6 +154,7 @@
|
|||||||
|
|
||||||
.maputnik-public-source-id {
|
.maputnik-public-source-id {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-active-source-type-editor {
|
.maputnik-active-source-type-editor {
|
||||||
@@ -242,12 +247,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-survey {
|
.maputnik-modal-survey {
|
||||||
width: 372px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-survey__logo {
|
.maputnik-modal-survey__logo {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
height: 128px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-survey__description {
|
.maputnik-modal-survey__description {
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
.maputnik-popup-layer {
|
.maputnik-popup-layer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-popup-layer__swatch {
|
||||||
|
display: inline-block;
|
||||||
|
width: 5px;
|
||||||
|
align-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maputnik-popup-layer__label {
|
||||||
display: block;
|
display: block;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -11,12 +22,14 @@
|
|||||||
|
|
||||||
.maputnik-popup-layer-id {
|
.maputnik-popup-layer-id {
|
||||||
padding-left: $margin-2;
|
padding-left: $margin-2;
|
||||||
padding-right: $margin-2;
|
padding-right: 1.6em;
|
||||||
background-color: $color-midgray;
|
background-color: $color-midgray;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-feature-property-popup {
|
.maputnik-feature-property-popup {
|
||||||
|
max-height: calc(50vh - 40px); /* toolbar height: 40px */
|
||||||
|
overflow-y: auto;
|
||||||
.maputnik-input-block {
|
.maputnik-input-block {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-left: $margin-2;
|
margin-left: $margin-2;
|
||||||
|
|||||||
3
src/styles/_react-codemirror.scss
Normal file
3
src/styles/_react-codemirror.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.react-codemirror2 {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// See <https://github.com/nkbt/react-collapse/commit/4f4fbce7c6c07b082dc62062338c9294c656f9df>
|
// See <https://github.com/nkbt/react-collapse/commit/4f4fbce7c6c07b082dc62062338c9294c656f9df>
|
||||||
.react-collapse-container {
|
.react-collapse-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -12,4 +12,8 @@ div:not(.maputnik-toolbar__actions) {
|
|||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Styling for Firefox
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #666 #26282e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-toolbar-name {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-toolbar-version {
|
.maputnik-toolbar-version {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
@@ -92,6 +96,24 @@
|
|||||||
@extend .maputnik-toolbar-link;
|
@extend .maputnik-toolbar-link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-toolbar-select {
|
||||||
|
background: inherit;
|
||||||
|
border-width: 0;
|
||||||
|
@extend .maputnik-toolbar-link;
|
||||||
|
|
||||||
|
select {
|
||||||
|
// HACK: <https://github.com/maputnik/editor/pull/392#issuecomment-427595172>
|
||||||
|
color: $color-black !important;
|
||||||
|
margin-left: 4px;
|
||||||
|
border-width: 0;
|
||||||
|
|
||||||
|
option {
|
||||||
|
// HACK: <https://github.com/maputnik/editor/pull/392#issuecomment-427595172>
|
||||||
|
color: $color-black !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-icon-text {
|
.maputnik-icon-text {
|
||||||
padding-left: $margin-1;
|
padding-left: $margin-1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
$color-black: #1c1f24;
|
$color-black: #191b20;
|
||||||
$color-gray: #26282e;
|
$color-gray: #222429;
|
||||||
$color-midgray: #36383e;
|
$color-midgray: #303237;
|
||||||
$color-lowgray: #8e8e8e;
|
$color-lowgray: #a4a4a4;
|
||||||
$color-white: #f0f0f0;
|
$color-white: #f0f0f0;
|
||||||
$color-red: #cf4a4a;
|
$color-red: #cf4a4a;
|
||||||
$color-green: #53b972;
|
$color-green: #53b972;
|
||||||
@@ -38,6 +38,7 @@ $toolbar-offset: 0;
|
|||||||
@import 'popup';
|
@import 'popup';
|
||||||
@import 'map';
|
@import 'map';
|
||||||
@import 'react-collapse';
|
@import 'react-collapse';
|
||||||
|
@import 'react-codemirror';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hacks for webdriverio isVisibleWithinViewport
|
* Hacks for webdriverio isVisibleWithinViewport
|
||||||
@@ -49,3 +50,9 @@ $toolbar-offset: 0;
|
|||||||
.maputnik-layout {
|
.maputnik-layout {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-icons {
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module.exports = {
|
|||||||
var result = browser.executeAsync(function(done) {
|
var result = browser.executeAsync(function(done) {
|
||||||
window.debug.get("maputnik", "styleStore").latestStyle(done);
|
window.debug.get("maputnik", "styleStore").latestStyle(done);
|
||||||
})
|
})
|
||||||
return result.value;
|
return result;
|
||||||
},
|
},
|
||||||
getRevisionStore: function(browser) {
|
getRevisionStore: function(browser) {
|
||||||
var result = browser.execute(function(done) {
|
var result = browser.execute(function(done) {
|
||||||
@@ -34,15 +34,16 @@ module.exports = {
|
|||||||
modal: {
|
modal: {
|
||||||
addLayer: {
|
addLayer: {
|
||||||
open: function() {
|
open: function() {
|
||||||
var selector = wd.$('layer-list:add-layer');
|
const selector = $(wd.$('layer-list:add-layer'));
|
||||||
browser.click(selector);
|
selector.click();
|
||||||
|
|
||||||
// Wait for events
|
// Wait for events
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.waitForExist(wd.$('modal:add-layer'));
|
const elem = $(wd.$('modal:add-layer'));
|
||||||
browser.isVisible(wd.$('modal:add-layer'));
|
elem.waitForExist();
|
||||||
browser.isVisibleWithinViewport(wd.$('modal:add-layer'));
|
elem.isDisplayed();
|
||||||
|
elem.isDisplayedInViewport();
|
||||||
|
|
||||||
// Wait for events
|
// Wait for events
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
@@ -58,7 +59,8 @@ module.exports = {
|
|||||||
id = type+":"+uuid();
|
id = type+":"+uuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.selectByValue(wd.$("add-layer.layer-type", "select"), type);
|
const selectBox = $(wd.$("add-layer.layer-type", "select"));
|
||||||
|
selectBox.selectByAttribute('value', type);
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.setValueSafe(wd.$("add-layer.layer-id", "input"), id);
|
browser.setValueSafe(wd.$("add-layer.layer-id", "input"), id);
|
||||||
@@ -67,7 +69,8 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
browser.click(wd.$("add-layer"));
|
const elem_addLayer = $(wd.$("add-layer"));
|
||||||
|
elem_addLayer.click();
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var config = require("../../config/specs");
|
var config = require("../../config/specs");
|
||||||
var helper = require("../helper");
|
var helper = require("../helper");
|
||||||
var wd = require("../../wd-helper");
|
|
||||||
|
|
||||||
|
|
||||||
describe.skip("history", function() {
|
describe.skip("history", function() {
|
||||||
@@ -14,6 +13,7 @@ describe.skip("history", function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
|
browser.alertAccept();
|
||||||
|
|
||||||
helper.modal.addLayer.open();
|
helper.modal.addLayer.open();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
var assert = require('assert');
|
|
||||||
var config = require("../config/specs");
|
var config = require("../config/specs");
|
||||||
var geoServer = require("../geojson-server");
|
|
||||||
var helper = require("./helper");
|
var helper = require("./helper");
|
||||||
|
|
||||||
require("./util/webdriverio-ext");
|
require("./util/webdriverio-ext");
|
||||||
@@ -13,7 +11,12 @@ describe('maputnik', function() {
|
|||||||
"geojson:example",
|
"geojson:example",
|
||||||
"raster:raster"
|
"raster:raster"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
browser.execute(function() {
|
||||||
|
localStorage.setItem("survey", true);
|
||||||
|
});
|
||||||
|
const elem = $(".maputnik-toolbar-link");
|
||||||
|
elem.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ describe("layers", function() {
|
|||||||
"geojson:example",
|
"geojson:example",
|
||||||
"raster:raster"
|
"raster:raster"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
const elem = $(".maputnik-toolbar-link");
|
||||||
|
elem.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
helper.modal.addLayer.open();
|
helper.modal.addLayer.open();
|
||||||
@@ -32,7 +34,8 @@ describe("layers", function() {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:"+id+":delete", ""));
|
const elem = $(wd.$("layer-list-item:"+id+":delete", ""));
|
||||||
|
elem.click();
|
||||||
|
|
||||||
styleObj = helper.getStyleStore(browser);
|
styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -53,7 +56,8 @@ describe("layers", function() {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:"+id+":copy", ""));
|
const elem = $(wd.$("layer-list-item:"+id+":copy", ""));
|
||||||
|
elem.click();
|
||||||
|
|
||||||
styleObj = helper.getStyleStore(browser);
|
styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -82,7 +86,8 @@ describe("layers", function() {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:"+id+":toggle-visibility", ""));
|
const elem = $(wd.$("layer-list-item:"+id+":toggle-visibility", ""));
|
||||||
|
elem.click();
|
||||||
|
|
||||||
styleObj = helper.getStyleStore(browser);
|
styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -95,7 +100,7 @@ describe("layers", function() {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:"+id+":toggle-visibility", ""));
|
elem.click();
|
||||||
|
|
||||||
styleObj = helper.getStyleStore(browser);
|
styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -146,11 +151,13 @@ describe("layers", function() {
|
|||||||
// Setup
|
// Setup
|
||||||
var id = uuid();
|
var id = uuid();
|
||||||
|
|
||||||
browser.selectByValue(wd.$("add-layer.layer-type", "select"), "background");
|
const selectBox = $(wd.$("add-layer.layer-type", "select"));
|
||||||
|
selectBox.selectByAttribute('value', "background");
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
browser.setValueSafe(wd.$("add-layer.layer-id", "input"), "background:"+id);
|
browser.setValueSafe(wd.$("add-layer.layer-id", "input"), "background:"+id);
|
||||||
|
|
||||||
browser.click(wd.$("add-layer"));
|
const elem = $(wd.$("add-layer"));
|
||||||
|
elem.click();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -168,11 +175,13 @@ describe("layers", function() {
|
|||||||
it("id", function() {
|
it("id", function() {
|
||||||
var bgId = createBackground();
|
var bgId = createBackground();
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:background:"+bgId))
|
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
||||||
|
elem.click();
|
||||||
|
|
||||||
var id = uuid();
|
var id = uuid();
|
||||||
browser.setValueSafe(wd.$("layer-editor.layer-id", "input"), "foobar:"+id)
|
browser.setValueSafe(wd.$("layer-editor.layer-id", "input"), "foobar:"+id)
|
||||||
browser.click(wd.$("min-zoom"))
|
const elem2 = $(wd.$("min-zoom"));
|
||||||
|
elem2.click();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -189,9 +198,11 @@ describe("layers", function() {
|
|||||||
it("min-zoom", function() {
|
it("min-zoom", function() {
|
||||||
var bgId = createBackground();
|
var bgId = createBackground();
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:background:"+bgId))
|
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
||||||
|
elem.click();
|
||||||
browser.setValueSafe(wd.$("min-zoom", "input"), 1)
|
browser.setValueSafe(wd.$("min-zoom", "input"), 1)
|
||||||
browser.click(wd.$("layer-editor.layer-id", "input"));
|
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
|
||||||
|
elem2.click();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -219,9 +230,11 @@ describe("layers", function() {
|
|||||||
it("max-zoom", function() {
|
it("max-zoom", function() {
|
||||||
var bgId = createBackground();
|
var bgId = createBackground();
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:background:"+bgId))
|
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
||||||
|
elem.click();
|
||||||
browser.setValueSafe(wd.$("max-zoom", "input"), 1)
|
browser.setValueSafe(wd.$("max-zoom", "input"), 1)
|
||||||
browser.click(wd.$("layer-editor.layer-id", "input"));
|
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
|
||||||
|
elem2.click();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -237,9 +250,11 @@ describe("layers", function() {
|
|||||||
var bgId = createBackground();
|
var bgId = createBackground();
|
||||||
var id = uuid();
|
var id = uuid();
|
||||||
|
|
||||||
browser.click(wd.$("layer-list-item:background:"+bgId));
|
const elem = $(wd.$("layer-list-item:background:"+bgId));
|
||||||
|
elem.click();
|
||||||
browser.setValueSafe(wd.$("layer-comment", "textarea"), id);
|
browser.setValueSafe(wd.$("layer-comment", "textarea"), id);
|
||||||
browser.click(wd.$("layer-editor.layer-id", "input"));
|
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
|
||||||
|
elem2.click();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
assert.deepEqual(styleObj.layers, [
|
assert.deepEqual(styleObj.layers, [
|
||||||
@@ -449,6 +464,7 @@ describe("layers", function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
|
browser.alertAccept();
|
||||||
|
|
||||||
helper.modal.addLayer.open();
|
helper.modal.addLayer.open();
|
||||||
var aId = helper.modal.addLayer.fill({
|
var aId = helper.modal.addLayer.fill({
|
||||||
@@ -482,4 +498,3 @@ describe("layers", function() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
var assert = require('assert');
|
|
||||||
var wd = require("../../wd-helper");
|
|
||||||
var config = require("../../config/specs");
|
var config = require("../../config/specs");
|
||||||
var helper = require("../helper");
|
var helper = require("../helper");
|
||||||
|
|
||||||
@@ -11,6 +9,7 @@ describe("map", function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
])+"#"+zoomLevel+"/41.3805/2.1635");
|
])+"#"+zoomLevel+"/41.3805/2.1635");
|
||||||
|
browser.alertAccept();
|
||||||
|
|
||||||
browser.waitUntil(function () {
|
browser.waitUntil(function () {
|
||||||
return (
|
return (
|
||||||
@@ -24,6 +23,7 @@ describe("map", function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
])+"#"+zoomLevel+"/41.3805/2.1635");
|
])+"#"+zoomLevel+"/41.3805/2.1635");
|
||||||
|
browser.alertAccept();
|
||||||
|
|
||||||
browser.click(".mapboxgl-ctrl-zoom-in")
|
browser.click(".mapboxgl-ctrl-zoom-in")
|
||||||
browser.waitUntil(function () {
|
browser.waitUntil(function () {
|
||||||
|
|||||||
@@ -7,14 +7,16 @@ var helper = require("../helper");
|
|||||||
|
|
||||||
function closeModal(wdKey) {
|
function closeModal(wdKey) {
|
||||||
browser.waitUntil(function() {
|
browser.waitUntil(function() {
|
||||||
return browser.isVisibleWithinViewport(wd.$(wdKey));
|
const elem = $(wdKey);
|
||||||
|
return elem.isDisplayedInViewport();
|
||||||
});
|
});
|
||||||
|
|
||||||
var closeBtnSelector = wd.$(wdKey+".close-modal");
|
const closeBtnSelector = $(wd.$(wdKey+".close-modal"));
|
||||||
browser.click(closeBtnSelector);
|
closeBtnSelector.click();
|
||||||
|
|
||||||
browser.waitUntil(function() {
|
browser.waitUntil(function() {
|
||||||
return !browser.isVisibleWithinViewport(wd.$(wdKey));
|
const elem = $(wdKey);
|
||||||
|
return !elem.isDisplayed();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +28,12 @@ describe("modals", function() {
|
|||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
browser.url(config.baseUrl+"?debug");
|
browser.url(config.baseUrl+"?debug");
|
||||||
|
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
const elem = $(".maputnik-toolbar-link");
|
||||||
|
elem.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:open"))
|
const elem2 = $(wd.$("nav:open"));
|
||||||
|
elem2.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,8 +41,10 @@ describe("modals", function() {
|
|||||||
closeModal("open-modal");
|
closeModal("open-modal");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("upload", function() {
|
// "chooseFile" command currently not available for wdio v5 https://github.com/webdriverio/webdriverio/pull/3632
|
||||||
browser.waitForExist("*[type='file']")
|
it.skip("upload", function() {
|
||||||
|
const elem = $("*[type='file']");
|
||||||
|
elem.waitForExist();
|
||||||
browser.chooseFile("*[type='file']", styleFilePath);
|
browser.chooseFile("*[type='file']", styleFilePath);
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
@@ -50,8 +56,8 @@ describe("modals", function() {
|
|||||||
|
|
||||||
browser.setValueSafe(wd.$("open-modal.url.input"), styleFileUrl);
|
browser.setValueSafe(wd.$("open-modal.url.input"), styleFileUrl);
|
||||||
|
|
||||||
var selector = wd.$("open-modal.url.button");
|
const selector = $(wd.$("open-modal.url.button"));
|
||||||
browser.click(selector);
|
selector.click();
|
||||||
|
|
||||||
// Allow the network request to happen
|
// Allow the network request to happen
|
||||||
// NOTE: Its localhost so this should be fast.
|
// NOTE: Its localhost so this should be fast.
|
||||||
@@ -70,10 +76,12 @@ describe("modals", function() {
|
|||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
browser.url(config.baseUrl+"?debug");
|
browser.url(config.baseUrl+"?debug");
|
||||||
|
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
const elem = $(".maputnik-toolbar-link");
|
||||||
|
elem.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:export"))
|
const elem2 = $(wd.$("nav:export"));
|
||||||
|
elem2.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,8 +107,10 @@ describe("modals", function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
|
browser.acceptAlert();
|
||||||
|
|
||||||
browser.click(wd.$("nav:inspect"));
|
const selectBox = $(wd.$("nav:inspect", "select"));
|
||||||
|
selectBox.selectByAttribute('value', "inspect");
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -108,16 +118,19 @@ describe("modals", function() {
|
|||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
browser.url(config.baseUrl+"?debug");
|
browser.url(config.baseUrl+"?debug");
|
||||||
|
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
const elem = $(".maputnik-toolbar-link");
|
||||||
|
elem.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:settings"))
|
const elem2 = $(wd.$("nav:settings"));
|
||||||
|
elem2.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("name", function() {
|
it("name", function() {
|
||||||
browser.setValueSafe(wd.$("modal-settings.name"), "foobar")
|
browser.setValueSafe(wd.$("modal-settings.name"), "foobar")
|
||||||
browser.click(wd.$("modal-settings.owner"))
|
const elem = $(wd.$("modal-settings.owner"));
|
||||||
|
elem.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
@@ -125,7 +138,8 @@ describe("modals", function() {
|
|||||||
})
|
})
|
||||||
it("owner", function() {
|
it("owner", function() {
|
||||||
browser.setValueSafe(wd.$("modal-settings.owner"), "foobar")
|
browser.setValueSafe(wd.$("modal-settings.owner"), "foobar")
|
||||||
browser.click(wd.$("modal-settings.name"))
|
const elem = $(wd.$("modal-settings.name"));
|
||||||
|
elem.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
@@ -133,7 +147,8 @@ describe("modals", function() {
|
|||||||
})
|
})
|
||||||
it("sprite url", function() {
|
it("sprite url", function() {
|
||||||
browser.setValueSafe(wd.$("modal-settings.sprite"), "http://example.com")
|
browser.setValueSafe(wd.$("modal-settings.sprite"), "http://example.com")
|
||||||
browser.click(wd.$("modal-settings.name"))
|
const elem = $(wd.$("modal-settings.name"));
|
||||||
|
elem.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
@@ -142,7 +157,8 @@ describe("modals", function() {
|
|||||||
it("glyphs url", function() {
|
it("glyphs url", function() {
|
||||||
var glyphsUrl = "http://example.com/{fontstack}/{range}.pbf"
|
var glyphsUrl = "http://example.com/{fontstack}/{range}.pbf"
|
||||||
browser.setValueSafe(wd.$("modal-settings.glyphs"), glyphsUrl)
|
browser.setValueSafe(wd.$("modal-settings.glyphs"), glyphsUrl)
|
||||||
browser.click(wd.$("modal-settings.name"))
|
const elem = $(wd.$("modal-settings.name"));
|
||||||
|
elem.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
@@ -152,7 +168,8 @@ describe("modals", function() {
|
|||||||
it("mapbox access token", function() {
|
it("mapbox access token", function() {
|
||||||
var apiKey = "testing123";
|
var apiKey = "testing123";
|
||||||
browser.setValueSafe(wd.$("modal-settings.maputnik:mapbox_access_token"), apiKey);
|
browser.setValueSafe(wd.$("modal-settings.maputnik:mapbox_access_token"), apiKey);
|
||||||
browser.click(wd.$("modal-settings.name"))
|
const elem = $(wd.$("modal-settings.name"));
|
||||||
|
elem.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
@@ -161,24 +178,37 @@ describe("modals", function() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("open map tiles access token", function() {
|
it("maptiler access token", function() {
|
||||||
var apiKey = "testing123";
|
var apiKey = "testing123";
|
||||||
browser.setValueSafe(wd.$("modal-settings.maputnik:openmaptiles_access_token"), apiKey);
|
browser.setValueSafe(wd.$("modal-settings.maputnik:openmaptiles_access_token"), apiKey);
|
||||||
browser.click(wd.$("modal-settings.name"))
|
const elem = $(wd.$("modal-settings.name"));
|
||||||
|
elem.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
assert.equal(styleObj.metadata["maputnik:openmaptiles_access_token"], apiKey);
|
assert.equal(styleObj.metadata["maputnik:openmaptiles_access_token"], apiKey);
|
||||||
})
|
})
|
||||||
|
|
||||||
it("style renderer", function() {
|
it("thunderforest access token", function() {
|
||||||
var selector = wd.$("modal-settings.maputnik:renderer");
|
var apiKey = "testing123";
|
||||||
browser.selectByValue(selector, "ol3");
|
browser.setValueSafe(wd.$("modal-settings.maputnik:thunderforest_access_token"), apiKey);
|
||||||
browser.click(wd.$("modal-settings.name"))
|
const elem = $(wd.$("modal-settings.name"));
|
||||||
|
elem.click();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
var styleObj = helper.getStyleStore(browser);
|
var styleObj = helper.getStyleStore(browser);
|
||||||
assert.equal(styleObj.metadata["maputnik:renderer"], "ol3");
|
assert.equal(styleObj.metadata["maputnik:thunderforest_access_token"], apiKey);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("style renderer", function() {
|
||||||
|
const selector = $(wd.$("modal-settings.maputnik:renderer"));
|
||||||
|
selector.selectByAttribute('value', "ol");
|
||||||
|
const elem = $(wd.$("modal-settings.name"));
|
||||||
|
elem.click();
|
||||||
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
|
var styleObj = helper.getStyleStore(browser);
|
||||||
|
assert.equal(styleObj.metadata["maputnik:renderer"], "ol");
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
var artifacts = require("../../artifacts");
|
|
||||||
var config = require("../../config/specs");
|
var config = require("../../config/specs");
|
||||||
var helper = require("../helper");
|
var helper = require("../helper");
|
||||||
var wd = require("../../wd-helper");
|
var wd = require("../../wd-helper");
|
||||||
@@ -9,17 +8,16 @@ var wd = require("../../wd-helper");
|
|||||||
describe('screenshots', function() {
|
describe('screenshots', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
browser.windowHandleSize({
|
browser.setWindowSize(1280, 800)
|
||||||
width: 1280,
|
|
||||||
height: 800
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("front_page", function() {
|
it("front_page", function() {
|
||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
const elem = $(".maputnik-toolbar-link");
|
||||||
|
elem.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.takeScreenShot("/front_page.png")
|
browser.takeScreenShot("/front_page.png")
|
||||||
@@ -29,10 +27,13 @@ describe('screenshots', function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
const elem = $(".maputnik-toolbar-link");
|
||||||
|
elem.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:open"))
|
const nav_open = $(wd.$("nav:open"));
|
||||||
|
nav_open.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.takeScreenShot("/open.png")
|
browser.takeScreenShot("/open.png")
|
||||||
@@ -42,10 +43,13 @@ describe('screenshots', function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
const elem = $(".maputnik-toolbar-link")
|
||||||
|
elem.waitForExist()
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:export"))
|
const nav_export = $(wd.$("nav:export"));
|
||||||
|
nav_export.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.takeScreenShot("/export.png")
|
browser.takeScreenShot("/export.png")
|
||||||
@@ -55,10 +59,13 @@ describe('screenshots', function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
const elem = $(".maputnik-toolbar-link")
|
||||||
|
elem.waitForExist()
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:sources"))
|
const nav_sources = $(wd.$("nav:sources"));
|
||||||
|
nav_sources.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.takeScreenShot("/sources.png")
|
browser.takeScreenShot("/sources.png")
|
||||||
@@ -68,10 +75,13 @@ describe('screenshots', function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
const elem = $(".maputnik-toolbar-link")
|
||||||
|
elem.waitForExist()
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:settings"))
|
const nav_settings = $(wd.$("nav:settings"));
|
||||||
|
nav_settings.waitForExist();
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.takeScreenShot("/settings.png")
|
browser.takeScreenShot("/settings.png")
|
||||||
@@ -81,10 +91,14 @@ describe('screenshots', function() {
|
|||||||
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
|
||||||
"geojson:example"
|
"geojson:example"
|
||||||
]));
|
]));
|
||||||
browser.waitForExist(".maputnik-toolbar-link");
|
browser.acceptAlert();
|
||||||
|
const elem = $(".maputnik-toolbar-link")
|
||||||
|
elem.waitForExist()
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.click(wd.$("nav:inspect"))
|
const selectBox = $(wd.$("nav:inspect", "select"));
|
||||||
|
selectBox.selectByAttribute('value', 'inspect');
|
||||||
|
|
||||||
browser.flushReactUpdates();
|
browser.flushReactUpdates();
|
||||||
|
|
||||||
browser.takeScreenShot("/inspect.png")
|
browser.takeScreenShot("/inspect.png")
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ var fs = require("fs");
|
|||||||
var path = require("path");
|
var path = require("path");
|
||||||
|
|
||||||
|
|
||||||
browser.timeoutsAsyncScript(20*1000);
|
browser.setTimeout({ 'script': 20*1000 });
|
||||||
browser.timeoutsImplicitWait(20*1000);
|
browser.setTimeout({ 'implicit': 20*1000 });
|
||||||
|
|
||||||
var SCREENSHOTS_PATH = artifacts.pathSync("/screenshots");
|
var SCREENSHOTS_PATH = artifacts.pathSync("/screenshots");
|
||||||
|
|
||||||
@@ -16,15 +16,18 @@ var SCREENSHOTS_PATH = artifacts.pathSync("/screenshots");
|
|||||||
try {
|
try {
|
||||||
browser.addCommand('setValueSafe', function(selector, text) {
|
browser.addCommand('setValueSafe', function(selector, text) {
|
||||||
for(var i=0; i<10; i++) {
|
for(var i=0; i<10; i++) {
|
||||||
browser.waitForVisible(selector);
|
const elem = $(selector);
|
||||||
|
elem.waitForDisplayed(500);
|
||||||
|
|
||||||
var elements = browser.elements(selector);
|
var elements = browser.findElements("css selector", selector);
|
||||||
if(elements.length > 1) {
|
if(elements.length > 1) {
|
||||||
throw "Too many elements found";
|
throw "Too many elements found";
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.setValue(selector, text);
|
const elem2 = $(selector);
|
||||||
var browserText = browser.getValue(selector);
|
elem2.setValue(text);
|
||||||
|
|
||||||
|
var browserText = elem2.getValue();
|
||||||
|
|
||||||
if(browserText == text) {
|
if(browserText == text) {
|
||||||
return;
|
return;
|
||||||
@@ -39,17 +42,17 @@ try {
|
|||||||
})
|
})
|
||||||
|
|
||||||
browser.addCommand('takeScreenShot', function(filepath) {
|
browser.addCommand('takeScreenShot', function(filepath) {
|
||||||
var data = browser.screenshot();
|
var data = browser.takeScreenshot();
|
||||||
fs.writeFileSync(path.join(SCREENSHOTS_PATH, filepath), data.value, 'base64');
|
fs.writeFileSync(path.join(SCREENSHOTS_PATH, filepath), data.value, 'base64');
|
||||||
});
|
});
|
||||||
|
|
||||||
browser.addCommand('flushReactUpdates', function() {
|
browser.addCommand('flushReactUpdates', function() {
|
||||||
browser.executeAsync(function(done) {
|
browser.executeAsync(function(done) {
|
||||||
// For any events to propogate
|
// For any events to propogate
|
||||||
setImmediate(function() {
|
setTimeout(function() {
|
||||||
// For the DOM to be updated.
|
// For the DOM to be updated.
|
||||||
setImmediate(done);
|
setTimeout(done, 0);
|
||||||
})
|
}, 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user