Compare commits

...

361 Commits

Author SHA1 Message Date
orangemug
5f1e212759 1.6.0 2019-10-08 18:28:56 +01:00
pathmapper
2b7db498ef Merge pull request #548 from pathmapper/popup_scroll
Make popups scrollable
2019-09-17 10:00:29 +02:00
pathmapper
e6464790f6 Merge pull request #549 from pathmapper/beta_readme
Add 1.6.0-beta to readme
2019-08-22 10:55:31 +02:00
pathmapper
13ddf9f754 Link 1.6 beta from readme 2019-08-22 10:26:53 +02:00
pathmapper
30edb881ed Ensure popup is fully visible 2019-08-21 14:41:14 +02:00
pathmapper
b30bbdc248 Calculate popup height depending on viewport 2019-08-10 10:37:39 +02:00
pathmapper
824616f6bd Make popups scrollable 2019-08-09 15:55:52 +02:00
orangemug
109198a524 1.6.0-beta 2019-08-02 18:08:21 +02:00
pathmapper
920e4fe630 Merge pull request #545 from pathmapper/navcontrols
Fix css and increase color contrast for navcontrols
2019-08-01 14:21:51 +02:00
pathmapper
5e143e0a8e Fix css and increase color contrast 2019-08-01 13:59:34 +02:00
pathmapper
57f803d52c Merge pull request #544 from pathmapper/update_styles
Update available styles
2019-07-30 14:19:38 +02:00
pathmapper
c55d342c7e Remove empty line 2019-07-30 13:58:18 +02:00
pathmapper
e9065635cd Add toner and move empty style 2019-07-30 13:51:05 +02:00
pathmapper
6057721249 Merge pull request #543 from pathmapper/update_mb_dependencies
Update mapbox dependencies to current version
2019-07-30 10:51:04 +02:00
pathmapper
975487d271 Use visualizePitch option 2019-07-30 08:10:56 +02:00
pathmapper
46b2fd5978 Update mapbox dependencies to current version 2019-07-30 06:45:21 +02:00
pathmapper
f61313449f Merge pull request #542 from pathmapper/update_maptiler_key
Update Maptiler key
2019-07-29 20:35:07 +02:00
pathmapper
366ad4d7df Update Maptiler key
Provided by @klokan via e-mail
2019-07-29 18:21:42 +02:00
pathmapper
b5cfb44cf0 Merge pull request #512 from JesseCrocker/local-port
Add support for connecting to a local server on port other than 8000
2019-07-29 17:16:36 +02:00
pathmapper
050cc9cea9 Merge pull request #539 from pathmapper/update_mbgl
Update mapbox-gl and style spec to latest release
2019-07-17 09:41:58 +02:00
pathmapper
b2f194eeee Update mapbox-gl to 1.1.1 2019-07-17 09:18:47 +02:00
pathmapper
97b0b8541d Update mapbox-gl and style spec 2019-07-02 11:04:44 +02:00
Orange Mug
b5eb74fe20 Merge pull request #532 from pathmapper/fix_issue_471
Remove UNSAFE_componentWillUpdate
2019-06-28 16:42:08 +01:00
pathmapper
0500172d42 Update layer types 2019-06-23 09:55:32 +02:00
pathmapper
0e7bd98485 Use componentDidUpdate 2019-06-23 09:53:52 +02:00
Orange Mug
ff0ece5149 Merge pull request #531 from orangemug/fix/issue-509
Ability to delete properties completely
2019-06-21 17:42:09 +01:00
orangemug
db9ad86ac2 Filter empty strings in <FontInput/> 2019-06-21 08:05:09 +01:00
orangemug
a066710bfb Always put an empty field and the end of the FontInput and remove blank fields if empty 2019-06-21 07:58:43 +01:00
orangemug
52740483b6 Merge remote-tracking branch 'upstream/master' into fix/issue-509 2019-06-20 20:40:22 +01:00
orangemug
518a624e20 Updated to mapbox-gl 1.1.0-beta.1 2019-06-20 20:37:26 +01:00
pathmapper
4ba71c8bd5 Remove UNSAFE_componentWillUpdate 2019-06-20 09:52:54 +02:00
orangemug
ceeb628784 Added ability to delete color value. 2019-06-19 20:44:27 +01:00
orangemug
2ec6a90dc3 Allow removal of properties. 2019-06-19 20:34:28 +01:00
Orange Mug
4e37d834ed Merge pull request #530 from pathmapper/remove_node11
Remove node v11 from test runners
2019-06-17 09:03:28 +01:00
pathmapper
a7922d894d Remove node v11 from test runners 2019-06-17 09:05:39 +02:00
pathmapper
eeda3296ab Merge pull request #514 from pathmapper/node_v12_test_runners
Add node v12 to test runners
2019-06-17 06:49:30 +02:00
pathmapper
acd26e0162 Update only if wanted version is not available 2019-06-16 22:03:37 +02:00
pathmapper
fbf828e202 Merge branch 'master' into node_v12_test_runners 2019-06-16 21:21:33 +02:00
pathmapper
af9015f529 Merge pull request #526 from pathmapper/update_wdio
Update WebdriverIO to v5
2019-06-16 11:53:09 +02:00
Orange Mug
7a172b2022 Merge pull request #528 from pathmapper/renderer_metadata
Set default renderer if undefined
2019-06-16 10:03:37 +01:00
pathmapper
a609dc4029 Set default renderer if undefined 2019-06-16 05:27:53 +02:00
pathmapper
92bfee4bcc Use number input again for min/max-zoom tests 2019-06-13 07:18:35 +02:00
pathmapper
559d4618d1 Update wdio dependencies 2019-06-13 07:05:02 +02:00
pathmapper
5c391ee287 Update selenium-standalone used for testing and move wdio-steps to node active LTS 2019-06-08 18:34:03 +02:00
pathmapper
db74cfeb2a Update express and is-docker dependencies 2019-06-08 18:10:54 +02:00
pathmapper
726b825e4c Update mocha dependency 2019-06-08 17:55:08 +02:00
pathmapper
84d56b2606 Skip "upload" test for the open modal 2019-06-08 17:16:51 +02:00
pathmapper
e9a8b094a2 Update tests for WebdriverIO v5 2019-06-08 07:19:18 +02:00
pathmapper
924b14621a Update wdio.conf.js 2019-06-08 07:04:41 +02:00
pathmapper
b072e3a98c Update wdio and selenium dependencies 2019-06-08 06:59:47 +02:00
Orange Mug
827bd5fa24 Merge pull request #524 from orangemug/feature/ol-improvements
Further OpenLayers improvements
2019-06-01 17:39:40 +01:00
orangemug
9e0410afe6 Fix to reset map state on renderer change. 2019-06-01 08:28:22 +01:00
orangemug
ef08a9347e Disable debugToolbox by default. 2019-05-29 19:18:24 +01:00
orangemug
9b732540a6 Remove commented out code. 2019-05-29 19:16:17 +01:00
orangemug
24c52074b8 Fixed lint errors. 2019-05-29 18:59:26 +01:00
orangemug
cb6c6e0d9f Remove projection code for now. 2019-05-29 18:54:32 +01:00
orangemug
884dc6fa49 Fixed typos. 2019-05-29 18:28:47 +01:00
orangemug
efe42021f1 Some more openlayers improvments as well as initial work for projection support 2019-05-29 17:37:55 +01:00
pathmapper
470277c253 Merge pull request #523 from orangemug/feature/update-ol-plus-stability-fixes
Update openlayers + stability fixes
2019-05-22 09:47:13 +02:00
orangemug
c1cab38c7a Merge remote-tracking branch 'upstream/master' into feature/update-ol-plus-stability-fixes 2019-05-22 07:51:13 +01:00
orangemug
1cf36ddb08 Updated ol-mapbox-style/ol, added throttle to ol map update and fix to clear layers. 2019-05-22 07:32:06 +01:00
Orange Mug
cc3c17078d Merge pull request #517 from orangemug/fix/editing-of-number-and-string-inputs
Fix for string/number inputs when inputting invalid of intermediate values
2019-05-20 10:15:25 +01:00
pathmapper
47965d5f57 Merge pull request #518 from orangemug/fix/color-accessibility-ua-switch
Toggle color accessibility based on user agent
2019-05-19 19:58:42 +02:00
Orange Mug
c947d9e3ed Merge pull request #516 from orangemug/fix/prevent-default-browser-undo-redo
Prevent native browser undo/redo when handled by app
2019-05-19 18:52:14 +01:00
Orange Mug
8318180e96 Merge pull request #519 from orangemug/feature/debug-modal
Added semi-hidden debug modal
2019-05-19 18:51:43 +01:00
orangemug
87daf6fb76 Fixes because I was incorrectly assuming mgljs only 2019-05-19 10:09:44 +01:00
pathmapper
5d254ac2ff Merge pull request #520 from orangemug/fix/color-filters-with-ol
Layout issues in accessibility color filters with the openlayers renderer
2019-05-19 10:04:16 +02:00
orangemug
482f322d9f Fixed layout issues in accessibilty color filters with the openlayers renderer
Also updated ol & ol-mapbox-style
2019-05-19 06:25:28 +01:00
Orange Mug
da456b08fe Merge pull request #444 from orangemug/feature/issue-433
Added color swatch to <FeatureLayerPopup/>
2019-05-18 19:15:27 +01:00
orangemug
2776ac3ce0 Added back in onOpenToggle 2019-05-18 19:06:54 +01:00
orangemug
eb2fc4c715 Fixed prop-type name. 2019-05-18 19:02:51 +01:00
orangemug
d2ffc3a0b1 Added semi-hidden debug modal and removed all this url param awkwardness 2019-05-18 18:54:10 +01:00
orangemug
40a9978f31 Removed console.log 2019-05-18 18:23:02 +01:00
orangemug
22688933b3 Toggle color accessibility based on user agent
It's only supported in chrome and firefox and we can't feature detect this.
2019-05-18 18:19:23 +01:00
orangemug
7cf01f0c12 Merge remote-tracking branch 'upstream/master' into feature/issue-433 2019-05-18 17:33:31 +01:00
orangemug
1e87765f95 Tidy and added fallback (exception) color. 2019-05-18 17:29:59 +01:00
orangemug
cc8fe4e02e Fix for buggy string/number inputs when inputting invalid of intermediate values 2019-05-18 15:56:14 +01:00
orangemug
979fc98e70 Prevent native browser undo/redo when handled by app 2019-05-18 15:47:51 +01:00
pathmapper
c3c0c35d8a Merge pull request #513 from pathmapper/update_readme
Update readme
2019-05-05 15:13:47 +02:00
pathmapper
f9913cad63 Use jsDelivr for Maputnik logo and sponsor images
To make them appear on https://hub.docker.com/r/maputnik/editor/.
2019-05-05 09:14:11 +02:00
pathmapper
0ed64f94e8 Fix link devDependencies 2019-05-05 08:58:34 +02:00
pathmapper
1deecd4e2a Update node-sass 2019-05-05 08:48:25 +02:00
pathmapper
e0e9201b46 Add node v12 to appveyor 2019-05-05 07:57:41 +02:00
pathmapper
362c7b437e Add node v12 to circleci 2019-05-05 07:47:50 +02:00
Jesse Crocker
efd0b547e9 Fix this being undefined 2019-04-16 09:19:36 -06:00
Jesse Crocker
afbaaa66bc Add support for using a port other than 8000 2019-04-16 08:23:21 -06:00
pathmapper
de8c687dfb Merge pull request #483 from sk1p/popup-cleanup
Various cleanups regarding popups and inspect mode
2019-04-16 07:29:19 +02:00
pathmapper
1bb079f078 Merge pull request #501 from pathmapper/issue_484
Fix JSON-editor
2019-04-16 07:05:43 +02:00
pathmapper
b35322522f Merge pull request #499 from pathmapper/scrollbar-ff
Firefox scrollbar styling
2019-04-15 07:48:15 +02:00
pathmapper
6a605571e0 Firefox scrollbar styling 2019-04-15 07:14:10 +02:00
pathmapper
c4ec77c911 Merge pull request #511 from pathmapper/update_stylelint
Update stylelint and remove node 6 from test-runners
2019-04-15 06:39:26 +02:00
pathmapper
f7643cee7e Fix appveyor lint-styles 2019-04-14 10:05:15 +02:00
pathmapper
cd95202dcc Update stylelint and remove node 6 from test-runners 2019-04-14 09:35:58 +02:00
pathmapper
facba3998b Merge pull request #510 from lukaswelte/master
Update mapbox dependencies to current version
2019-04-11 09:24:54 +02:00
Lukas Welte
1f9cc2ce33 Update mapbox dependencies to current version 2019-04-10 14:09:44 +02:00
pathmapper
ad505378ab Merge pull request #508 from pathmapper/master
Change Maptiler URLs
2019-04-08 10:13:16 +02:00
pathmapper
fb7b30c81d Add backwards compability for tilehosting URLs 2019-04-06 12:00:48 +02:00
pathmapper
cb3f93c67d Change Maptiler URLs 2019-04-03 09:51:43 +02:00
pathmapper
76cb5a6b7c Merge pull request #505 from maputnik/revert-504-readme_images
Revert "Readme: Use absolute URLs for logo and sponsor images"
2019-03-11 20:45:15 +01:00
pathmapper
990e47cb24 Revert "Readme: Use absolute URLs for logo and sponsor images" 2019-03-11 20:07:08 +01:00
pathmapper
8be7465428 Merge pull request #504 from pathmapper/readme_images
Use absolute URLs for logo and sponsor images
2019-03-11 19:31:14 +01:00
pathmapper
e2b7a6a517 Use absolute URLs for logo and sponsor images 2019-03-11 14:47:47 +01:00
pathmapper
284e00b665 Merge pull request #500 from pathmapper/appveyor_lint-styles
Appveyor add again lint-styles
2019-03-11 10:26:31 +01:00
pathmapper
1fbfa8428b Remove unused argument 2019-03-07 13:47:53 +01:00
pathmapper
b13b89c7ce Fix JSON-editor 2019-03-07 13:06:02 +01:00
pathmapper
3fa31e2a2e Merge pull request #485 from sk1p/icon-input-value-type
IconInput value type should be string, not array
2019-03-06 07:22:23 +01:00
pathmapper
0e7d2d8ff5 Appveyor lint-styles 2019-03-05 13:43:34 +01:00
pathmapper
2def3820dd Merge pull request #498 from pathmapper/node10-slim_for_docker
Use node:10-slim for Docker
2019-03-01 17:42:13 +01:00
pathmapper
e1a489f318 Use node:10-slim for Docker 2019-02-27 13:50:27 +01:00
pathmapper
760839eb83 Merge pull request #497 from pathmapper/externally-accessible-dev-server
How to start externally accessible dev server
2019-02-15 19:51:30 +01:00
pathmapper
7136d2dea3 How to start externally accessible dev server 2019-02-15 19:25:14 +01:00
pathmapper
21f4d26b50 Merge pull request #482 from sk1p/type-formatted-hotfix
Use StringInput for 'formatted' field type
2019-02-11 12:50:38 +01:00
pathmapper
627ea268c9 Merge pull request #495 from maputnik/fix/popup-tip-border-color
Fix/popup tip border color
2019-02-10 19:09:24 +01:00
pathmapper
65f77db4d6 Merge pull request #494 from pathmapper/fix-docker
Fix Docker
2019-02-10 10:11:12 +01:00
pathmapper
03b4f1eb8d Update Dockerfile 2019-02-10 07:58:37 +01:00
pathmapper
82d1fc0a8b Merge pull request #492 from pathmapper/pathmapper-readme-cli
Adding information regarding CLI
2019-02-09 14:36:38 +01:00
pathmapper
3b16de5df4 Adding information regarding CLI again 2019-02-09 14:15:23 +01:00
pathmapper
94d4070653 Merge pull request #490 from pathmapper/pathmapper-warning_docker
Dockerfile fix warning
2019-01-31 09:05:03 +01:00
pathmapper
d856a1cd8e Dockerfile fix warning 2019-01-29 14:14:28 +01:00
pathmapper
7c1d0f7bee Merge pull request #480 from pathmapper/update_status_badge
Update status badge: travis -> circleci
2019-01-22 14:46:36 +01:00
pathmapper
d02036321f Merge pull request #486 from fredj/spelling
Correct some spelling mistake in README
2019-01-22 14:44:45 +01:00
Frederic Junod
1edadcc6bb Correct some spelling mistake in README 2019-01-18 16:30:36 +01:00
Alexander Clausen
aa92091d2d IconInput value type should be string, not array 2019-01-06 18:07:38 +01:00
Alexander Clausen
14e0385575 Reflect current view in dropdown
Useful if the view is toggled between inspect and map via keyboard
shortcut.
2019-01-06 06:20:03 +01:00
Alexander Clausen
6cf861d44e Keep inspect mode consistent across renderer changes
If you were in inspect mode, switched to open layers, and back to
MapboxGlMap, the state in MapboxGlMap and MapboxInspect diverged,
meaning MapboxGlMap thought it was in inspect mode, while MapboxInspect
had inspector mode disabled.
2019-01-06 06:02:10 +01:00
Alexander Clausen
1375240bfa For better debuggability, actually keep one popup node around 2019-01-06 06:01:04 +01:00
Alexander Clausen
8f391d7d52 Cleanup popup nodes
Before, the component instances used for rendering popup content were
kept around, slowly leaking memory. This could be observed using react
developer tools.
2019-01-06 05:49:03 +01:00
Alexander Clausen
84654e81af MapboxGlMap: remove unused state variables 2019-01-06 05:39:19 +01:00
Alexander Clausen
7ff0524bb7 Allow inspect mode to count properly
Example: https://maputnik.github.io/editor/#12.86/54.38618/9.76697
2019-01-06 05:35:43 +01:00
Alexander Clausen
06c3c92fd6 Ensure key uniqueness in FeaturePropertyPopup 2019-01-06 05:35:11 +01:00
Alexander Clausen
4c2941e9b6 Use StringInput for 'formatted' field type 2019-01-05 22:38:58 +01:00
pathmapper
ed7a25646e Use master branch 2019-01-05 08:16:22 +01:00
pathmapper
8c821176cf Update status badge: travis -> circleci 2019-01-04 20:31:01 +01:00
Orange Mug
98ded98583 Merge pull request #476 from pathmapper/fix_issue_404
Use access token for fetchSources
2019-01-04 14:34:26 +00:00
Orange Mug
10ae69e41f Merge pull request #479 from maputnik/revert-465-source_layer_list
Revert "Use replaceAccessTokens in onStyleChanged"
2019-01-04 14:33:35 +00:00
Orange Mug
04531b4305 Revert "Use replaceAccessTokens in onStyleChanged" 2019-01-04 14:32:41 +00:00
Orange Mug
7ab4b2481c Merge pull request #477 from pathmapper/update_OMT_styles
Update OMT styles for v3.9
2019-01-04 14:31:37 +00:00
Orange Mug
6fa88e6869 Merge pull request #468 from meetar/shouldComponentUpdate-patch
shouldComponentUpdate hack
2019-01-04 14:31:14 +00:00
Orange Mug
5b90c31645 Merge pull request #465 from pathmapper/source_layer_list
Use replaceAccessTokens in onStyleChanged
2019-01-04 14:29:55 +00:00
pathmapper
5eba11faee Update OMT styles for v3.9 2018-12-19 10:57:06 +01:00
pathmapper
54b4fc473c Use access token for fetchSources 2018-11-27 13:51:26 +01:00
Orange Mug
fe8595cdc9 Merge pull request #474 from pathmapper/empty_style
Update empty-style URL
2018-11-26 10:48:07 +00:00
Orange Mug
2565a89474 Merge pull request #475 from pathmapper/rtl_blob
Set MIME type for RTL blob
2018-11-26 10:47:42 +00:00
pathmapper
92b970377d Set MIME type for RTL blob 2018-11-25 08:13:21 +01:00
pathmapper
dd3a550ec3 Update empty-style URL 2018-11-24 20:15:05 +01:00
Orange Mug
7d5d9e2d82 Merge pull request #470 from pathmapper/node_v11
Add node v11 to test runners and fix Appveyor
2018-11-19 11:42:52 +00:00
pathmapper
9daa71befc Bump node-sass 2018-11-18 19:23:06 +01:00
pathmapper
f10a2d28df Use Visual Studio 2015 for Appveyor 2018-11-18 11:05:29 +01:00
Orange Mug
d52c6c70bb Merge pull request #469 from morlay/master
make docker simpler
2018-11-17 15:34:18 +00:00
Orange Mug
6e2f46a0da Merge pull request #464 from pathmapper/fix-wobbling
Fix layer list wobbling
2018-11-17 15:18:41 +00:00
Orange Mug
2d6f91d0cd Merge pull request #447 from orangemug/fix/lock-logo-size-in-survey-popup
Fix the size of the logo in the survey modal
2018-11-17 15:17:51 +00:00
pathmapper
f409079d93 Bump wdio-mocha-framework 2018-11-17 13:48:17 +01:00
pathmapper
7004259867 Use previous VS image 2018-11-17 12:19:15 +01:00
pathmapper
f3128cb6d2 Add node v11 to test runners 2018-11-17 11:08:55 +01:00
Morlay
1dfd4d8d48 make docker simpler 2018-11-14 14:48:08 +08:00
Peter Richardson
2f30eb6cbe rm build 2018-11-12 20:50:20 -08:00
Peter Richardson
cf45c04069 add shouldComponentUpdate
checks for differences in props and state - addresses https://github.com/maputnik/editor/issues/467
2018-11-12 20:47:24 -08:00
Peter Richardson
05d149bcfa added build 2018-11-12 16:35:18 -08:00
pathmapper
3acbc3291c Remove duplicated line 2018-11-05 21:08:34 +01:00
pathmapper
24aa2fd5fa Use replaceAccessToken onStyleChanged 2018-11-05 08:17:21 +01:00
pathmapper
cc7d7a56f5 Fix wobbling 2018-11-04 10:38:58 +01:00
Orange Mug
da17646b8d Merge pull request #463 from pathmapper/color-contrast
Increase color contrast (fixes #318)
2018-11-04 09:08:30 +00:00
pathmapper
f9233a1e31 Increase color contrast 2018-11-03 18:01:34 +01:00
Orange Mug
c94f536e5a Merge pull request #459 from orangemug/feature/layer-list-ui-handle
Make whole layer label draggable
2018-11-02 20:48:14 +00:00
orangemug
b712e7f184 Merge remote-tracking branch 'upstream/master' into feature/layer-list-ui-handle
Conflicts:
	src/styles/_layer.scss
2018-11-02 17:57:42 +00:00
Orange Mug
b8ab802de5 Merge pull request #461 from pathmapper/helper
Style layer which is dragged
2018-11-02 17:55:44 +00:00
pathmapper
a74eb2989c Add helper class and style the helper 2018-11-02 14:25:31 +01:00
Orange Mug
0f0684e701 Merge pull request #458 from pathmapper/debug
Add 'show-overdraw-inspector' query parameter
2018-11-02 08:50:20 +00:00
orangemug
e9b5bfb572 Remove old code. 2018-11-02 08:34:56 +00:00
pathmapper
59ad91fdf8 Add 'show-overdraw-inspector' query parameter 2018-11-02 09:07:01 +01:00
orangemug
5de5281b49 Prevent title resize. 2018-11-01 18:26:21 +00:00
orangemug
deec7894dd Make the whole layer label draggable 2018-11-01 08:12:40 +00:00
Orange Mug
c9a0c0400e Merge pull request #455 from pathmapper/autocomplete_z-index
Add z-index to autocomplete
2018-10-31 18:21:15 +00:00
pathmapper
419e62f69b Add z-index to autocomplete 2018-10-31 18:53:15 +01:00
Orange Mug
9ffbe3a7a2 Merge pull request #453 from orangemug/fix/more-menu-icon-size
Increase more-menu icon size, broken by react-icons update
2018-10-31 13:44:48 +00:00
orangemug
c8e548e3be Increase more-menu icon size, broken by react-icons update 2018-10-31 08:19:12 +00:00
Orange Mug
b9160bd333 Merge pull request #451 from pathmapper/firefox_select
Improve select styling
2018-10-31 08:05:22 +00:00
Orange Mug
8ad8b4cdea Merge pull request #450 from pathmapper/activate_renderer-test
Update tests for style settings modal
2018-10-31 08:03:18 +00:00
Orange Mug
c51c40a20e Merge pull request #448 from orangemug/fix/shortcut-fixes
Shortcut fixes
2018-10-31 08:02:13 +00:00
Orange Mug
2ccb1a8e0a Merge pull request #446 from pathmapper/fix-445
Remove node v9 from appveyor and update readme
2018-10-31 08:00:48 +00:00
pathmapper
008bb75c04 Remove border for select select 2018-10-30 22:12:52 +01:00
orangemug
82d3c934c8 Merge remote-tracking branch 'upstream/master' into fix/lock-logo-size-in-survey-popup 2018-10-30 20:26:17 +00:00
orangemug
f2f0270936 Use KeyboardEvent.key rather than keyCode in attempt to support more keyboard layouts. 2018-10-30 20:12:48 +00:00
pathmapper
00f646d489 Update test runners and readme 2018-10-30 21:08:42 +01:00
Orange Mug
90a02df45c Merge pull request #449 from orangemug/fix/popup-tip-border-color
Fixes for 'mapboxgl-popup-tip' border color.
2018-10-30 19:57:52 +00:00
Orange Mug
ad40a15a77 Merge pull request #443 from pathmapper/style-parameter
Warning when style parameter is used
2018-10-30 19:50:26 +00:00
Orange Mug
d6809cb504 Merge pull request #409 from orangemug/fix/color-accessibility-styling-issue
Added hacks to fix styling issue
2018-10-30 19:46:41 +00:00
pathmapper
ff8d3055b4 Add test for TF access token 2018-10-30 20:37:56 +01:00
orangemug
1f81449e3c Added a visible drag handle. 2018-10-30 19:32:20 +00:00
pathmapper
c59e0cb046 Activate renderer test 2018-10-30 20:06:52 +01:00
pathmapper
2d1675c181 Fixes for code review comments 2018-10-29 17:35:12 +01:00
orangemug
48ebca6236 Fix the size of the logo in the survey modal so it doesn't cause a visual jump when it loads. 2018-10-29 13:12:01 +00:00
orangemug
bce8e8b807 Shortcut fixes
- Allow shortcuts to trigger from the shortcut modal
 - Fix inspect/map toggle shortcut
2018-10-29 13:07:49 +00:00
pathmapper
1f4dfa7603 Update readme (Node versions and CLI) 2018-10-29 13:56:09 +01:00
pathmapper
c23af0063d Add node v11 to the test runners 2018-10-29 13:42:01 +01:00
pathmapper
9f6250c489 Update tests 2018-10-29 11:16:27 +01:00
orangemug
cf6c6f1c17 Fixes for 'mapboxgl-popup-tip' border color. 2018-10-27 14:41:50 +01:00
orangemug
c6163b6ba2 Added missing css for popup changes 2018-10-27 14:14:20 +01:00
orangemug
7121a680b4 Add missing prop type 2018-10-27 14:08:23 +01:00
orangemug
cf391031f0 Added color swatch to <FeatureLayerPopup/> 2018-10-27 14:04:13 +01:00
pathmapper
9cac5305cd Warning when style parameter is used 2018-10-25 19:37:39 +02:00
pathmapper
b0adb8cd3d Remove style querystring parameter 2018-10-25 19:34:19 +02:00
Orange Mug
3d2a1d5d19 Merge pull request #439 from orangemug/feature/style-spec-additions
Style spec additions
2018-10-24 19:13:53 +01:00
Orange Mug
3c93c41de1 Merge pull request #442 from pathmapper/export-style-name
Use style name for export filename if available
2018-10-24 08:36:22 +01:00
Orange Mug
4baed5d8ab Merge pull request #441 from pathmapper/image_property
Move image property
2018-10-24 08:32:47 +01:00
pathmapper
f17b02b1fe Slugify result in lower case 2018-10-23 13:31:38 +02:00
pathmapper
3c72d07a88 Export stylename if available 2018-10-23 13:04:23 +02:00
orangemug
7495c0dfcf Added support for 'fill-extrusion-vertical-gradient' and 'symbol-z-order'
Also updated @mapbox/mapbox-gl-style-spec
2018-10-22 22:29:23 +01:00
Orange Mug
b0c877d4ae Merge pull request #418 from orangemug/fix/modal-exit-apply-changes
Delay modal close until blur events have triggered
2018-10-22 16:32:39 +01:00
Orange Mug
e1fd0f8014 Merge pull request #438 from pathmapper/top_issues
Create .topissuesrc
2018-10-21 11:48:21 +01:00
pathmapper
cb2198b661 Move image property 2018-10-21 11:28:05 +02:00
pathmapper
68beeeb599 Create .topissuesrc 2018-10-20 18:35:35 +02:00
orangemug
218ce148d5 Delay modal close until blur events have triggered. 2018-10-19 08:39:07 +01:00
Orange Mug
d0cafb06ee Merge pull request #414 from pathmapper/osm-libety_gh-pages
Use GitHub Pages for OSM Liberty
2018-10-17 13:06:13 +01:00
Orange Mug
5671a58704 Merge pull request #415 from pathmapper/pathmapper-update_empty-style
Update Empty Style
2018-10-17 13:05:19 +01:00
pathmapper
9cf74ca405 Update empty-style.json 2018-10-17 13:16:10 +02:00
pathmapper
1c6e3648eb Use GitHub Pages for OSM Liberty 2018-10-16 07:32:25 +02:00
Orange Mug
b3a4628a79 Merge pull request #413 from pathmapper/rawgit_shutdown
rawgit.com -> jsDelivr
2018-10-15 15:03:31 +01:00
pathmapper
941cc37c87 rawgit.com -> jsDelivr 2018-10-13 09:13:34 +02:00
Orange Mug
906d7ac3d5 Merge pull request #411 from orangemug/fix/autocomplete-only-autocomplete-strings
Stop autocomplete crashing the editor
2018-10-12 19:24:35 +01:00
orangemug
588b18d10e Only render strings in the autocomplete menu.
Expression objects were making there way into this menu which was crashing the editor.
2018-10-11 21:39:52 +01:00
Orange Mug
90024c5ec7 Merge pull request #408 from orangemug/maintenance/update-deps-2018-10-06
Updated dependencies
2018-10-10 15:15:26 +01:00
orangemug
889005de6c Set default value of autocomplete shouldItemRender value param as sometimes it's undefined 2018-10-09 21:11:47 +01:00
orangemug
843d3df8bc Fixed react-icons size/position. 2018-10-09 20:58:14 +01:00
orangemug
825b9044b9 Tidy naming. 2018-10-09 20:48:10 +01:00
orangemug
92a1be83b6 Moved babel config back out into .babelrc
This is due to .babelrc being used for other tooling.
2018-10-09 20:45:03 +01:00
orangemug
36e35eb208 Tidy webpack configs. 2018-10-09 20:43:35 +01:00
orangemug
2fcdb47fe5 Moved babel config, removed uglifyJs and decorators plugins 2018-10-09 20:14:16 +01:00
orangemug
012e4b670e Updated mapbox-gl and react-sortable-hoc usage 2018-10-08 09:32:21 +01:00
orangemug
492cc244d8 Added hacks to fix styling issue
See <https://github.com/maputnik/editor/pull/392#issuecomment-427595172>
2018-10-06 22:14:59 +01:00
orangemug
d17d6b43c0 Fixed merge issue. 2018-10-06 21:57:24 +01:00
orangemug
1bf10cd6d6 Merge remote-tracking branch 'upstream/master' into maintenance/update-deps-2018-10-06
Conflicts:
	src/components/App.jsx
	src/components/Toolbar.jsx
2018-10-06 21:39:26 +01:00
orangemug
b0cd9140be Updated remaining deps. 2018-10-06 21:21:42 +01:00
orangemug
cc6196969f Updated lockfile 2018-10-06 21:07:48 +01:00
orangemug
802a7eb1be Updated babel/webpack 2018-10-06 21:05:33 +01:00
Orange Mug
a666f86be0 Merge pull request #397 from orangemug/feature/ol-experimental-support
Added back in experimental OpenLayers support
2018-10-06 16:59:13 +01:00
Orange Mug
44fad76d45 Merge pull request #392 from orangemug/feature/color-accessibility-ui
Color accessibility UI
2018-10-06 16:58:32 +01:00
orangemug
c8d23a534e Updated some dev deps 2018-10-06 16:44:23 +01:00
orangemug
cf3650c8cd Updated react-icons 2018-10-06 16:38:08 +01:00
orangemug
1a8349f821 Updated deps with no API changes. 2018-10-06 16:01:34 +01:00
orangemug
855771a6b6 npm is getting confused about the lockfile. This now works locally from fresh install 2018-10-06 15:13:25 +01:00
orangemug
b711168e44 Move package-lock.json fixes... :/ 2018-10-06 15:00:34 +01:00
orangemug
4134919dde Added missing key attribute to <option/> 2018-10-06 14:44:12 +01:00
orangemug
158153e366 Revert package-lock.json update and build against node:10 2018-10-06 14:42:33 +01:00
orangemug
6b890d162a Merge remote-tracking branch 'upstream/master' into feature/ol-experimental-support 2018-10-06 14:06:10 +01:00
orangemug
1525807d06 setValue -> selectByValue 2018-10-06 13:59:50 +01:00
orangemug
a356bfd601 Switch from react-aria-menubutton to <select/> 2018-10-06 13:54:02 +01:00
Orange Mug
e6d2a6d5ff Merge pull request #384 from orangemug/feature/os-open-zoomstack
OS Open Zoomstack
2018-10-06 13:25:23 +01:00
Orange Mug
c8a004422f Merge pull request #406 from emuanalytics/bug/#380
Fix for Issue #380
2018-10-06 13:18:31 +01:00
Orange Mug
df457fc7bf Merge pull request #407 from pathmapper/get-name
Get name from package.json
2018-10-04 18:15:42 +01:00
pathmapper
6e03f1f077 get name from package.json 2018-10-04 16:42:39 +02:00
Robin Summerhill
4c13350c14 Issue #380 - fix for data functions 2018-10-03 20:11:53 +01:00
Orange Mug
63a2495c68 Merge pull request #405 from pathmapper/update-tilehosting
Update tilehosting URL
2018-10-01 21:21:24 +01:00
pathmapper
f9de73e18a Update tilehosting URL 2018-10-01 10:13:00 +02:00
Orange Mug
e6e2be61f0 Merge pull request #398 from orangemug/fix/load-url-error
Show error if style fails to load and disabled button if input is empty
2018-09-26 18:35:51 +01:00
Gregory Wolanski
060f7aa42c Merge pull request #400 from orangemug/feature/make-layer-visibility-always-shown
Make layer visibility icon shown when hidden
2018-09-24 22:19:07 +02:00
orangemug
8b0ae178b8 Added in thumbnails 2018-09-24 21:17:41 +01:00
orangemug
6b94e9b78b Merge remote-tracking branch 'upstream/master' into feature/os-open-zoomstack
Conflicts:
	src/config/styles.json
2018-09-24 21:14:55 +01:00
orangemug
b171bf3127 Fixes for code review comments. 2018-09-24 21:01:37 +01:00
orangemug
0c6a179cec Switch to using styleUrl for disabling button 2018-09-24 20:58:43 +01:00
Orange Mug
aa50785c12 Merge pull request #399 from orangemug/fix/added-default-prop
Added default isActive prop to Collapse component
2018-09-24 20:44:04 +01:00
orangemug
252403b1e3 Added classBlockModifier to <LayerAction/> and switched to only displaying icons for hidden layers 2018-09-24 20:40:51 +01:00
orangemug
bc1d0de057 Added default isActive prop to Collapse component 2018-09-24 16:55:53 +01:00
orangemug
4a0b9fd0de Added missing prop type 2018-09-24 15:13:35 +01:00
orangemug
e8777e1857 Updated package-lock.json 2018-09-24 15:10:00 +01:00
orangemug
94a2a16330 Updated ol* versions 2018-09-24 14:51:25 +01:00
orangemug
004d135d93 Show error if style fails to load and disabled button if input is empty 2018-09-24 14:41:09 +01:00
orangemug
0973dcee8a Added back in experimental OpenLayers support 2018-09-24 14:05:53 +01:00
Orange Mug
c908f7dcd0 Merge pull request #394 from orangemug/fix/request-abort-and-oerlapping-modals
Fix overlapping modals & request canceling
2018-09-23 22:28:39 +01:00
orangemug
b7fd889fcd Removed signal from fetch as not supported in all browsers. 2018-09-23 21:00:17 +01:00
orangemug
35600c253d Revert react-aria-modal 2018-09-23 20:43:34 +01:00
orangemug
673465af77 Use AbortController in activeRequest 2018-09-23 19:53:32 +01:00
orangemug
cc5d0dc4fe Merge remote-tracking branch 'upstream/master' into fix/request-abort-and-oerlapping-modals 2018-09-23 19:45:47 +01:00
orangemug
e6da977c48 Prevented overlapping modals in react tree and fixed request canceling. 2018-09-23 19:40:50 +01:00
orangemug
e4aa016713 Fixed lint errors 2018-09-23 14:50:29 +01:00
orangemug
8b67499a64 Removed external svg from filter command. 2018-09-23 14:48:19 +01:00
orangemug
bcdc7c6811 Improved color accessibility UI 2018-09-23 14:39:02 +01:00
orangemug
8f07a79a49 Merge remote-tracking branch 'upstream/master' into feature/color-accessibility-ui
Conflicts:
	src/components/App.jsx
	src/styles/_components.scss
2018-09-23 11:39:15 +01:00
orangemug
cdcf16196c Merge remote-tracking branch 'upstream/master' into feature/make-layer-visibility-always-shown
Conflicts:
	src/components/layers/LayerListItem.jsx
2018-09-23 09:07:02 +01:00
orangemug
a0ed6a379b Always show layer visibility toggle in layer list 2018-09-23 09:05:08 +01:00
Orange Mug
7ffb44f604 Merge pull request #391 from orangemug/maintenance/remove-mapbox-for-now
Remove mapbox for the time being
2018-09-22 18:28:00 +01:00
Orange Mug
225d0388ce Merge pull request #386 from orangemug/fix/showTileBoundaries-error
Added guard to map object
2018-09-22 16:15:00 +01:00
Orange Mug
0468db8cc2 Merge pull request #390 from orangemug/fix/switch-visibility-icons
Switch visibility icons
2018-09-22 15:51:10 +01:00
Orange Mug
695f612110 Merge pull request #389 from orangemug/maintenance/remove-travis
Remove travis CI
2018-09-22 15:50:49 +01:00
Orange Mug
9c07852b87 Merge pull request #388 from orangemug/fix/glyph-key-replace
Fix glyph key replace
2018-09-22 15:50:12 +01:00
orangemug
9ef198fb86 Merge remote-tracking branch 'upstream/master' into maintenance/remove-mapbox-for-now 2018-09-22 14:19:44 +01:00
orangemug
fd34e31462 Merge remote-tracking branch 'upstream/master' into fix/showTileBoundaries-error 2018-09-22 14:17:16 +01:00
orangemug
8eb49427fd Merge remote-tracking branch 'upstream/master' into fix/switch-visibility-icons 2018-09-22 14:15:43 +01:00
orangemug
ebafb3c3dd Merge remote-tracking branch 'upstream/master' into maintenance/remove-travis 2018-09-22 14:14:12 +01:00
orangemug
09c6154949 Merge remote-tracking branch 'upstream/master' into fix/glyph-key-replace 2018-09-22 14:12:39 +01:00
Orange Mug
53c8661cd3 Merge pull request #358 from orangemug/fix/remove-componentWillUpdate
Remove UNSAFE_componentWillReceiveProps
2018-09-22 14:11:58 +01:00
orangemug
3d5eec897e Try moving to 'Visual Studio 2017' image 2018-09-22 13:44:41 +01:00
orangemug
3763ec3737 Remove mapbox until we have a valid access token. 2018-09-22 13:18:52 +01:00
orangemug
f1216795d2 Swap layer visibility icons. 2018-09-22 13:12:36 +01:00
orangemug
0ac70df00f Remove travis as we now have CircleCI. 2018-09-22 13:06:56 +01:00
orangemug
7d0a985f1d Only replace glyphs key if a replacement exists. 2018-09-22 13:04:12 +01:00
orangemug
c5ff67b6e0 Appveyor nodejs -v6 +v10 2018-09-22 12:50:40 +01:00
orangemug
db6b9ac176 Added OS Open Zoomstack and styles 2018-09-20 19:26:53 +01:00
orangemug
77475af3c6 Added guard to map object 2018-09-20 19:16:23 +01:00
Orange Mug
805133d10c Merge pull request #383 from pathmapper/master
Access token labels OMT -> MapTiler
2018-09-15 10:03:21 +01:00
pathmapper
fff8fb72c5 Access token labels OMT - MapTiler 2018-09-14 09:08:04 +02:00
Orange Mug
e02b18cea3 Merge pull request #378 from pathmapper/patch-1
fix typo
2018-09-13 08:52:12 +01:00
pathmapper
b8e9307ce2 fix typo 2018-09-11 13:52:39 +02:00
orangemug
00b22eb902 OpenLayers removed in previous PR. 2018-09-10 15:11:06 +01:00
orangemug
be954143c3 Switch to setTimeout(fn, 0) 2018-09-10 15:09:34 +01:00
orangemug
b314642586 Rollback to setImmediate 2018-09-10 14:55:45 +01:00
orangemug
b5fc315b37 setImmediate -> process.nextTick 2018-09-10 14:51:22 +01:00
orangemug
26ff9f63bb Merge remote-tracking branch 'upstream/master' into fix/remove-componentWillUpdate
Conflicts:
	src/components/map/MapboxGlMap.jsx
	src/components/modals/ExportModal.jsx
2018-09-10 14:46:52 +01:00
Orange Mug
7e5fb4d42f Merge pull request #364 from orangemug/maintenance/reduce-bundle-size-v2
Reduce bundle size
2018-09-10 14:12:03 +01:00
Orange Mug
762bb786be Merge pull request #365 from orangemug/feature/added-no-webgl-error
Added no WebGL error message to MapboxGL map component
2018-09-10 14:09:09 +01:00
Orange Mug
cec87765fc Merge pull request #366 from loicgasser/maintenance/transform-class-properties-and-arrow-func
Take advantage of transform-class-properties and use arrow functions
2018-09-10 14:08:46 +01:00
orangemug
b966fae926 1.5.0 2018-09-10 13:18:21 +01:00
orangemug
f1ddf4e57e 1.5.0-beta3 2018-09-03 21:17:50 +01:00
Orange Mug
64e65dc7d3 Merge pull request #377 from orangemug/fix/glyphs-for-tilehosting
Updated regex to tilehosting.com
2018-09-03 21:15:01 +01:00
orangemug
1e07a88aed Updated regex to tilehosting.com, partial revert of #367 2018-09-03 21:02:38 +01:00
orangemug
6e49cc65a9 1.5.0-beta2 2018-09-03 20:37:16 +01:00
Orange Mug
06d579118a Merge pull request #367 from loicgasser/bugfix/default-token
Fix glyph access key for openmaptiles
2018-09-03 20:34:01 +01:00
orangemug
f0e4b5b930 1.5.0-beta 2018-09-01 11:09:19 +01:00
Loïc Gasser
088127a9a5 Fix glyph access key for openmaptiles 2018-08-27 16:18:43 -04:00
Loïc Gasser
a4b4d077fa Fix .babelrc 2018-08-23 00:18:39 -04:00
Loïc Gasser
bc2ec4d0b7 Remove unused imports 2018-08-23 00:18:39 -04:00
Loïc Gasser
e4de101553 Take advantage of transform-class-properties and use arrow functions instead of bind 2018-08-23 00:18:37 -04:00
orangemug
6207416b32 Added no WebGL error message to MapboxGL map component 2018-08-22 22:05:46 +01:00
orangemug
f0202241f4 Remove old base64 loader. 2018-08-22 18:51:58 +01:00
orangemug
0e8c94af1e Remove logging. 2018-08-22 18:51:42 +01:00
orangemug
922ee616ec Reduce bundle size
- Use the browsers fetch rather than the request module
 - base64-loader -> raw-loader
 - Remove ol3 because it's been broken for a while
 - Removed old GitHub gist support as it's no longer functional
 - Removed Mousetrap as we were only using a small part of the functionality
 - Moved to single js file to make things simplier
2018-08-22 09:36:34 +01:00
Orange Mug
98c235bc21 Merge pull request #363 from orangemug/feature/added-show-collision-boxes
Added 'show-collision-boxes' query parameter
2018-08-19 19:48:03 +01:00
orangemug
70f1f9ffac Added 'show-collision-boxes' query parameter 2018-08-19 17:32:18 +01:00
orangemug
409f81f0d8 More componentWillUpdate transition fixes. 2018-08-07 20:13:45 +01:00
orangemug
1aa90bef37 Removed componentWillUpdate 2018-08-06 22:27:03 +01:00
Orange Mug
c5ea9494df Merge pull request #357 from chriswhong/356-zoom-button-fix
find correct zoom attribute in spec
2018-08-04 09:20:31 +01:00
Chris Whong
9a34db7008 find correct zoom attribute in spec 2018-08-03 15:25:05 -04:00
Orange Mug
988b7fca0f Merge pull request #355 from orangemug/fix/failing-tests
Disable survey in test runner
2018-08-01 22:14:36 +01:00
orangemug
bdc6489db4 Disable survey in test runner, which was making the tests fail. 2018-08-01 20:59:42 +01:00
Orange Mug
49b096b601 Merge pull request #352 from orangemug/feature/add-thunderforest-source-v2
Added thunderforest source
2018-08-01 20:48:12 +01:00
Orange Mug
31d83f6a26 Merge pull request #354 from orangemug/maintenance/updated-mapbox-deps
Updated mapbox dependencies
2018-08-01 20:47:39 +01:00
orangemug
03e52b7a72 Added support for 'raster-resampling' 2018-08-01 20:34:26 +01:00
orangemug
551e950c39 Updated mapbox dependencies 2018-08-01 20:33:33 +01:00
orangemug
a7620f83a6 Fixed broken token replacer function. 2018-07-28 16:36:01 +01:00
orangemug
0384181ee1 Added final bits for thunderforest integration 2018-07-27 16:25:53 +01:00
orangemug
fd59f42819 Merge remote-tracking branch 'upstream/master' into feature/add-thunderforest-source
Conflicts:
	src/components/App.jsx
2018-07-27 15:43:02 +01:00
orangemug
cc51774259 1.4.0 2018-07-27 13:19:14 +01:00
Orange Mug
5a19245ee0 Merge pull request #349 from orangemug/fix/react-codemirror-overflow
Fix to prevent contents of react-codemirror being hidden
2018-07-18 19:16:14 +01:00
orangemug
45f45b7547 Fix to prevent contents of react-codemirror being hidden 2018-07-18 08:07:35 +01:00
Orange Mug
530bfaf3b3 Merge pull request #348 from orangemug/fix/color-filter-undefined
Undefined filter fix (color accessibility)
2018-07-17 21:51:54 +01:00
orangemug
6ea70ab9cf Fix what I believe to be a 'first boot' error. 2018-07-17 20:45:12 +01:00
orangemug
a0e2d68dae Only apply filter if defined. 2018-07-17 20:40:23 +01:00
Orange Mug
1447e8bfb5 Merge pull request #345 from orangemug/feature/option-to-download-with-own-tokens
Option to download styles with your own tokens
2018-07-16 08:10:43 +01:00
orangemug
c0480a50ea Option to download styles with own tokens. 2018-07-15 22:51:57 +01:00
Orange Mug
09ba2be416 Merge pull request #344 from orangemug/fix/map-overflow-zoom-issues
Fixed map width so it no longer overflows
2018-07-15 22:44:51 +01:00
Orange Mug
115ce3305d Merge pull request #343 from orangemug/fix/disable-bounce-scroll
Prevent bounce scroll on <body/>
2018-07-15 22:11:18 +01:00
orangemug
960b2022ed Fixed map width (fixes #260) 2018-07-15 22:08:06 +01:00
orangemug
252b442ca9 The UI is 100% height so prevent bounce scroll on OSX 2018-07-15 21:51:25 +01:00
Orange Mug
03b9ddda9c Merge pull request #342 from orangemug/fix/layer-editor-overflow
Fixed <LayerEditor/> overflow issues
2018-07-15 21:49:17 +01:00
orangemug
968d7d7fda Fixed <LayerEditor/> overflow issues. 2018-07-15 13:17:47 +01:00
orangemug
b211f1cd12 1.3.0 2018-07-12 15:54:01 +01:00
Orange Mug
870d4349f4 Merge pull request #341 from orangemug/fix/normalizeSourceURL-import-error
Fixed normalizeSourceURL import issue
2018-07-12 14:23:16 +01:00
orangemug
d88bc59720 Fixed normalizeSourceURL import issue. 2018-07-12 12:33:40 +01:00
orangemug
85a28999fb Initial color accessibility UI 2018-06-18 20:28:24 +01:00
orangemug
45bdf53a41 Added thunderforest maputnik token. 2018-04-11 16:20:23 +01:00
orangemug
00e94212bd Added initial thunderforest source integration 2018-04-11 15:53:40 +01:00
100 changed files with 10352 additions and 9400 deletions

View File

@@ -1,11 +1,17 @@
{
"presets": ["env", "react"],
"plugins": ["transform-object-rest-spread", "transform-class-properties"],
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"react-hot-loader/babel",
"@babel/plugin-proposal-class-properties"
],
"env": {
"test": {
"plugins": [
["istanbul", {
exclude: ["node_modules/**", "test/**"]
"exclude": ["node_modules/**", "test/**"]
}]
]
}

View File

@@ -49,29 +49,21 @@ templates:
path: /tmp/artifacts
destination: /artifacts
jobs:
build-linux-node-v6:
docker:
- image: node:6
working_directory: ~/repo-linux-node-v6
steps: *build-steps
build-linux-node-v8:
docker:
- image: node:8
- image: selenium/standalone-chrome:3.8.1
working_directory: ~/repo-linux-node-v8
steps: *wdio-steps
steps: *build-steps
build-linux-node-v10:
docker:
- image: node:10
- image: selenium/standalone-chrome:3.141.59
working_directory: ~/repo-linux-node-v10
steps: *build-steps
build-osx-node-v6:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@6
working_directory: ~/repo-osx-node-v6
steps: *wdio-steps
build-linux-node-v12:
docker:
- image: node:12
working_directory: ~/repo-linux-node-v12
steps: *build-steps
build-osx-node-v8:
macos:
@@ -89,15 +81,22 @@ jobs:
- brew install node@10
working_directory: ~/repo-osx-node-v10
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:
version: 2
build:
jobs:
- build-linux-node-v6
- build-linux-node-v8
- build-linux-node-v10
- build-osx-node-v6
- build-linux-node-v12
- build-osx-node-v8
- build-osx-node-v10
- build-osx-node-v12

15
.topissuesrc Normal file
View 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
}
}

View File

@@ -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

View File

@@ -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
@@ -9,7 +14,8 @@ COPY . ${HOME}/
WORKDIR ${HOME}
RUN npm install -d --dev
RUN npm install -d
RUN npm run build
CMD npm run start -- --host 0.0.0.0
WORKDIR ${HOME}/build/build
CMD python -m SimpleHTTPServer 8888

View File

@@ -1,26 +1,27 @@
# Maputnik
[![Build Status](https://travis-ci.org/maputnik/editor.svg?branch=master)][travis]
[![Build Status](https://circleci.com/gh/maputnik/editor/tree/master.svg?style=shield)][circleci]
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/anelbgv6jdb3qnh9/branch/master?svg=true)][appveyor]
[![Dependency Status](https://david-dm.org/maputnik/editor.svg)][dm-prod]
[![Dev Dependency Status](https://david-dm.org/maputnik/editor/dev-status.svg)][dm-dev]
[![License](https://img.shields.io/badge/license-MIT-blue.svg)][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
[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
<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/)
targeted at developers and map designers.
- :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
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
@@ -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/).
We ensure building and developing Maputnik works with
- Linux, OSX and Windows
- Node >4
We ensure building and developing Maputnik works with the [current active LTS Node.js version and above](https://github.com/nodejs/Release#release-schedule).
Install the deps, start the dev server and open the web browser on `http://localhost:8888/`.
@@ -54,12 +52,18 @@ npm install
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.
Snippet from <https://webpack.js.org/configuration/dev-server/#devserver-watchoptions->
```bash
# 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
@@ -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.
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
@@ -115,13 +119,13 @@ Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter
- [Terranodo](http://terranodo.io/)
<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 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 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>
<br/>
@@ -133,13 +137,13 @@ Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter
- [Dreipol](https://www.dreipol.ch/)
<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 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 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>
<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.
**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.
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.

View File

@@ -1,17 +1,26 @@
image: Visual Studio 2015
environment:
matrix:
- nodejs_version: "6"
- nodejs_version: "8"
- nodejs_version: "9"
- nodejs_version: "10"
- nodejs_version: "12"
platform:
- x86
- x64
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
- npm install --global --production windows-build-tools
- npm --vs2015 install --global windows-build-tools
- npm install
build_script:
- npm run build
test_script:
- npm run lint
- npm run lint-styles

View File

@@ -10,29 +10,135 @@ var server;
var SCREENSHOT_PATH = artifacts.pathSync("screenshots");
exports.config = {
//
// ====================
// Runner Configuration
// ====================
//
// WebdriverIO allows it to run your tests in arbitrary locations (e.g. locally or
// on a remote machine).
runner: 'local',
//
// ==================
// Specify Test Files
// ==================
// Define which test specs should run. The pattern is relative to the directory
// from which `wdio` was called. Notice that, if you are calling `wdio` from an
// NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
// directory is where your package.json resides, so `wdio` will be called from there.
//
specs: [
'./test/functional/index.js'
],
// Patterns to exclude.
exclude: [
// 'path/to/excluded/files'
],
//
// ============
// Capabilities
// ============
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
// time. Depending on the number of capabilities, WebdriverIO launches several test
// sessions. Within your capabilities you can overwrite the spec and exclude options in
// order to group specific specs to a specific capability.
//
// 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
// files and you set maxInstances to 10, all spec files will get tested at the same time
// and 30 processes will get spawned. The property handles how many capabilities
// from the same test should run tests.
//
maxInstances: 10,
//
// 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
//
capabilities: [{
// 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'
//
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'],
}],
sync: true,
logLevel: 'verbose',
coloredLogs: true,
//
// ===================
// 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...
@@ -59,4 +165,124 @@ exports.config = {
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) {
//}
}

View File

@@ -1,7 +1,7 @@
"use strict";
var webpack = require('webpack');
var path = require('path');
var loaders = require('./webpack.loaders');
var rules = require('./webpack.rules');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
@@ -10,6 +10,7 @@ const PORT = process.env.PORT || "8888";
module.exports = {
target: 'web',
mode: 'development',
entry: [
`webpack-dev-server/client?http://${HOST}:${PORT}`,
`webpack/hot/only-dev-server`,
@@ -27,7 +28,7 @@ module.exports = {
noParse: [
/mapbox-gl\/dist\/mapbox-gl.js/
],
loaders: loaders
rules: rules
},
node: {
fs: "empty",

View File

@@ -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(?![\/\\](@mapbox[\/\\]mapbox-gl-style-spec|ol|mapbox-to-ol-style))|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'
]
}
];

View File

@@ -1,43 +1,22 @@
var webpack = require('webpack');
var path = require('path');
var loaders = require('./webpack.loaders');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var rules = require('./webpack.rules');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var CopyWebpackPlugin = require('copy-webpack-plugin');
var artifacts = require("../test/artifacts");
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
var OUTPATH = artifacts.pathSync("/build");
module.exports = {
entry: {
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: {
path: OUTPATH,
filename: '[name].[chunkhash].js',
chunkFilename: '[chunkhash].js'
filename: '[name].[contenthash].js',
chunkFilename: '[contenthash].js'
},
resolve: {
extensions: ['.js', '.jsx']
@@ -46,7 +25,7 @@ module.exports = {
noParse: [
/mapbox-gl\/dist\/mapbox-gl.js/
],
loaders
rules: rules
},
node: {
fs: "empty",
@@ -55,17 +34,12 @@ module.exports = {
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[chunkhash].vendor.js' }),
new WebpackCleanupPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new UglifyJsPlugin(),
new ExtractTextPlugin('[contenthash].css', {
allChunks: true
}),
new HtmlWebpackPlugin({
template: './src/template.html',
title: 'Maputnik'

38
config/webpack.rules.js Normal file
View 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'
]
}
];

15933
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "maputnik",
"version": "1.3.0-beta",
"version": "1.6.0",
"description": "A MapboxGL visual style editor",
"main": "''",
"scripts": {
@@ -9,9 +9,8 @@
"test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
"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",
"lint": "eslint --ext js --ext jsx {src,test}",
"lint-styles": "stylelint 'src/styles/*.scss'",
"nsp": "nsp check --reporter summary"
"lint": "eslint --ext js --ext jsx src test",
"lint-styles": "stylelint \"src/styles/*.scss\""
},
"repository": {
"type": "git",
@@ -21,44 +20,43 @@
"license": "MIT",
"homepage": "https://github.com/maputnik/editor#readme",
"dependencies": {
"@mapbox/mapbox-gl-rtl-text": "^0.1.2",
"@mapbox/mapbox-gl-style-spec": "^12.0.0",
"classnames": "^2.2.5",
"codemirror": "^5.37.0",
"@babel/runtime": "^7.1.2",
"@mapbox/mapbox-gl-rtl-text": "^0.2.2",
"@mapbox/mapbox-gl-style-spec": "^13.7.2",
"classnames": "^2.2.6",
"codemirror": "^5.40.2",
"color": "^3.0.0",
"detect-browser": "^4.5.0",
"file-saver": "^1.3.8",
"github-api": "^3.0.0",
"jsonlint": "github:josdejong/jsonlint#85a19d7",
"lodash.capitalize": "^4.2.1",
"lodash.clamp": "^4.0.3",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.45.0",
"mapbox-gl": "^1.2.0",
"mapbox-gl-inspect": "^1.3.1",
"maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.1",
"ol-mapbox-style": "^2.10.1",
"ol": "^4.6.5",
"prop-types": "^15.6.0",
"react": "^16.3.2",
"react-addons-pure-render-mixin": "^15.6.2",
"react-aria-menubutton": "^5.1.1",
"react-aria-modal": "^2.12.1",
"react-autocomplete": "^1.7.2",
"react-codemirror2": "^4.2.1",
"ol": "^6.0.0-beta.8",
"ol-mapbox-style": "^5.0.0-beta.2",
"prop-types": "^15.6.2",
"react": "^16.5.2",
"react-aria-menubutton": "^6.0.1",
"react-aria-modal": "^3.0.0",
"react-autobind": "^1.0.6",
"react-autocomplete": "^1.8.1",
"react-codemirror2": "^5.1.0",
"react-collapse": "^4.0.3",
"react-color": "^2.14.1",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.3.2",
"react-file-reader-input": "^1.1.4",
"react-height": "^3.0.0",
"react-icon-base": "^2.1.1",
"react-icons": "^2.2.7",
"react-dom": "^16.5.2",
"react-file-reader-input": "^2.0.0",
"react-icon-base": "^2.1.2",
"react-icons": "^3.1.0",
"react-motion": "^0.5.2",
"react-sortable-hoc": "^0.6.8",
"react-sortable-hoc": "^0.8.3",
"reconnecting-websocket": "^3.2.2",
"request": "^2.85.0",
"slugify": "^1.3.1",
"url": "^0.11.0"
},
"jshintConfig": {
@@ -102,58 +100,51 @@
}
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "7.1.4",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-flow": "^6.23.0",
"babel-preset-react": "^6.24.1",
"babel-register": "^6.26.0",
"babel-runtime": "^6.26.0",
"base64-loader": "^1.0.0",
"copy-webpack-plugin": "^4.5.1",
"@babel/core": "^7.1.2",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-transform-runtime": "^7.1.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@wdio/cli": "^5.10.4",
"@wdio/local-runner": "^5.10.4",
"@wdio/mocha-framework": "^5.10.1",
"@wdio/selenium-standalone-service": "^5.9.3",
"@wdio/spec-reporter": "^5.9.3",
"@wdio/sync": "^5.10.1",
"babel-eslint": "^10.0.1",
"babel-loader": "8.0.4",
"babel-plugin-istanbul": "^5.0.1",
"copy-webpack-plugin": "^4.5.2",
"cors": "^2.8.4",
"cross-env": "^5.1.4",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"eslint-plugin-react": "^7.4.0",
"express": "^4.16.3",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.5",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"eslint": "^5.6.1",
"eslint-plugin-react": "^7.11.1",
"express": "^4.17.1",
"file-loader": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"is-docker": "^1.1.0",
"is-docker": "^2.0.0",
"istanbul": "^0.4.5",
"istanbul-lib-coverage": "^1.2.0",
"json-loader": "^0.5.7",
"istanbul-lib-coverage": "^2.0.1",
"mkdirp": "^0.5.1",
"mocha": "^5.1.1",
"node-sass": "^4.9.0",
"nsp": "^3.1.0",
"react-hot-loader": "^3.1.1",
"sass-loader": "^7.0.1",
"selenium-standalone": "^6.14.0",
"style-loader": "^0.20.3",
"stylelint": "^9.2.0",
"mocha": "^6.1.4",
"node-sass": "^4.12.0",
"raw-loader": "^0.5.1",
"react-hot-loader": "^4.3.11",
"sass-loader": "^7.1.0",
"selenium-standalone": "^6.16.0",
"style-loader": "^0.23.0",
"stylelint": "^10.0.0",
"stylelint-config-recommended-scss": "^3.2.0",
"stylelint-scss": "^3.0.0",
"stylelint-scss": "^3.5.4",
"transform-loader": "^0.2.4",
"uglifyjs-webpack-plugin": "^1.2.4",
"uuid": "^3.1.0",
"wdio-mocha-framework": "^0.5.13",
"wdio-phantomjs-service": "^0.2.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",
"uuid": "^3.3.2",
"webdriverio": "^5.10.4",
"webpack": "^4.20.2",
"webpack-bundle-analyzer": "^3.0.2",
"webpack-cleanup-plugin": "^0.5.1",
"webpack-dev-server": "^2.9.4"
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.9"
}
}

View File

@@ -1,12 +1,13 @@
import autoBind from 'react-autobind';
import React from 'react'
import Mousetrap from 'mousetrap'
import cloneDeep from 'lodash.clonedeep'
import clamp from 'lodash.clamp'
import get from 'lodash.get'
import {arrayMove} from 'react-sortable-hoc'
import url from 'url'
import MapboxGlMap from './map/MapboxGlMap'
import OpenLayers3Map from './map/OpenLayers3Map'
import OpenLayersMap from './map/OpenLayersMap'
import LayerList from './layers/LayerList'
import LayerEditor from './layers/LayerEditor'
import Toolbar from './Toolbar'
@@ -19,13 +20,14 @@ import SourcesModal from './modals/SourcesModal'
import OpenModal from './modals/OpenModal'
import ShortcutsModal from './modals/ShortcutsModal'
import SurveyModal from './modals/SurveyModal'
import DebugModal from './modals/DebugModal'
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import style from '../libs/style.js'
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
import {latest, validate} from '@mapbox/mapbox-gl-style-spec'
import style from '../libs/style'
import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
import { undoMessages, redoMessages } from '../libs/diffmessage'
import { loadDefaultStyle, StyleStore } from '../libs/stylestore'
import { StyleStore } from '../libs/stylestore'
import { ApiStyleStore } from '../libs/apistore'
import { RevisionStore } from '../libs/revisions'
import LayerWatcher from '../libs/layerwatcher'
@@ -35,9 +37,41 @@ import Debug from '../libs/debug'
import queryUtil from '../libs/query-util'
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) {
return {
...spec,
@@ -54,89 +88,100 @@ function updateRootSpec(spec, fieldName, newValues) {
export default class App extends React.Component {
constructor(props) {
super(props)
autoBind(this);
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({
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 = [
{
keyCode: keyCodes["?"],
key: "?",
handler: () => {
this.toggleModal("shortcuts");
}
},
{
keyCode: keyCodes["o"],
key: "o",
handler: () => {
this.toggleModal("open");
}
},
{
keyCode: keyCodes["e"],
key: "e",
handler: () => {
this.toggleModal("export");
}
},
{
keyCode: keyCodes["d"],
key: "d",
handler: () => {
this.toggleModal("sources");
}
},
{
keyCode: keyCodes["s"],
key: "s",
handler: () => {
this.toggleModal("settings");
}
},
{
keyCode: keyCodes["i"],
key: "i",
handler: () => {
this.changeInspectMode();
this.setMapState(
this.state.mapState === "map" ? "inspect" : "map"
);
}
},
{
keyCode: keyCodes["m"],
key: "m",
handler: () => {
document.querySelector(".mapboxgl-canvas").focus();
}
},
{
key: "!",
handler: () => {
this.toggleModal("debug");
}
},
]
document.body.addEventListener("keyup", (e) => {
if(e.keyCode === keyCodes["esc"]) {
if(e.key === "Escape") {
e.target.blur();
document.body.focus();
}
else if(document.activeElement === document.body) {
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
const shortcut = shortcuts.find((shortcut) => {
return (shortcut.keyCode === e.keyCode)
return (shortcut.key === e.key)
})
if(shortcut) {
this.setModal("shortcuts", false);
shortcut.handler(e);
}
}
})
const styleUrl = initialStyleUrl()
if(styleUrl) {
if(styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?")) {
this.styleStore = new StyleStore()
loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle))
removeStyleQuerystring()
} else {
if(styleUrl) {
removeStyleQuerystring()
}
this.styleStore.init(err => {
if(err) {
console.log('Falling back to local storage for storing styles')
@@ -165,20 +210,25 @@ export default class App extends React.Component {
selectedLayerIndex: 0,
sources: {},
vectorLayers: {},
inspectModeEnabled: false,
spec: styleSpec.latest,
mapState: "map",
spec: latest,
isOpen: {
settings: false,
sources: false,
open: false,
shortcuts: false,
export: false,
survey: localStorage.hasOwnProperty('survey') ? false : true
survey: localStorage.hasOwnProperty('survey') ? false : true,
debug: false,
},
mapOptions: {
showTileBoundaries: queryUtil.asBool(queryObj, "show-tile-boundaries")
mapboxGlDebugOptions: {
showTileBoundaries: false,
showCollisionBoxes: false,
showOverdrawInspector: false,
},
openlayersDebugOptions: {
debugToolbox: false,
},
mapFilter: queryObj["color-blindness-emulation"],
}
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() {
Mousetrap.bind(['mod+z'], this.onUndo.bind(this));
Mousetrap.bind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
window.addEventListener("keydown", this.handleKeyPress);
}
componentWillUnmount() {
Mousetrap.unbind(['mod+z'], this.onUndo.bind(this));
Mousetrap.unbind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
window.removeEventListener("keydown", this.handleKeyPress);
}
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(newStyle.glyphs !== this.state.mapStyle.glyphs) {
@@ -243,7 +335,7 @@ export default class App extends React.Component {
this.fetchSources();
}
onUndo() {
onUndo = () => {
const activeStyle = this.revisionStore.undo()
const messages = undoMessages(this.state.mapStyle, activeStyle)
this.saveStyle(activeStyle)
@@ -253,7 +345,7 @@ export default class App extends React.Component {
})
}
onRedo() {
onRedo = () => {
const activeStyle = this.revisionStore.redo()
const messages = redoMessages(this.state.mapStyle, activeStyle)
this.saveStyle(activeStyle)
@@ -263,7 +355,7 @@ export default class App extends React.Component {
})
}
onMoveLayer(move) {
onMoveLayer = (move) => {
let { oldIndex, newIndex } = move;
let layers = this.state.mapStyle.layers;
oldIndex = clamp(oldIndex, 0, layers.length-1);
@@ -281,7 +373,7 @@ export default class App extends React.Component {
this.onLayersChange(layers);
}
onLayersChange(changedLayers) {
onLayersChange = (changedLayers) => {
const changedStyle = {
...this.state.mapStyle,
layers: changedLayers
@@ -289,7 +381,7 @@ export default class App extends React.Component {
this.onStyleChanged(changedStyle)
}
onLayerDestroy(layerId) {
onLayerDestroy = (layerId) => {
let layers = this.state.mapStyle.layers;
const remainingLayers = layers.slice(0);
const idx = style.indexOfLayer(remainingLayers, layerId)
@@ -297,7 +389,7 @@ export default class App extends React.Component {
this.onLayersChange(remainingLayers);
}
onLayerCopy(layerId) {
onLayerCopy = (layerId) => {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
@@ -308,7 +400,7 @@ export default class App extends React.Component {
this.onLayersChange(changedLayers)
}
onLayerVisibilityToggle(layerId) {
onLayerVisibilityToggle = (layerId) => {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
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 idx = style.indexOfLayer(changedLayers, oldId)
@@ -335,7 +427,7 @@ export default class App extends React.Component {
this.onLayersChange(changedLayers)
}
onLayerChanged(layer) {
onLayerChanged = (layer) => {
const changedLayers = this.state.mapStyle.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layer.id)
changedLayers[idx] = layer
@@ -343,12 +435,33 @@ export default class App extends React.Component {
this.onLayersChange(changedLayers)
}
changeInspectMode() {
setMapState = (newState) => {
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() {
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")) {
let url = val.url;
try {
url = mapboxUtil.normalizeSourceURL(url, MapboxGl.accessToken);
url = normalizeSourceURL(url, MapboxGl.accessToken);
} catch(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) => {
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() {
const metadata = this.state.mapStyle.metadata || {};
const mapProps = {
mapStyle: style.replaceAccessToken(this.state.mapStyle, {allowFallback: true}),
options: this.state.mapOptions,
mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}),
onDataChange: (e) => {
this.layerWatcher.analyzeMap(e.map)
this.fetchSources();
},
}
const metadata = this.state.mapStyle.metadata || {}
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
const renderer = this._getRenderer();
let mapElement;
// Check if OL3 code has been loaded?
if(renderer === 'ol3') {
mapElement = <OpenLayers3Map {...mapProps} />
// Check if OL code has been loaded?
if(renderer === 'ol') {
mapElement = <OpenLayersMap
{...mapProps}
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
onLayerSelect={this.onLayerSelect}
/>
} else {
mapElement = <MapboxGlMap {...mapProps}
inspectModeEnabled={this.state.inspectModeEnabled}
options={this.state.mapboxGlDebugOptions}
inspectModeEnabled={this.state.mapState === "inspect"}
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
onLayerSelect={this.onLayerSelect.bind(this)} />
onLayerSelect={this.onLayerSelect} />
}
const elementStyle = {
"filter": `url('#${this.state.mapFilter}')`
let filterName;
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}
</div>
}
onLayerSelect(layerId) {
onLayerSelect = (layerId) => {
const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
this.setState({ selectedLayerIndex: idx })
}
toggleModal(modalName) {
setModal(modalName, value) {
if(modalName === 'survey' && value === false) {
localStorage.setItem('survey', '');
}
this.setState({
isOpen: {
...this.state.isOpen,
[modalName]: !this.state.isOpen[modalName]
[modalName]: value
}
})
if(modalName === 'survey') {
localStorage.setItem('survey', '');
}
toggleModal(modalName) {
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() {
@@ -463,22 +621,24 @@ export default class App extends React.Component {
const metadata = this.state.mapStyle.metadata || {}
const toolbar = <Toolbar
renderer={this._getRenderer()}
mapState={this.state.mapState}
mapStyle={this.state.mapStyle}
inspectModeEnabled={this.state.inspectModeEnabled}
inspectModeEnabled={this.state.mapState === "inspect"}
sources={this.state.sources}
onStyleChanged={this.onStyleChanged.bind(this)}
onStyleOpen={this.onStyleChanged.bind(this)}
onInspectModeToggle={this.changeInspectMode.bind(this)}
onStyleChanged={this.onStyleChanged}
onStyleOpen={this.onStyleChanged}
onSetMapState={this.setMapState}
onToggleModal={this.toggleModal.bind(this)}
/>
const layerList = <LayerList
onMoveLayer={this.onMoveLayer.bind(this)}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayersChange={this.onLayersChange.bind(this)}
onLayerSelect={this.onLayerSelect.bind(this)}
onMoveLayer={this.onMoveLayer}
onLayerDestroy={this.onLayerDestroy}
onLayerCopy={this.onLayerCopy}
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
onLayersChange={this.onLayersChange}
onLayerSelect={this.onLayerSelect}
selectedLayerIndex={this.state.selectedLayerIndex}
layers={layers}
sources={this.state.sources}
@@ -492,12 +652,12 @@ export default class App extends React.Component {
sources={this.state.sources}
vectorLayers={this.state.vectorLayers}
spec={this.state.spec}
onMoveLayer={this.onMoveLayer.bind(this)}
onLayerChanged={this.onLayerChanged.bind(this)}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayerIdChange={this.onLayerIdChange.bind(this)}
onMoveLayer={this.onMoveLayer}
onLayerChanged={this.onLayerChanged}
onLayerDestroy={this.onLayerDestroy}
onLayerCopy={this.onLayerCopy}
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
onLayerIdChange={this.onLayerIdChange}
/> : null
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>
<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
ref={(el) => this.shortcutEl = el}
isOpen={this.state.isOpen.shortcuts}
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
/>
<SettingsModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged.bind(this)}
onStyleChanged={this.onStyleChanged}
onChangeMetadataProperty={this.onChangeMetadataProperty}
isOpen={this.state.isOpen.settings}
onOpenToggle={this.toggleModal.bind(this, 'settings')}
openlayersDebugOptions={this.state.openlayersDebugOptions}
/>
<ExportModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged.bind(this)}
onStyleChanged={this.onStyleChanged}
isOpen={this.state.isOpen.export}
onOpenToggle={this.toggleModal.bind(this, 'export')}
/>
<OpenModal
isOpen={this.state.isOpen.open}
onStyleOpen={this.onStyleChanged.bind(this)}
onStyleOpen={this.openStyle}
onOpenToggle={this.toggleModal.bind(this, 'open')}
/>
<SourcesModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged.bind(this)}
onStyleChanged={this.onStyleChanged}
isOpen={this.state.isOpen.sources}
onOpenToggle={this.toggleModal.bind(this, 'sources')}
/>

View File

@@ -9,12 +9,14 @@ class Button extends React.Component {
onClick: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string,
children: PropTypes.node
children: PropTypes.node,
disabled: PropTypes.bool,
}
render() {
return <button
onClick={this.props.onClick}
disabled={this.props.disabled}
aria-label={this.props["aria-label"]}
className={classnames("maputnik-button", this.props.className)}
data-wd-key={this.props["data-wd-key"]}

View File

@@ -1,27 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import FileReaderInput from 'react-file-reader-input'
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 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 {
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 {
static propTypes = {
children: PropTypes.node,
@@ -102,14 +110,14 @@ export default class Toolbar extends React.Component {
onStyleOpen: PropTypes.func.isRequired,
// A dict of source id's and the available source layers
sources: PropTypes.object.isRequired,
onInspectModeToggle: PropTypes.func.isRequired,
children: PropTypes.node,
onToggleModal: PropTypes.func,
onSetMapState: PropTypes.func,
mapState: PropTypes.string,
renderer: PropTypes.string,
}
constructor(props) {
super(props)
this.state = {
state = {
isOpen: {
settings: false,
sources: false,
@@ -118,9 +126,48 @@ export default class Toolbar extends React.Component {
export: false,
}
}
handleSelection(val) {
this.props.onSetMapState(val);
}
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'>
<div className="maputnik-toolbar__inner">
<div
@@ -136,14 +183,15 @@ export default class Toolbar extends React.Component {
className="maputnik-toolbar-logo"
>
<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>
</h1>
</a>
</div>
<div className="maputnik-toolbar__actions">
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
<OpenIcon />
<MdOpenInBrowser />
<IconText>Open</IconText>
</ToolbarAction>
<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>
</ToolbarAction>
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
<SourcesIcon />
<MdLayers />
<IconText>Data Sources</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
<SettingsIcon />
<MdSettings />
<IconText>Style Settings</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:inspect" onClick={this.props.onInspectModeToggle}>
<InspectionIcon />
<IconText>
{ this.props.inspectModeEnabled && <span>Map Mode</span> }
{ !this.props.inspectModeEnabled && <span>Inspect Mode</span> }
</IconText>
</ToolbarAction>
<ToolbarSelect wdKey="nav:inspect">
<MdFindInPage />
<IconText>View </IconText>
<select onChange={(e) => this.handleSelection(e.target.value)} value={currentView.id}>
{views.map((item) => {
return (
<option key={item.id} value={item.id} disabled={item.disabled}>
{item.title}
</option>
);
})}
</select>
</ToolbarSelect>
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
<HelpIcon />
<MdHelpOutline />
<IconText>Help</IconText>
</ToolbarLink>
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
<SurveyIcon />
<MdAssignmentTurnedIn />
<IconText>Take the Maputnik Survey</IconText>
</ToolbarLinkHighlighted>
</div>

View File

@@ -19,17 +19,14 @@ class ColorField extends React.Component {
default: PropTypes.string,
}
constructor(props) {
super(props)
this.state = {
pickerOpened: false,
}
state = {
pickerOpened: false
}
//TODO: I much rather would do this with absolute positioning
//but I am too stupid to get it to work together with fixed position
//and scrollbars so I have to fallback to JavaScript
calcPickerOffset() {
calcPickerOffset = () => {
const elem = this.colorInput
if(elem) {
const pos = elem.getBoundingClientRect()
@@ -45,7 +42,7 @@ class ColorField extends React.Component {
}
}
togglePicker() {
togglePicker = () => {
this.setState({ pickerOpened: !this.state.pickerOpened })
}
@@ -60,6 +57,10 @@ class ColorField extends React.Component {
}
}
onChange (v) {
this.props.onChange(v === "" ? undefined : v);
}
render() {
const offset = this.calcPickerOffset()
var currentColor = this.color.object()
@@ -85,7 +86,7 @@ class ColorField extends React.Component {
/>
<div
className="maputnik-color-picker-offset"
onClick={this.togglePicker.bind(this)}
onClick={this.togglePicker}
style={{
zIndex: -1,
position: 'fixed',
@@ -108,12 +109,12 @@ class ColorField extends React.Component {
spellCheck="false"
className="maputnik-color"
ref={(input) => this.colorInput = input}
onClick={this.togglePicker.bind(this)}
onClick={this.togglePicker}
style={this.props.style}
name={this.props.name}
placeholder={this.props.default}
value={this.props.value ? this.props.value : ""}
onChange={(e) => this.props.onChange(e.target.value)}
onChange={(e) => this.onChange(e.target.value)}
/>
</div>
}

View File

@@ -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 lastStop = stops[stops.length - 1]
if (typeof lastStop[0] === "object") {
@@ -53,7 +63,7 @@ export default class FunctionSpecProperty extends React.Component {
this.props.onChange(this.props.fieldName, changedValue)
}
deleteStop(stopIdx) {
deleteStop = (stopIdx) => {
const stops = this.props.value.stops.slice(0)
stops.splice(stopIdx, 1)
@@ -69,7 +79,7 @@ export default class FunctionSpecProperty extends React.Component {
this.props.onChange(this.props.fieldName, changedValue)
}
makeZoomFunction() {
makeZoomFunction = () => {
const zoomFunc = {
stops: [
[6, this.props.value],
@@ -79,13 +89,15 @@ export default class FunctionSpecProperty extends React.Component {
this.props.onChange(this.props.fieldName, zoomFunc)
}
makeDataFunction() {
makeDataFunction = () => {
const functionType = this.getFieldFunctionType(this.props.fieldSpec);
const stopValue = functionType === 'categorical' ? '' : 0;
const dataFunc = {
property: "",
type: "categorical",
type: functionType,
stops: [
[{zoom: 6, value: 0}, this.props.value],
[{zoom: 10, value: 0}, this.props.value]
[{zoom: 6, value: stopValue}, this.props.value || stopValue],
[{zoom: 10, value: stopValue}, this.props.value || stopValue]
]
}
this.props.onChange(this.props.fieldName, dataFunc)
@@ -102,8 +114,8 @@ export default class FunctionSpecProperty extends React.Component {
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onDeleteStop={this.deleteStop.bind(this)}
onAddStop={this.addStop.bind(this)}
onDeleteStop={this.deleteStop}
onAddStop={this.addStop}
/>
)
}
@@ -114,8 +126,8 @@ export default class FunctionSpecProperty extends React.Component {
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onDeleteStop={this.deleteStop.bind(this)}
onAddStop={this.addStop.bind(this)}
onDeleteStop={this.deleteStop}
onAddStop={this.addStop}
/>
)
}
@@ -126,8 +138,8 @@ export default class FunctionSpecProperty extends React.Component {
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onZoomClick={this.makeZoomFunction.bind(this)}
onDataClick={this.makeDataFunction.bind(this)}
onZoomClick={this.makeZoomFunction}
onDataClick={this.makeDataFunction}
/>
)
}

View File

@@ -42,7 +42,7 @@ export default class PropertyGroup extends React.Component {
spec: PropTypes.object.isRequired,
}
onPropertyChange(property, newValue) {
onPropertyChange = (property, newValue) => {
const group = getGroupName(this.props.spec, this.props.layer.type, property)
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]
return <FunctionSpecField
onChange={this.onPropertyChange.bind(this)}
onChange={this.onPropertyChange}
key={fieldName}
fieldName={fieldName}
value={fieldValue === undefined ? fieldSpec.default : fieldValue}

View File

@@ -1,6 +1,5 @@
import React from 'react'
import PropTypes from 'prop-types'
import color from 'color'
import ColorField from './ColorField'
import NumberInput from '../inputs/NumberInput'
@@ -82,6 +81,7 @@ export default class SpecField extends React.Component {
options={options}
/>
}
case 'formatted':
case 'string':
if(iconProperties.indexOf(this.props.fieldName) >= 0) {
return <IconInput

View File

@@ -30,7 +30,7 @@ export default class DataProperty extends React.Component {
}
getFieldFunctionType(fieldSpec) {
if (fieldSpec.function === "interpolated") {
if (fieldSpec.expression.interpolated) {
return "exponential"
}
if (fieldSpec.type === "number") {
@@ -39,8 +39,8 @@ export default class DataProperty extends React.Component {
return "categorical"
}
getDataFunctionTypes(functionType) {
if (functionType === "interpolated") {
getDataFunctionTypes(fieldSpec) {
if (fieldSpec.expression.interpolated) {
return ["categorical", "interval", "exponential"]
}
else {
@@ -150,7 +150,7 @@ export default class DataProperty extends React.Component {
<SelectInput
value={this.props.value.type}
onChange={propVal => this.changeDataProperty("type", propVal)}
options={this.getDataFunctionTypes(this.props.fieldSpec.function)}
options={this.getDataFunctionTypes(this.props.fieldSpec)}
/>
</div>
</div>

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import DocLabel from './DocLabel'
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 {
@@ -17,7 +17,7 @@ export default class DeleteStopButton extends React.Component {
onClick={this.props.onClick}
>
<DocLabel
label={<DeleteIcon />}
label={<MdDelete />}
doc={"Remove zoom level stop."}
/>
</Button>

View File

@@ -3,8 +3,7 @@ import PropTypes from 'prop-types'
import DocLabel from './DocLabel'
import Button from '../Button'
import FunctionIcon from 'react-icons/lib/md/functions'
import MdInsertChart from 'react-icons/lib/md/insert-chart'
import {MdFunctions, MdInsertChart} from 'react-icons/md'
export default class FunctionButtons extends React.Component {
@@ -16,19 +15,19 @@ export default class FunctionButtons extends React.Component {
render() {
let makeZoomButton, makeDataButton
if (this.props.fieldSpec['zoom-function']) {
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
makeZoomButton = <Button
className="maputnik-make-zoom-function"
onClick={this.props.onZoomClick}
>
<DocLabel
label={<FunctionIcon />}
label={<MdFunctions />}
cursorTargetStyle={{ cursor: 'pointer' }}
doc={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
/>
</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
className="maputnik-make-data-function"
onClick={this.props.onDataClick}

View File

@@ -13,6 +13,30 @@ import docUid from '../../libs/document-uid'
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 {
static propTypes = {
onChange: PropTypes.func,
@@ -29,45 +53,13 @@ export default class ZoomProperty extends React.Component {
]),
}
constructor() {
super()
this.state = {
state = {
refs: {}
}
}
componentDidMount() {
this.setState({
refs: this.setStopRefs(this.props)
})
}
const newRefs = setStopRefs(this.props, this.state);
/**
* 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) {
this.setState({
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.
orderStopsByZoom(stops) {
const mappedWithRef = stops

View File

@@ -2,16 +2,13 @@ import React from 'react'
import PropTypes from 'prop-types'
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 SelectInput from '../inputs/SelectInput'
import SingleFilterEditor from './SingleFilterEditor'
import FilterEditorBlock from './FilterEditorBlock'
import Button from '../Button'
import DeleteIcon from 'react-icons/lib/md/delete'
import AddIcon from 'react-icons/lib/fa/plus'
function hasCombiningFilter(filter) {
return combiningFilterOps.indexOf(filter[0]) >= 0
}
@@ -60,7 +57,7 @@ export default class CombiningFilterEditor extends React.Component {
this.props.onChange(newFilter)
}
addFilterItem() {
addFilterItem = () => {
const newFilterItem = this.combiningFilter().slice(0)
newFilterItem.push(['==', 'name', ''])
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">
<DocLabel
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
value={combiningOp}
@@ -105,7 +102,7 @@ export default class CombiningFilterEditor extends React.Component {
<Button
data-wd-key="layer-filter-button"
className="maputnik-add-filter"
onClick={this.addFilterItem.bind(this)}>
onClick={this.addFilterItem}>
Add filter
</Button>
</div>

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import DeleteIcon from 'react-icons/lib/md/delete'
import {MdDelete} from 'react-icons/md'
class FilterEditorBlock extends React.Component {
static propTypes = {
@@ -16,7 +16,7 @@ class FilterEditorBlock extends React.Component {
className="maputnik-delete-filter"
onClick={this.props.onDelete}
>
<DeleteIcon />
<MdDelete />
</Button>
</div>
<div className="maputnik-filter-editor-block-content">

View File

@@ -14,18 +14,15 @@ class AutocompleteInput extends React.Component {
keepMenuWithinWindowBounds: PropTypes.bool
}
state = {
maxHeight: MAX_HEIGHT
}
static defaultProps = {
onChange: () => {},
options: [],
}
constructor(props) {
super(props);
this.state = {
maxHeight: MAX_HEIGHT
};
}
calcMaxHeight() {
if(this.props.keepMenuWithinWindowBounds) {
const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
@@ -38,6 +35,7 @@ class AutocompleteInput extends React.Component {
}
}
}
componentDidMount() {
this.calcMaxHeight();
}
@@ -46,6 +44,10 @@ class AutocompleteInput extends React.Component {
this.calcMaxHeight();
}
onChange (v) {
this.props.onChange(v === "" ? undefined : v);
}
render() {
return <div
ref={(el) => {
@@ -56,7 +58,8 @@ class AutocompleteInput extends React.Component {
menuStyle={{
position: "fixed",
overflow: "auto",
maxHeight: this.state.maxHeight
maxHeight: this.state.maxHeight,
zIndex: '998'
}}
wrapperProps={{
className: "maputnik-autocomplete",
@@ -69,10 +72,12 @@ class AutocompleteInput extends React.Component {
value={this.props.value}
items={this.props.options}
getItemValue={(item) => item[0]}
onSelect={v => this.props.onChange(v)}
onChange={(e, v) => this.props.onChange(v)}
shouldItemRender={(item, value) => {
onSelect={v => this.onChange(v)}
onChange={(e, v) => this.onChange(v)}
shouldItemRender={(item, value="") => {
if (typeof(value) === "string") {
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
}
}}
renderItem={(item, isHighlighted) => (
<div

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import StringInput from './StringInput'
import NumberInput from './NumberInput'
import Button from '../Button'
import DeleteIcon from 'react-icons/lib/md/delete'
import {MdDelete} from 'react-icons/md'
import DocLabel from '../fields/DocLabel'
@@ -27,7 +27,7 @@ class DynamicArrayInput extends React.Component {
return this.props.value || this.props.default || []
}
addValue() {
addValue = () => {
const values = this.values.slice(0)
if (this.props.type === 'number') {
values.push(0)
@@ -35,7 +35,6 @@ class DynamicArrayInput extends React.Component {
values.push("")
}
this.props.onChange(values)
}
@@ -77,7 +76,7 @@ class DynamicArrayInput extends React.Component {
{inputs}
<Button
className="maputnik-array-add-value"
onClick={this.addValue.bind(this)}
onClick={this.addValue}
>
Add value
</Button>
@@ -96,7 +95,7 @@ class DeleteValueButton extends React.Component {
onClick={this.props.onClick}
>
<DocLabel
label={<DeleteIcon />}
label={<MdDelete />}
doc={"Remove array entry."}
/>
</Button>

View File

@@ -16,13 +16,25 @@ class FontInput extends React.Component {
}
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) {
const changedValues = this.values.slice(0)
changedValues[idx] = newValue
this.props.onChange(changedValues)
const filteredValues = changedValues
.filter(v => v !== undefined)
.filter(v => v !== "")
this.props.onChange(filteredValues);
}
render() {

View File

@@ -5,7 +5,7 @@ import AutocompleteInput from './AutocompleteInput'
class IconInput extends React.Component {
static propTypes = {
value: PropTypes.array,
value: PropTypes.string,
icons: PropTypes.array,
style: PropTypes.object,
onChange: PropTypes.func.isRequired,

View File

@@ -20,7 +20,7 @@ class InputBlock extends React.Component {
onChange(e) {
const value = e.target.value
return this.props.onChange(value === "" ? null: value)
return this.props.onChange(value === "" ? undefined : value)
}
render() {

View File

@@ -13,23 +13,28 @@ class NumberInput extends React.Component {
constructor(props) {
super(props)
this.state = {
value: props.value
editing: false,
value: props.value,
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.value })
static getDerivedStateFromProps(props, state) {
if (!state.editing) {
return {
value: props.value
};
}
}
changeValue(newValue) {
this.setState({editing: true});
const value = parseFloat(newValue)
const hasChanged = this.state.value !== value
if(this.isValid(value) && hasChanged) {
this.props.onChange(value)
} else {
this.setState({ value: newValue })
}
this.setState({ value: newValue })
}
isValid(v) {
@@ -49,7 +54,8 @@ class NumberInput extends React.Component {
return true
}
resetValue() {
resetValue = () => {
this.setState({editing: false});
// Reset explicitly to default value if value has been cleared
if(this.state.value === "") {
return this.changeValue(this.props.default)
@@ -72,7 +78,7 @@ class NumberInput extends React.Component {
placeholder={this.props.default}
value={this.state.value}
onChange={e => this.changeValue(e.target.value)}
onBlur={this.resetValue.bind(this)}
onBlur={this.resetValue}
/>
}
}

View File

@@ -14,12 +14,17 @@ class StringInput extends React.Component {
constructor(props) {
super(props)
this.state = {
editing: false,
value: props.value || ''
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.value || '' })
static getDerivedStateFromProps(props, state) {
if (!state.editing) {
return {
value: props.value
};
}
}
render() {
@@ -49,11 +54,15 @@ class StringInput extends React.Component {
placeholder: this.props.default,
onChange: e => {
this.setState({
editing: true,
value: e.target.value
})
},
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);
}
}
});
}

View File

@@ -10,6 +10,10 @@ export default class CollapseAlt extends React.Component {
children: PropTypes.element.isRequired
}
static defaultProps = {
isActive: true
}
render() {
if (accessibility.reducedMotionEnabled()) {
return (

View File

@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import CollapseOpenIcon from 'react-icons/lib/md/arrow-drop-down'
import CollapseCloseIcon from 'react-icons/lib/md/arrow-drop-up'
import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md'
export default class Collapser extends React.Component {
static propTypes = {
@@ -15,7 +14,7 @@ export default class Collapser extends React.Component {
height: 20,
...this.props.style,
}
return this.props.isCollapsed ? <CollapseCloseIcon style={iconStyle}/> : <CollapseOpenIcon style={iconStyle} />
return this.props.isCollapsed ? <MdArrowDropUp style={iconStyle}/> : <MdArrowDropDown style={iconStyle} />
}
}

View File

@@ -29,21 +29,12 @@ class JSONEditor extends React.Component {
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
componentDidUpdate(prevProps) {
if (prevProps.layer !== this.props.layer) {
this.setState({
code: JSON.stringify(nextProps.layer, null, 2)
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
}
}
onCodeUpdate(newCode) {

View File

@@ -14,10 +14,7 @@ import CommentBlock from './CommentBlock'
import LayerSourceBlock from './LayerSourceBlock'
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
import MoreVertIcon from 'react-icons/lib/md/more-vert'
import InputBlock from '../inputs/InputBlock'
import MultiButtonInput from '../inputs/MultiButtonInput'
import {MdMoreVert} from 'react-icons/md'
import { changeType, changeProperty } from '../../libs/layer'
import layout from '../../config/layout.json'
@@ -79,18 +76,18 @@ export default class LayerEditor extends React.Component {
this.state = { editorGroups }
}
UNSAFE_componentWillReceiveProps(nextProps) {
const additionalGroups = { ...this.state.editorGroups }
static getDerivedStateFromProps(props, state) {
const additionalGroups = { ...state.editorGroups }
layout[nextProps.layer.type].groups.forEach(group => {
layout[props.layer.type].groups.forEach(group => {
if(!(group.title in additionalGroups)) {
additionalGroups[group.title] = true
}
})
this.setState({
return {
editorGroups: additionalGroups
})
};
}
getChildContext () {
@@ -257,7 +254,7 @@ export default class LayerEditor extends React.Component {
closeOnSelection={false}
>
<Button className='more-menu__button'>
<MoreVertIcon className="more-menu__button__svg" />
<MdMoreVert className="more-menu__button__svg" />
</Button>
<Menu>
<ul className="more-menu__menu">

View File

@@ -1,7 +1,7 @@
import React from 'react'
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 StringInput from '../inputs/StringInput'
@@ -13,7 +13,7 @@ class LayerIdBlock extends React.Component {
}
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}
>
<StringInput

View File

@@ -2,14 +2,11 @@ import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Button from '../Button'
import LayerListGroup from './LayerListGroup'
import LayerListItem from './LayerListItem'
import AddIcon from 'react-icons/lib/md/add-circle-outline'
import AddModal from '../modals/AddModal'
import style from '../../libs/style.js'
import {SortableContainer, SortableHandle} from 'react-sortable-hoc';
import {SortableContainer} from 'react-sortable-hoc';
const layerListPropTypes = {
layers: PropTypes.array.isRequired,
@@ -38,23 +35,19 @@ function findClosestCommonPrefix(layers, idx) {
}
// List of collapsible layer editors
@SortableContainer
class LayerListContainer extends React.Component {
static propTypes = {...layerListPropTypes}
static defaultProps = {
onLayerSelect: () => {},
}
constructor(props) {
super(props)
this.state = {
state = {
collapsedGroups: {},
areAllGroupsExpanded: false,
isOpen: {
add: false,
}
}
}
toggleModal(modalName) {
this.setState({
@@ -65,7 +58,7 @@ class LayerListContainer extends React.Component {
})
}
toggleLayers() {
toggleLayers = () => {
let idx=0
let newGroups=[]
@@ -179,7 +172,7 @@ class LayerListContainer extends React.Component {
<div className="maputnik-multibutton">
<button
id="skip-menu"
onClick={this.toggleLayers.bind(this)}
onClick={this.toggleLayers}
className="maputnik-button">
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
</button>
@@ -203,12 +196,15 @@ class LayerListContainer extends React.Component {
}
}
const LayerListContainerSortable = SortableContainer((props) => <LayerListContainer {...props} />)
export default class LayerList extends React.Component {
static propTypes = {...layerListPropTypes}
render() {
return <LayerListContainer
return <LayerListContainerSortable
{...this.props}
helperClass='sortableHelper'
onSortEnd={this.props.onMoveLayer.bind(this)}
useDragHandle={true}
/>

View File

@@ -1,55 +1,57 @@
import React from 'react'
import PropTypes from 'prop-types'
import Color from 'color'
import classnames from 'classnames'
import CopyIcon from 'react-icons/lib/md/content-copy'
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 {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from 'react-icons/md'
import LayerIcon from '../icons/LayerIcon'
import LayerEditor from './LayerEditor'
import {SortableElement, SortableHandle} from 'react-sortable-hoc'
@SortableHandle
class LayerTypeDragHandle extends React.Component {
static propTypes = LayerIcon.propTypes
render() {
return <LayerIcon
{...this.props}
style={{
cursor: 'move',
width: 14,
height: 14,
paddingRight: 3,
}}
const DraggableLabel = SortableHandle((props) => {
return <div className="maputnik-layer-list-item-handle">
<LayerIcon
className="layer-handle__icon"
type={props.layerType}
/>
}
}
<span className="maputnik-layer-list-item-id">{props.layerId}</span>
</div>
});
class IconAction extends React.Component {
static propTypes = {
action: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
wdKey: PropTypes.string
wdKey: PropTypes.string,
classBlockName: PropTypes.string,
classBlockModifier: PropTypes.string,
}
renderIcon() {
switch(this.props.action) {
case 'duplicate': return <CopyIcon />
case 'show': return <VisibilityIcon />
case 'hide': return <VisibilityOffIcon />
case 'delete': return <DeleteIcon />
case 'duplicate': return <MdContentCopy />
case 'show': return <MdVisibility />
case 'hide': return <MdVisibilityOff />
case 'delete': return <MdDelete />
}
}
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
tabIndex="-1"
title={this.props.action}
className="maputnik-layer-list-icon-action"
className={`maputnik-layer-list-icon-action ${classAdditions}`}
data-wd-key={this.props.wdKey}
onClick={this.props.onClick}
>
@@ -58,7 +60,6 @@ class IconAction extends React.Component {
}
}
@SortableElement
class LayerListItem extends React.Component {
static propTypes = {
layerId: PropTypes.string.isRequired,
@@ -92,6 +93,8 @@ class LayerListItem extends React.Component {
}
render() {
const visibilityAction = this.props.visibility === 'visible' ? 'show' : 'hide';
return <li
key={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,
[this.props.className]: true,
})}>
<LayerTypeDragHandle type={this.props.layerType} />
<span className="maputnik-layer-list-item-id">{this.props.layerId}</span>
<DraggableLabel {...this.props} />
<span style={{flexGrow: 1}} />
<IconAction
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
action={'delete'}
classBlockName="delete"
onClick={e => this.props.onLayerDestroy(this.props.layerId)}
/>
<IconAction
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
action={'duplicate'}
classBlockName="duplicate"
onClick={e => this.props.onLayerCopy(this.props.layerId)}
/>
<IconAction
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)}
/>
</li>
}
}
export default LayerListItem;
const LayerListItemSortable = SortableElement((props) => <LayerListItem {...props} />);
export default LayerListItemSortable;

View File

@@ -1,9 +1,8 @@
import React from 'react'
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 StringInput from '../inputs/StringInput'
import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceBlock extends React.Component {
@@ -20,7 +19,7 @@ class LayerSourceBlock extends React.Component {
}
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}
>
<AutocompleteInput

View File

@@ -1,9 +1,8 @@
import React from 'react'
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 StringInput from '../inputs/StringInput'
import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceLayer extends React.Component {
@@ -21,7 +20,7 @@ class LayerSourceLayer extends React.Component {
}
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"
>
<AutocompleteInput

View File

@@ -1,7 +1,7 @@
import React from 'react'
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 SelectInput from '../inputs/SelectInput'
@@ -13,7 +13,7 @@ class LayerTypeBlock extends React.Component {
}
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}
>
<SelectInput

View File

@@ -1,7 +1,7 @@
import React from 'react'
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 NumberInput from '../inputs/NumberInput'
@@ -12,15 +12,15 @@ class MaxZoomBlock extends React.Component {
}
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"
>
<NumberInput
value={this.props.value}
onChange={this.props.onChange}
min={styleSpec.latest.layer.maxzoom.minimum}
max={styleSpec.latest.layer.maxzoom.maximum}
default={styleSpec.latest.layer.maxzoom.maximum}
min={latest.layer.maxzoom.minimum}
max={latest.layer.maxzoom.maximum}
default={latest.layer.maxzoom.maximum}
/>
</InputBlock>
}

View File

@@ -1,7 +1,7 @@
import React from 'react'
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 NumberInput from '../inputs/NumberInput'
@@ -12,15 +12,15 @@ class MinZoomBlock extends React.Component {
}
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"
>
<NumberInput
value={this.props.value}
onChange={this.props.onChange}
min={styleSpec.latest.layer.minzoom.minimum}
max={styleSpec.latest.layer.minzoom.maximum}
default={styleSpec.latest.layer.minzoom.minimum}
min={latest.layer.minzoom.minimum}
max={latest.layer.minzoom.maximum}
default={latest.layer.minzoom.minimum}
/>
</InputBlock>
}

View File

@@ -1,8 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import LayerIcon from '../icons/LayerIcon'
import {latest, expression, function as styleFunction} from '@mapbox/mapbox-gl-style-spec'
function groupFeaturesBySourceLayer(features) {
const sources = {}
@@ -30,7 +29,64 @@ function groupFeaturesBySourceLayer(features) {
class FeatureLayerPopup extends React.Component {
static propTypes = {
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() {
@@ -38,21 +94,33 @@ class FeatureLayerPopup extends React.Component {
const items = Object.keys(sources).map(vectorLayerId => {
const layers = sources[vectorLayerId].map((feature, idx) => {
return <label
const featureColor = this._getFeatureColor(feature, this.props.zoom);
return <div
key={idx}
className="maputnik-popup-layer"
>
<div
className="maputnik-popup-layer__swatch"
style={{background: featureColor}}
></div>
<label
className="maputnik-popup-layer__label"
onClick={() => {
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}>
<div className="maputnik-popup-layer-id">{vectorLayerId}</div>

View File

@@ -22,7 +22,7 @@ function renderProperties(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>
<InputBlock key={"property-type"} label={"$type"}>
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
@@ -43,7 +43,7 @@ function removeDuplicatedFeatures(features) {
if(featureIndex === -1) {
uniqueFeatures.push(feature)
} else {
if(uniqueFeatures[featureIndex].hasOwnProperty('counter')) {
if(uniqueFeatures[featureIndex].hasOwnProperty('inspectModeCounter')) {
uniqueFeatures[featureIndex].inspectModeCounter++
} else {
uniqueFeatures[featureIndex].inspectModeCounter = 2

View File

@@ -5,7 +5,6 @@ import MapboxGl from 'mapbox-gl'
import MapboxInspect from 'mapbox-gl-inspect'
import FeatureLayerPopup from './FeatureLayerPopup'
import FeaturePropertyPopup from './FeaturePropertyPopup'
import style from '../../libs/style.js'
import tokens from '../../config/tokens.json'
import colors from 'mapbox-gl-inspect/lib/colors'
import Color from 'color'
@@ -15,10 +14,13 @@ import 'mapbox-gl/dist/mapbox-gl.css'
import '../../mapboxgl.css'
import '../../libs/mapbox-rtl'
function renderPropertyPopup(features) {
var mountNode = document.createElement('div');
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode)
return mountNode.innerHTML;
const IS_SUPPORTED = MapboxGl.supported();
function renderPopup(popup, mountNode) {
ReactDOM.render(popup, mountNode);
var content = mountNode.innerHTML;
return content;
}
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
@@ -66,6 +68,7 @@ export default class MapboxGlMap extends React.Component {
onDataChange: () => {},
onLayerSelect: () => {},
mapboxAccessToken: tokens.mapbox,
options: {},
}
constructor(props) {
@@ -74,27 +77,40 @@ export default class MapboxGlMap extends React.Component {
this.state = {
map: null,
inspect: null,
isPopupOpen: false,
popupX: 0,
popupY: 0,
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
updateMapFromProps(props) {
if(!IS_SUPPORTED) 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
if(!nextProps.inspectModeEnabled) {
if(!props.inspectModeEnabled) {
//Mapbox GL now does diffing natively so we don't need to calculate
//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) {
if(!IS_SUPPORTED) return;
const map = this.state.map;
this.updateMapFromProps(this.props);
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
this.state.inspect.toggleInspector()
}
@@ -102,10 +118,16 @@ export default class MapboxGlMap extends React.Component {
this.state.inspect.render()
}
if (map) {
map.showTileBoundaries = this.props.options.showTileBoundaries;
map.showCollisionBoxes = this.props.options.showCollisionBoxes;
map.showOverdrawInspector = this.props.options.showOverdrawInspector;
}
}
componentDidMount() {
if(!IS_SUPPORTED) return;
const mapOpts = {
...this.props.options,
container: this.container,
@@ -116,13 +138,17 @@ export default class MapboxGlMap extends React.Component {
const map = new MapboxGl.Map(mapOpts);
map.showTileBoundaries = mapOpts.showTileBoundaries;
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
const zoom = new ZoomControl;
map.addControl(zoom, 'top-right');
const nav = new MapboxGl.NavigationControl();
const nav = new MapboxGl.NavigationControl({visualizePitch:true});
map.addControl(nav, 'top-right');
const tmpNode = document.createElement('div');
const inspect = new MapboxInspect({
popup: new MapboxGl.Popup({
closeOnClick: false
@@ -138,18 +164,23 @@ export default class MapboxGlMap extends React.Component {
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
renderPopup: features => {
if(this.props.inspectModeEnabled) {
return renderPropertyPopup(features)
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
} else {
var mountNode = document.createElement('div');
ReactDOM.render(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} />, mountNode)
return mountNode
return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} zoom={this.state.zoom} />, tmpNode);
}
}
})
map.addControl(inspect)
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 => {
@@ -158,12 +189,29 @@ export default class MapboxGlMap extends React.Component {
map: this.state.map
})
})
map.on("zoom", e => {
this.setState({
zoom: map.getZoom()
});
})
}
render() {
if(IS_SUPPORTED) {
return <div
className="maputnik-map"
className="maputnik-map__map"
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>
}
}
}

View File

@@ -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

View 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>
}
}

View File

@@ -2,8 +2,6 @@ import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import Modal from './Modal'
import LayerTypeBlock from '../layers/LayerTypeBlock'
@@ -22,7 +20,7 @@ class AddModal extends React.Component {
sources: PropTypes.object.isRequired,
}
addLayer() {
addLayer = () => {
const changedLayers = this.props.layers.slice(0)
const layer = {
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
const oldType = this.state.type;
const newType = nextState.type;
const oldType = prevState.type;
const newType = this.state.type;
const availableSourcesOld = this.getSources(oldType);
const availableSourcesNew = this.getSources(newType);
@@ -66,11 +64,11 @@ class AddModal extends React.Component {
if(
// Type has changed
oldType !== newType
&& this.state.source !== ""
&& prevState.source !== ""
// Was a valid source previously
&& availableSourcesOld.indexOf(this.state.source) > -1
&& availableSourcesOld.indexOf(prevState.source) > -1
// And is not a valid source now
&& availableSourcesNew.indexOf(nextState.source) < 0
&& availableSourcesNew.indexOf(this.state.source) < 0
) {
// Clear the source
this.setState({
@@ -93,10 +91,19 @@ class AddModal extends React.Component {
"line",
"symbol",
"circle",
"fill-extrusion"
"fill-extrusion",
"heatmap"
],
raster: [
"raster"
],
geojson: [
"fill",
"line",
"symbol",
"circle",
"fill-extrusion",
"heatmap"
]
}
@@ -151,7 +158,7 @@ class AddModal extends React.Component {
}
<Button
className="maputnik-add-layer-button"
onClick={this.addLayer.bind(this)}
onClick={this.addLayer}
data-wd-key="add-layer"
>
Add Layer

View 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;

View File

@@ -1,216 +1,18 @@
import React from 'react'
import PropTypes from 'prop-types'
import Slugify from 'slugify'
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 StringInput from '../inputs/StringInput'
import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button'
import Modal from './Modal'
import MdFileDownload from 'react-icons/lib/md/file-download'
import TiClipboard from 'react-icons/lib/ti/clipboard'
import style from '../../libs/style.js'
import GitHub from 'github-api'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import {MdFileDownload} from 'react-icons/md'
import style from '../../libs/style'
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) {
const changedMetadata = { ...mapStyle.metadata }
@@ -235,9 +37,32 @@ class ExportModal extends React.Component {
}
downloadStyle() {
const blob = new Blob([styleSpec.format(stripAccessTokens(this.props.mapStyle))], {type: "application/json;charset=utf-8"});
saveAs(blob, this.props.mapStyle.id + ".json");
const tokenStyle = format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle)));
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() {
return <Modal
@@ -252,16 +77,34 @@ class ExportModal extends React.Component {
<p>
Download a JSON style to your computer.
</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)}>
<MdFileDownload />
Download
</Button>
</div>
<div className="maputnik-modal-section hide">
<h4>Save style</h4>
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
</div>
</Modal>
}
}

View File

@@ -13,10 +13,6 @@ class LoadingModal extends React.Component {
message: PropTypes.node.isRequired,
}
constructor(props) {
super(props);
}
underlayOnClick(e) {
// This stops click events falling through to underlying modals.
e.stopPropagation();

View File

@@ -1,6 +1,6 @@
import React from 'react'
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'
@@ -19,6 +19,17 @@ class Modal extends React.Component {
underlayClickExits: true
}
// See <https://github.com/maputnik/editor/issues/416>
onClose = () => {
if (document.activeElement) {
document.activeElement.blur();
}
setImmediate(() => {
this.props.onOpenToggle(false);
});
}
getApplicationNode() {
return document.getElementById('app');
}
@@ -32,7 +43,7 @@ class Modal extends React.Component {
getApplicationNode={this.getApplicationNode}
data-wd-key={this.props["data-wd-key"]}
verticallyCenter={true}
onExit={() => this.props.onOpenToggle(false)}
onExit={this.onClose}
>
<div className="maputnik-modal"
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>
<span className="maputnik-modal-header-space"></span>
<button className="maputnik-modal-header-toggle"
onClick={() => this.props.onOpenToggle(false)}
onClick={this.onClose}
data-wd-key={this.props["data-wd-key"]+".close-modal"}
>
<CloseIcon />
<MdClose />
</button>
</header>
<div className="maputnik-modal-scroller">

View File

@@ -4,10 +4,9 @@ import LoadingModal from './LoadingModal'
import Modal from './Modal'
import Button from '../Button'
import FileReaderInput from 'react-file-reader-input'
import request from 'request'
import FileUploadIcon from 'react-icons/lib/md/file-upload'
import AddIcon from 'react-icons/lib/md/add-circle-outline'
import {MdFileUpload} from 'react-icons/md'
import {MdAddCircleOutline} from 'react-icons/md'
import style from '../../libs/style.js'
import publicStyles from '../../config/styles.json'
@@ -30,7 +29,7 @@ class PublicStyle extends React.Component {
<header className="maputnik-public-style-header">
<h4>{this.props.title}</h4>
<span className="maputnik-space" />
<AddIcon />
<MdAddCircleOutline />
</header>
<div
className="maputnik-public-style-thumbnail"
@@ -52,7 +51,9 @@ class OpenModal extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.state = {
styleUrl: ""
};
}
clearError() {
@@ -74,42 +75,59 @@ class OpenModal extends React.Component {
}
}
onStyleSelect(styleUrl) {
onStyleSelect = (styleUrl) => {
this.clearError();
const reqOpts = {
url: styleUrl,
withCredentials: false,
let canceled;
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({
activeRequest: null,
activeRequestUrl: null
});
if (!error && response.statusCode == 200) {
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
const mapStyle = style.ensureStyleValidity(body)
console.log('Loaded style ', mapStyle.id)
this.props.onStyleOpen(mapStyle)
this.onOpenToggle()
} else {
})
.catch((err) => {
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({
activeRequest: activeRequest,
activeRequestUrl: reqOpts.url
activeRequest: {
abort: function() {
canceled = true;
}
},
activeRequestUrl: styleUrl
})
}
onOpenUrl() {
onOpenUrl = () => {
const url = this.styleUrlElement.value;
this.onStyleSelect(url);
}
onUpload(_, files) {
onUpload = (_, files) => {
const [e, file] = files[0];
const reader = new FileReader();
@@ -135,10 +153,19 @@ class OpenModal extends React.Component {
}
onOpenToggle() {
this.setState({
styleUrl: ""
});
this.clearError();
this.props.onOpenToggle();
}
onChangeUrl = () => {
this.setState({
styleUrl: this.styleUrlElement.value
});
}
render() {
const styleOptions = publicStyles.map(style => {
return <PublicStyle
@@ -146,7 +173,7 @@ class OpenModal extends React.Component {
url={style.url}
title={style.title}
thumbnailUrl={style.thumbnail}
onSelect={this.onStyleSelect.bind(this)}
onSelect={this.onStyleSelect}
/>
})
@@ -160,7 +187,9 @@ class OpenModal extends React.Component {
);
}
return <Modal
return (
<div>
<Modal
data-wd-key="open-modal"
isOpen={this.props.isOpen}
onOpenToggle={() => this.onOpenToggle()}
@@ -170,8 +199,8 @@ class OpenModal extends React.Component {
<section className="maputnik-modal-section">
<h2>Upload Style</h2>
<p>Upload a JSON style from your computer.</p>
<FileReaderInput onChange={this.onUpload.bind(this)} tabIndex="-1">
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
<FileReaderInput onChange={this.onUpload} tabIndex="-1">
<Button className="maputnik-upload-button"><MdFileUpload /> Upload</Button>
</FileReaderInput>
</section>
@@ -180,9 +209,22 @@ class OpenModal extends React.Component {
<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>.
</p>
<input data-wd-key="open-modal.url.input" type="text" ref={(input) => this.styleUrlElement = input} className="maputnik-input" placeholder="Enter URL..."/>
<input
data-wd-key="open-modal.url.input"
type="text"
ref={(input) => this.styleUrlElement = input}
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.bind(this)}>Open URL</Button>
<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>
@@ -195,6 +237,7 @@ class OpenModal extends React.Component {
{styleOptions}
</div>
</section>
</Modal>
<LoadingModal
isOpen={!!this.state.activeRequest}
@@ -202,7 +245,8 @@ class OpenModal extends React.Component {
onCancel={(e) => this.onCancelActiveRequest(e)}
message={"Loading: "+this.state.activeRequestUrl}
/>
</Modal>
</div>
)
}
}

View File

@@ -1,7 +1,7 @@
import React from 'react'
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 StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
@@ -11,14 +11,11 @@ class SettingsModal extends React.Component {
static propTypes = {
mapStyle: PropTypes.object.isRequired,
onStyleChanged: PropTypes.func.isRequired,
onChangeMetadataProperty: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
}
changeStyleProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
@@ -27,19 +24,9 @@ class SettingsModal extends React.Component {
this.props.onStyleChanged(changedStyle)
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
render() {
const metadata = this.props.mapStyle.metadata || {}
const {onChangeMetadataProperty} = this.props;
const inputProps = { }
return <Modal
data-wd-key="modal-settings"
@@ -48,7 +35,7 @@ class SettingsModal extends React.Component {
title={'Style Settings'}
>
<div style={{minWidth: 350}}>
<InputBlock label={"Name"} doc={styleSpec.latest.$root.name.doc}>
<InputBlock label={"Name"} doc={latest.$root.name.doc}>
<StringInput {...inputProps}
data-wd-key="modal-settings.name"
value={this.props.mapStyle.name}
@@ -62,7 +49,7 @@ class SettingsModal extends React.Component {
onChange={this.changeStyleProperty.bind(this, "owner")}
/>
</InputBlock>
<InputBlock label={"Sprite URL"} doc={styleSpec.latest.$root.sprite.doc}>
<InputBlock label={"Sprite URL"} doc={latest.$root.sprite.doc}>
<StringInput {...inputProps}
data-wd-key="modal-settings.sprite"
value={this.props.mapStyle.sprite}
@@ -70,7 +57,7 @@ class SettingsModal extends React.Component {
/>
</InputBlock>
<InputBlock label={"Glyphs URL"} doc={styleSpec.latest.$root.glyphs.doc}>
<InputBlock label={"Glyphs URL"} doc={latest.$root.glyphs.doc}>
<StringInput {...inputProps}
data-wd-key="modal-settings.glyphs"
value={this.props.mapStyle.glyphs}
@@ -82,15 +69,23 @@ class SettingsModal extends React.Component {
<StringInput {...inputProps}
data-wd-key="modal-settings.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 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}
data-wd-key="modal-settings.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>
@@ -99,12 +94,13 @@ class SettingsModal extends React.Component {
data-wd-key="modal-settings.maputnik:renderer"
options={[
['mbgljs', 'MapboxGL JS'],
['ol3', 'Open Layers 3'],
['ol', 'Open Layers (experimental)'],
]}
value={metadata['maputnik:renderer'] || 'mbgljs'}
onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')}
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
/>
</InputBlock>
</div>
</Modal>
}

View File

@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
@@ -11,10 +10,6 @@ class ShortcutsModal extends React.Component {
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
}
render() {
const help = [
{
@@ -45,6 +40,10 @@ class ShortcutsModal extends React.Component {
key: "m",
text: "Focus map"
},
{
key: "!",
text: "Debug modal"
},
]

View File

@@ -1,6 +1,6 @@
import React from 'react'
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 Button from '../Button'
import InputBlock from '../inputs/InputBlock'
@@ -12,8 +12,7 @@ import style from '../../libs/style'
import { deleteSource, addSource, changeSource } from '../../libs/source'
import publicSources from '../../config/tilesets.json'
import AddIcon from 'react-icons/lib/md/add-circle-outline'
import DeleteIcon from 'react-icons/lib/md/delete'
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
class PublicSource extends React.Component {
static propTypes = {
@@ -34,7 +33,7 @@ class PublicSource extends React.Component {
<p className="maputnik-public-source-id">#{this.props.id}</p>
</div>
<span className="maputnik-space" />
<AddIcon />
<MdAddCircleOutline />
</Button>
</div>
}
@@ -76,7 +75,7 @@ class ActiveSourceTypeEditor extends React.Component {
onClick={()=> this.props.onDelete(this.props.sourceId)}
style={{backgroundColor: 'transparent'}}
>
<DeleteIcon />
<MdDelete />
</Button>
</div>
<div className="maputnik-active-source-type-editor-content">
@@ -153,7 +152,7 @@ class AddSource extends React.Component {
onChange={v => this.setState({ sourceId: v})}
/>
</InputBlock>
<InputBlock label={"Source Type"} doc={styleSpec.latest.source_vector.type.doc}>
<InputBlock label={"Source Type"} doc={latest.source_vector.type.doc}>
<SelectInput
options={[
['geojson', 'GeoJSON'],
@@ -236,9 +235,12 @@ class SourcesModal extends React.Component {
<p>
Add one of the publicly available sources to your style.
</p>
<div style={{maxwidth: 500}}>
<div className="maputnik-public-sources" style={{maxwidth: 500}}>
{tilesetOptions}
</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 className="maputnik-modal-section">

View File

@@ -12,8 +12,6 @@ class SurveyModal extends React.Component {
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) { super(props); }
onClick = () => {
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');

View File

@@ -1,6 +1,6 @@
import React from 'react'
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 StringInput from '../inputs/StringInput'
import NumberInput from '../inputs/NumberInput'
@@ -16,7 +16,7 @@ class TileJSONSourceEditor extends React.Component {
render() {
return <div>
<InputBlock label={"TileJSON URL"} doc={styleSpec.latest.source_vector.url.doc}>
<InputBlock label={"TileJSON URL"} doc={latest.source_vector.url.doc}>
<StringInput
value={this.props.source.url}
onChange={url => this.props.onChange({
@@ -50,7 +50,7 @@ class TileURLSourceEditor extends React.Component {
const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th']
const tiles = this.props.source.tiles || []
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
value={tileUrl}
onChange={this.changeTileUrl.bind(this, tileIndex)}
@@ -62,7 +62,7 @@ class TileURLSourceEditor extends React.Component {
render() {
return <div>
{this.renderTileUrls()}
<InputBlock label={"Min Zoom"} doc={styleSpec.latest.source_vector.minzoom.doc}>
<InputBlock label={"Min Zoom"} doc={latest.source_vector.minzoom.doc}>
<NumberInput
value={this.props.source.minzoom || 0}
onChange={minzoom => this.props.onChange({
@@ -71,7 +71,7 @@ class TileURLSourceEditor extends React.Component {
})}
/>
</InputBlock>
<InputBlock label={"Max Zoom"} doc={styleSpec.latest.source_vector.maxzoom.doc}>
<InputBlock label={"Max Zoom"} doc={latest.source_vector.maxzoom.doc}>
<NumberInput
value={this.props.source.maxzoom || 22}
onChange={maxzoom => this.props.onChange({
@@ -93,7 +93,7 @@ class GeoJSONSourceEditor extends React.Component {
}
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
value={this.props.source.data}
onChange={data => this.props.onChange({
@@ -125,14 +125,14 @@ class SourceTypeEditor extends React.Component {
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...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
options={Object.keys(styleSpec.latest.source_raster_dem.encoding.values)}
options={Object.keys(latest.source_raster_dem.encoding.values)}
onChange={encoding => this.props.onChange({
...this.props.source,
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>
</TileURLSourceEditor>

View File

@@ -1,14 +1,9 @@
{
"version": 8,
"name": "Empty Style",
"metadata": {
"mapbox:autocomposite": false,
"mapbox:type": "template",
"maputnik:renderer": "mbgljs",
"openmaptiles:version": "3.x"
},
"metadata": {},
"sources": {},
"glyphs": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"sprites": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"sprite": "",
"glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
"layers": []
}

View File

@@ -71,7 +71,8 @@
"fill-extrusion-translate-anchor",
"fill-extrusion-pattern",
"fill-extrusion-height",
"fill-extrusion-base"
"fill-extrusion-base",
"fill-extrusion-vertical-gradient"
]
}
]
@@ -105,7 +106,8 @@
"fields": [
"symbol-placement",
"symbol-spacing",
"symbol-avoid-edges"
"symbol-avoid-edges",
"symbol-z-order"
]
},
{
@@ -137,6 +139,7 @@
"title": "Icon layout properties",
"type": "properties",
"fields": [
"icon-image",
"icon-allow-overlap",
"icon-ignore-placement",
"icon-optional",
@@ -144,7 +147,6 @@
"icon-size",
"icon-text-fit",
"icon-text-fit-padding",
"icon-image",
"icon-rotate",
"icon-padding",
"icon-keep-upright",
@@ -193,7 +195,8 @@
"raster-brightness-max",
"raster-saturation",
"raster-contrast",
"raster-fade-duration"
"raster-fade-duration",
"raster-resampling"
]
}
]

View File

@@ -2,55 +2,67 @@
{
"id": "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"
},
{
"id": "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"
},
{
"id": "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"
},
{
"id": "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"
},
{
"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",
"title": "OSM Liberty",
"url": "https://rawgit.com/maputnik/osm-liberty/gh-pages/style.json",
"thumbnail": "https://cdn.rawgit.com/maputnik/osm-liberty/gh-pages/thumbnail.png"
"url": "https://maputnik.github.io/osm-liberty/style.json",
"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",
"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="
},
{
"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"
}
]

View File

@@ -1,12 +1,22 @@
{
"mapbox-streets": {
"type": "vector",
"url": "mapbox://mapbox.mapbox-streets-v7",
"title": "Mapbox Streets"
},
"openmaptiles": {
"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"
},
"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"
}
}

View File

@@ -1,4 +1,5 @@
{
"mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w",
"openmaptiles": "Og58UhhtiiTaLVlPtPgs"
"openmaptiles": "KDhMfHvorAFkFe64wlZb",
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6"
}

View File

@@ -1,3 +1,4 @@
import { IconContext } from "react-icons";
import React from 'react';
import ReactDOM from 'react-dom';
@@ -5,4 +6,9 @@ import './favicon.ico'
import './styles/index.scss'
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")
);

View File

@@ -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.
const reducedMotionEnabled = lodash.throttle(() => {
const reducedMotionEnabled = throttle(() => {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
}, 3000);

View File

@@ -1,33 +1,37 @@
import request from 'request'
import style from './style.js'
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 {
constructor(opts) {
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) {
request(localUrl + '/styles', (error, response, body) => {
if (!error && body && response.statusCode == 200) {
const styleIds = JSON.parse(body)
fetch(this.localUrl + '/styles', {
mode: 'cors',
})
.then((response) => {
return response.json();
})
.then((body) => {
const styleIds = body;
this.latestStyleId = styleIds[0]
this.notifyLocalChanges()
cb(null)
} else {
})
.catch(function(e) {
cb(new Error('Can not connect to style API'))
}
})
}
notifyLocalChanges() {
const connection = new ReconnectingWebSocket(websocketUrl)
const connection = new ReconnectingWebSocket(this.websocketUrl)
connection.onmessage = e => {
if(!e.data) return
console.log('Received style update from API')
@@ -44,8 +48,14 @@ export class ApiStyleStore {
latestStyle(cb) {
if(this.latestStyleId) {
request(localUrl + '/styles/' + this.latestStyleId, (error, response, body) => {
cb(style.ensureStyleValidity(JSON.parse(body)))
fetch(this.localUrl + '/styles/' + this.latestStyleId, {
mode: 'cors',
})
.then(function(response) {
return response.json();
})
.then(function(body) {
cb(style.ensureStyleValidity(body))
})
} else {
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(mapStyle) {
const id = mapStyle.id
request.put({
url: localUrl + '/styles/' + id,
json: true,
body: mapStyle
}, (error, response, body) => {
fetch(this.localUrl + '/styles/' + id, {
method: "PUT",
mode: 'cors',
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify(mapStyle)
})
.catch(function(error) {
if(error) console.error(error)
})
return mapStyle

View File

@@ -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) {
const changes = styleSpec.diff(beforeStyle, afterStyle)
const changes = diff(beforeStyle, afterStyle)
return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' '))
}

View File

@@ -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 setFilterOps = ['in', '!in']
export const otherFilterOps = Object
.keys(styleSpec.latest.filter_operator.values)
.keys(latest.filter_operator.values)
.filter(op => combiningFilterOps.indexOf(op) < 0)

View File

@@ -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) {
const changedPaintProps = { ...layer.paint }
Object.keys(changedPaintProps).forEach(propertyName => {
if(!(propertyName in styleSpec.latest['paint_' + newType])) {
if(!(propertyName in latest['paint_' + newType])) {
delete changedPaintProps[propertyName]
}
})
const changedLayoutProps = { ...layer.layout }
Object.keys(changedLayoutProps).forEach(propertyName => {
if(!(propertyName in styleSpec.latest['layout_' + newType])) {
if(!(propertyName in latest['layout_' + newType])) {
delete changedLayoutProps[propertyName]
}
})
@@ -31,7 +31,11 @@ export function changeProperty(layer, group, property, newValue) {
if(newValue === undefined) {
if(group) {
const newLayer = {
...layer
...layer,
// Change object so the diff works in ./src/components/map/MapboxGlMap.jsx
[group]: {
...layer[group]
}
};
delete newLayer[group][property];

View File

@@ -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.
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 objectUrl = window.URL.createObjectURL(blob, {
const blob = new window.Blob([data], {
type: "text/javascript"
});
const objectUrl = window.URL.createObjectURL(blob);
MapboxGl.setRTLTextPlugin(objectUrl);

View File

@@ -1,22 +1,19 @@
import request from 'request'
import npmurl from 'url'
function loadJSON(url, defaultValue, cb) {
request({
url: url,
withCredentials: false,
}, (error, response, body) => {
if (!error && body && response.statusCode == 200) {
try {
cb(JSON.parse(body))
} catch(err) {
console.error(err)
cb(defaultValue)
}
} else {
fetch(url, {
mode: 'cors',
credentials: "same-origin"
})
.then(function(response) {
return response.json();
})
.then(function(body) {
cb(body)
})
.catch(function() {
console.warn('Can not metadata for ' + url)
cb(defaultValue)
}
})
}

View File

@@ -1,4 +1,3 @@
import React from 'react';
import deref from '@mapbox/mapbox-gl-style-spec/deref'
import tokens from '../config/tokens.json'
@@ -54,18 +53,28 @@ function indexOfLayer(layers, layerId) {
return null
}
function replaceAccessToken(mapStyle, opts={}) {
const omtSource = mapStyle.sources.openmaptiles
if(!omtSource) return mapStyle
if(!omtSource.hasOwnProperty("url")) return mapStyle
function getAccessToken(sourceName, mapStyle, opts) {
if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") {
sourceName = "thunderforest"
}
const metadata = mapStyle.metadata || {}
let accessToken = metadata['maputnik:openmaptiles_access_token'];
let accessToken = metadata[`maputnik:${sourceName}_access_token`]
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) {
// Early exit.
return mapStyle;
@@ -73,16 +82,34 @@ function replaceAccessToken(mapStyle, opts={}) {
const changedSources = {
...mapStyle.sources,
openmaptiles: {
...omtSource,
url: omtSource.url.replace('{key}', accessToken)
[sourceName]: {
...source,
url: source.url.replace('{key}', accessToken)
}
}
const changedStyle = {
...mapStyle,
glyphs: mapStyle.glyphs ? mapStyle.glyphs.replace('{key}', accessToken) : mapStyle.glyphs,
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
}
@@ -92,5 +119,6 @@ export default {
emptyStyle,
indexOfLayer,
generateId,
replaceAccessToken,
getAccessToken,
replaceAccessTokens,
}

View File

@@ -1,8 +1,6 @@
import { colorizeLayers } from './style.js'
import style from './style.js'
import { loadStyleUrl } from './urlopen'
import publicSources from '../config/styles.json'
import request from 'request'
const storagePrefix = "maputnik"
const stylePrefix = 'style'

View File

@@ -1,5 +1,5 @@
import request from 'request'
import url from 'url'
import querystring from 'querystring'
import style from './style.js'
export function initialStyleUrl() {
@@ -9,34 +9,57 @@ export function initialStyleUrl() {
export function loadStyleUrl(styleUrl, cb) {
console.log('Loading style', styleUrl)
request({
url: styleUrl,
withCredentials: false,
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
cb(style.ensureStyleValidity(JSON.parse(body)))
} else {
fetch(styleUrl, {
mode: 'cors',
credentials: "same-origin"
})
.then(function(response) {
return response.json();
})
.then(function(body) {
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) {
request({
url: url,
withCredentials: false,
}, (error, response, body) => {
if (!error && body && response.statusCode == 200) {
fetch(url, {
mode: 'cors',
credentials: "same-origin"
})
.then(function(response) {
return response.json();
})
.then(function(body) {
try {
cb(JSON.parse(body))
cb(body)
} catch(err) {
console.error(err)
cb(defaultValue)
}
} else {
})
.catch(function() {
console.error('Can not load JSON from ' + url)
cb(defaultValue)
}
})
}

View File

@@ -14,6 +14,22 @@
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 {
background-color: rgb(28, 31, 36);
border-radius: 0px;
@@ -26,7 +42,7 @@
}
.mapboxgl-ctrl-zoom {
color: rgb(138, 138, 138);
color: #a4a4a4;
font-weight: bold;
padding: 4px 8px;
user-select: none;
@@ -46,15 +62,15 @@
}
.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 {
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 {
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")
.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%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 {

View File

@@ -3,6 +3,7 @@
src: url('../fonts/Roboto-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
@@ -10,6 +11,7 @@
src: url('../fonts/Roboto-Medium.ttf') format('truetype');
font-weight: bold;
font-style: normal;
font-display: swap;
}
html {
@@ -18,6 +20,11 @@ html {
box-sizing: border-box;
}
body {
// The UI is 100% height so prevent bounce scroll on OSX
overflow: hidden;
}
*,
*::before,
*::after {

View File

@@ -1,11 +1,31 @@
// MAP
.maputnik-map {
.maputnik-map__container {
display: flex;
position: fixed !important;
top: $toolbar-height + $toolbar-offset;
right: 0;
bottom: 0;
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
@@ -62,6 +82,12 @@
background-color: lighten($color-midgray, 12);
color: $color-white;
}
&:disabled {
background-color: darken($color-midgray, 5);
color: $color-midgray;
cursor: not-allowed;
}
}
.maputnik-big-button {
@@ -150,3 +176,32 @@
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;
}
}
}
}

View File

@@ -24,6 +24,15 @@
padding-bottom: $margin-5;
}
&-item-handle {
flex: 1;
display: flex;
svg {
margin-right: 4px;
}
}
&-item {
font-weight: 400;
color: $color-lowgray;
@@ -36,7 +45,7 @@
z-index: 2000;
cursor: pointer;
position: relative;
padding: 5px 10px;
padding: 5px;
line-height: 1.3;
max-height: 50px;
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-selected {
background-color: lighten($color-black, 2);
.maputnik-layer-list-icon-action {
display: block;
background: initial;
border: none;
padding: 0 2px;
}
.maputnik-layer-list-icon-action svg {
svg {
fill: darken($color-lowgray, 0.5);
&:hover {
fill: $color-white;
}
}
}
@@ -100,6 +121,8 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: inherit;
text-decoration: none;
}
&-group-header {
@@ -149,7 +172,7 @@
// PROPERTY
.maputnik-default-property {
.maputnik-input-block-label {
color: darken($color-lowgray, 25%);
color: darken($color-lowgray, 20%);
}
.maputnik-string,
@@ -158,7 +181,7 @@
.maputnik-select,
.maputnik-checkbox-wrapper {
background-color: darken($color-gray, 2%);
color: darken($color-lowgray, 25%);
color: darken($color-lowgray, 20%);
}
.maputnik-make-zoom-function svg {
@@ -167,7 +190,7 @@
.maputnik-multibutton .maputnik-button {
background-color: darken($color-midgray, 10%);
color: darken($color-lowgray, 25%);
color: darken($color-lowgray, 20%);
&:hover {
background-color: lighten($color-midgray, 12);
@@ -184,6 +207,11 @@
.more-menu {
position: relative;
svg {
width: 22px;
height: 22px;
}
&__menu {
position: absolute;
z-index: 9999;
@@ -218,3 +246,10 @@
min-width: 28px;
}
}
// Clone of the element which is sorted
.sortableHelper {
font-family: $font-family;
z-index: 9999;
border: none;
}

View File

@@ -1,6 +1,6 @@
//SCROLLING
.maputnik-scroll-container {
overflow-x: visible;
overflow-x: hidden;
overflow-y: scroll;
bottom: 0;
left: 0;

View File

@@ -1,7 +1,13 @@
//OPENLAYERS
.maputnik-layout {
.ol-zoom {
top: 10px;
top: 40px;
right: 10px;
left: auto;
}
.ol-rotate {
top: 94px;
right: 10px;
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;
}

View File

@@ -125,6 +125,10 @@
}
//SOURCE MODAL
.maputnik-public-sources {
margin-bottom: 1.5%;
}
.maputnik-public-source {
vertical-align: top;
margin-top: 1.5%;
@@ -150,6 +154,7 @@
.maputnik-public-source-id {
font-weight: 400;
text-align: left;
}
.maputnik-active-source-type-editor {
@@ -242,12 +247,13 @@
}
.maputnik-modal-survey {
width: 372px;
width: 400px;
}
.maputnik-modal-survey__logo {
display: block;
margin: 0 auto;
height: 128px;
}
.maputnik-modal-survey__description {

View File

@@ -1,4 +1,15 @@
.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;
color: $color-lowgray;
cursor: pointer;
@@ -11,12 +22,14 @@
.maputnik-popup-layer-id {
padding-left: $margin-2;
padding-right: $margin-2;
padding-right: 1.6em;
background-color: $color-midgray;
color: $color-white;
}
.maputnik-feature-property-popup {
max-height: calc(50vh - 40px); /* toolbar height: 40px */
overflow-y: auto;
.maputnik-input-block {
margin: 0;
margin-left: $margin-2;

View File

@@ -0,0 +1,3 @@
.react-codemirror2 {
max-width: 100%;
}

View File

@@ -1,6 +1,7 @@
// See <https://github.com/nkbt/react-collapse/commit/4f4fbce7c6c07b082dc62062338c9294c656f9df>
.react-collapse-container {
display: flex;
max-width: 100%;
> * {
flex: 1;

View File

@@ -12,4 +12,8 @@ div:not(.maputnik-toolbar__actions) {
padding-left: 2px;
padding-right: 2px;
}
// Styling for Firefox
scrollbar-width: thin;
scrollbar-color: #666 #26282e;
}

View File

@@ -80,6 +80,10 @@
}
}
.maputnik-toolbar-name {
text-transform: capitalize;
}
.maputnik-toolbar-version {
font-size: 10px;
margin-left: 4px;
@@ -92,6 +96,24 @@
@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 {
padding-left: $margin-1;
}

View File

@@ -1,7 +1,7 @@
$color-black: #1c1f24;
$color-gray: #26282e;
$color-midgray: #36383e;
$color-lowgray: #8e8e8e;
$color-black: #191b20;
$color-gray: #222429;
$color-midgray: #303237;
$color-lowgray: #a4a4a4;
$color-white: #f0f0f0;
$color-red: #cf4a4a;
$color-green: #53b972;
@@ -38,6 +38,7 @@ $toolbar-offset: 0;
@import 'popup';
@import 'map';
@import 'react-collapse';
@import 'react-codemirror';
/**
* Hacks for webdriverio isVisibleWithinViewport
@@ -49,3 +50,9 @@ $toolbar-offset: 0;
.maputnik-layout {
height: 100vh;
}
.react-icons {
vertical-align: middle;
width: 14px;
height: 14px;
}

View File

@@ -18,7 +18,7 @@ module.exports = {
var result = browser.executeAsync(function(done) {
window.debug.get("maputnik", "styleStore").latestStyle(done);
})
return result.value;
return result;
},
getRevisionStore: function(browser) {
var result = browser.execute(function(done) {
@@ -34,15 +34,16 @@ module.exports = {
modal: {
addLayer: {
open: function() {
var selector = wd.$('layer-list:add-layer');
browser.click(selector);
const selector = $(wd.$('layer-list:add-layer'));
selector.click();
// Wait for events
browser.flushReactUpdates();
browser.waitForExist(wd.$('modal:add-layer'));
browser.isVisible(wd.$('modal:add-layer'));
browser.isVisibleWithinViewport(wd.$('modal:add-layer'));
const elem = $(wd.$('modal:add-layer'));
elem.waitForExist();
elem.isDisplayed();
elem.isDisplayedInViewport();
// Wait for events
browser.flushReactUpdates();
@@ -58,7 +59,8 @@ module.exports = {
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.setValueSafe(wd.$("add-layer.layer-id", "input"), id);
@@ -67,7 +69,8 @@ module.exports = {
}
browser.flushReactUpdates();
browser.click(wd.$("add-layer"));
const elem_addLayer = $(wd.$("add-layer"));
elem_addLayer.click();
return id;
}

View File

@@ -1,7 +1,6 @@
var assert = require("assert");
var config = require("../../config/specs");
var helper = require("../helper");
var wd = require("../../wd-helper");
describe.skip("history", function() {
@@ -14,6 +13,7 @@ describe.skip("history", function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
]));
browser.alertAccept();
helper.modal.addLayer.open();

View File

@@ -1,6 +1,4 @@
var assert = require('assert');
var config = require("../config/specs");
var geoServer = require("../geojson-server");
var helper = require("./helper");
require("./util/webdriverio-ext");
@@ -13,7 +11,12 @@ describe('maputnik', function() {
"geojson:example",
"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();
});

View File

@@ -11,7 +11,9 @@ describe("layers", function() {
"geojson:example",
"raster:raster"
]));
browser.waitForExist(".maputnik-toolbar-link");
browser.acceptAlert();
const elem = $(".maputnik-toolbar-link");
elem.waitForExist();
browser.flushReactUpdates();
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);
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);
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);
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);
assert.deepEqual(styleObj.layers, [
@@ -146,11 +151,13 @@ describe("layers", function() {
// Setup
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.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);
assert.deepEqual(styleObj.layers, [
@@ -168,11 +175,13 @@ describe("layers", function() {
it("id", function() {
var bgId = createBackground();
browser.click(wd.$("layer-list-item:background:"+bgId))
const elem = $(wd.$("layer-list-item:background:"+bgId));
elem.click();
var id = uuid();
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);
assert.deepEqual(styleObj.layers, [
@@ -189,9 +198,11 @@ describe("layers", function() {
it("min-zoom", function() {
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.click(wd.$("layer-editor.layer-id", "input"));
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
elem2.click();
var styleObj = helper.getStyleStore(browser);
assert.deepEqual(styleObj.layers, [
@@ -219,9 +230,11 @@ describe("layers", function() {
it("max-zoom", function() {
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.click(wd.$("layer-editor.layer-id", "input"));
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
elem2.click();
var styleObj = helper.getStyleStore(browser);
assert.deepEqual(styleObj.layers, [
@@ -237,9 +250,11 @@ describe("layers", function() {
var bgId = createBackground();
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.click(wd.$("layer-editor.layer-id", "input"));
const elem2 = $(wd.$("layer-editor.layer-id", "input"));
elem2.click();
var styleObj = helper.getStyleStore(browser);
assert.deepEqual(styleObj.layers, [
@@ -449,6 +464,7 @@ describe("layers", function() {
browser.url(config.baseUrl+"?debug&style="+getStyleUrl([
"geojson:example"
]));
browser.alertAccept();
helper.modal.addLayer.open();
var aId = helper.modal.addLayer.fill({
@@ -482,4 +498,3 @@ describe("layers", function() {
})
})
});

View File

@@ -1,5 +1,3 @@
var assert = require('assert');
var wd = require("../../wd-helper");
var config = require("../../config/specs");
var helper = require("../helper");
@@ -11,6 +9,7 @@ describe("map", function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
])+"#"+zoomLevel+"/41.3805/2.1635");
browser.alertAccept();
browser.waitUntil(function () {
return (
@@ -24,6 +23,7 @@ describe("map", function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
])+"#"+zoomLevel+"/41.3805/2.1635");
browser.alertAccept();
browser.click(".mapboxgl-ctrl-zoom-in")
browser.waitUntil(function () {

View File

@@ -7,14 +7,16 @@ var helper = require("../helper");
function closeModal(wdKey) {
browser.waitUntil(function() {
return browser.isVisibleWithinViewport(wd.$(wdKey));
const elem = $(wdKey);
return elem.isDisplayedInViewport();
});
var closeBtnSelector = wd.$(wdKey+".close-modal");
browser.click(closeBtnSelector);
const closeBtnSelector = $(wd.$(wdKey+".close-modal"));
closeBtnSelector.click();
browser.waitUntil(function() {
return !browser.isVisibleWithinViewport(wd.$(wdKey));
const elem = $(wdKey);
return !elem.isDisplayed();
});
}
@@ -26,10 +28,12 @@ describe("modals", function() {
beforeEach(function() {
browser.url(config.baseUrl+"?debug");
browser.waitForExist(".maputnik-toolbar-link");
const elem = $(".maputnik-toolbar-link");
elem.waitForExist();
browser.flushReactUpdates();
browser.click(wd.$("nav:open"))
const elem2 = $(wd.$("nav:open"));
elem2.click();
browser.flushReactUpdates();
});
@@ -37,8 +41,10 @@ describe("modals", function() {
closeModal("open-modal");
});
it("upload", function() {
browser.waitForExist("*[type='file']")
// "chooseFile" command currently not available for wdio v5 https://github.com/webdriverio/webdriverio/pull/3632
it.skip("upload", function() {
const elem = $("*[type='file']");
elem.waitForExist();
browser.chooseFile("*[type='file']", styleFilePath);
var styleObj = helper.getStyleStore(browser);
@@ -50,8 +56,8 @@ describe("modals", function() {
browser.setValueSafe(wd.$("open-modal.url.input"), styleFileUrl);
var selector = wd.$("open-modal.url.button");
browser.click(selector);
const selector = $(wd.$("open-modal.url.button"));
selector.click();
// Allow the network request to happen
// NOTE: Its localhost so this should be fast.
@@ -70,10 +76,12 @@ describe("modals", function() {
beforeEach(function() {
browser.url(config.baseUrl+"?debug");
browser.waitForExist(".maputnik-toolbar-link");
const elem = $(".maputnik-toolbar-link");
elem.waitForExist();
browser.flushReactUpdates();
browser.click(wd.$("nav:export"))
const elem2 = $(wd.$("nav:export"));
elem2.click();
browser.flushReactUpdates();
});
@@ -99,8 +107,10 @@ describe("modals", function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"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() {
browser.url(config.baseUrl+"?debug");
browser.waitForExist(".maputnik-toolbar-link");
const elem = $(".maputnik-toolbar-link");
elem.waitForExist();
browser.flushReactUpdates();
browser.click(wd.$("nav:settings"))
const elem2 = $(wd.$("nav:settings"));
elem2.click();
browser.flushReactUpdates();
});
it("name", function() {
browser.setValueSafe(wd.$("modal-settings.name"), "foobar")
browser.click(wd.$("modal-settings.owner"))
const elem = $(wd.$("modal-settings.owner"));
elem.click();
browser.flushReactUpdates();
var styleObj = helper.getStyleStore(browser);
@@ -125,7 +138,8 @@ describe("modals", function() {
})
it("owner", function() {
browser.setValueSafe(wd.$("modal-settings.owner"), "foobar")
browser.click(wd.$("modal-settings.name"))
const elem = $(wd.$("modal-settings.name"));
elem.click();
browser.flushReactUpdates();
var styleObj = helper.getStyleStore(browser);
@@ -133,7 +147,8 @@ describe("modals", function() {
})
it("sprite url", function() {
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();
var styleObj = helper.getStyleStore(browser);
@@ -142,7 +157,8 @@ describe("modals", function() {
it("glyphs url", function() {
var glyphsUrl = "http://example.com/{fontstack}/{range}.pbf"
browser.setValueSafe(wd.$("modal-settings.glyphs"), glyphsUrl)
browser.click(wd.$("modal-settings.name"))
const elem = $(wd.$("modal-settings.name"));
elem.click();
browser.flushReactUpdates();
var styleObj = helper.getStyleStore(browser);
@@ -152,7 +168,8 @@ describe("modals", function() {
it("mapbox access token", function() {
var apiKey = "testing123";
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();
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";
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();
var styleObj = helper.getStyleStore(browser);
assert.equal(styleObj.metadata["maputnik:openmaptiles_access_token"], apiKey);
})
it("style renderer", function() {
var selector = wd.$("modal-settings.maputnik:renderer");
browser.selectByValue(selector, "ol3");
browser.click(wd.$("modal-settings.name"))
it("thunderforest access token", function() {
var apiKey = "testing123";
browser.setValueSafe(wd.$("modal-settings.maputnik:thunderforest_access_token"), apiKey);
const elem = $(wd.$("modal-settings.name"));
elem.click();
browser.flushReactUpdates();
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");
})
})

View File

@@ -1,4 +1,3 @@
var artifacts = require("../../artifacts");
var config = require("../../config/specs");
var helper = require("../helper");
var wd = require("../../wd-helper");
@@ -9,17 +8,16 @@ var wd = require("../../wd-helper");
describe('screenshots', function() {
beforeEach(function() {
browser.windowHandleSize({
width: 1280,
height: 800
});
browser.setWindowSize(1280, 800)
})
it("front_page", function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
]));
browser.waitForExist(".maputnik-toolbar-link");
browser.acceptAlert();
const elem = $(".maputnik-toolbar-link");
elem.waitForExist();
browser.flushReactUpdates();
browser.takeScreenShot("/front_page.png")
@@ -29,10 +27,13 @@ describe('screenshots', function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
]));
browser.waitForExist(".maputnik-toolbar-link");
browser.acceptAlert();
const elem = $(".maputnik-toolbar-link");
elem.waitForExist();
browser.flushReactUpdates();
browser.click(wd.$("nav:open"))
const nav_open = $(wd.$("nav:open"));
nav_open.waitForExist();
browser.flushReactUpdates();
browser.takeScreenShot("/open.png")
@@ -42,10 +43,13 @@ describe('screenshots', function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
]));
browser.waitForExist(".maputnik-toolbar-link");
browser.acceptAlert();
const elem = $(".maputnik-toolbar-link")
elem.waitForExist()
browser.flushReactUpdates();
browser.click(wd.$("nav:export"))
const nav_export = $(wd.$("nav:export"));
nav_export.waitForExist();
browser.flushReactUpdates();
browser.takeScreenShot("/export.png")
@@ -55,10 +59,13 @@ describe('screenshots', function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
]));
browser.waitForExist(".maputnik-toolbar-link");
browser.acceptAlert();
const elem = $(".maputnik-toolbar-link")
elem.waitForExist()
browser.flushReactUpdates();
browser.click(wd.$("nav:sources"))
const nav_sources = $(wd.$("nav:sources"));
nav_sources.waitForExist();
browser.flushReactUpdates();
browser.takeScreenShot("/sources.png")
@@ -68,10 +75,13 @@ describe('screenshots', function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
]));
browser.waitForExist(".maputnik-toolbar-link");
browser.acceptAlert();
const elem = $(".maputnik-toolbar-link")
elem.waitForExist()
browser.flushReactUpdates();
browser.click(wd.$("nav:settings"))
const nav_settings = $(wd.$("nav:settings"));
nav_settings.waitForExist();
browser.flushReactUpdates();
browser.takeScreenShot("/settings.png")
@@ -81,10 +91,14 @@ describe('screenshots', function() {
browser.url(config.baseUrl+"?debug&style="+helper.getStyleUrl([
"geojson:example"
]));
browser.waitForExist(".maputnik-toolbar-link");
browser.acceptAlert();
const elem = $(".maputnik-toolbar-link")
elem.waitForExist()
browser.flushReactUpdates();
browser.click(wd.$("nav:inspect"))
const selectBox = $(wd.$("nav:inspect", "select"));
selectBox.selectByAttribute('value', 'inspect');
browser.flushReactUpdates();
browser.takeScreenShot("/inspect.png")

View File

@@ -3,8 +3,8 @@ var fs = require("fs");
var path = require("path");
browser.timeoutsAsyncScript(20*1000);
browser.timeoutsImplicitWait(20*1000);
browser.setTimeout({ 'script': 20*1000 });
browser.setTimeout({ 'implicit': 20*1000 });
var SCREENSHOTS_PATH = artifacts.pathSync("/screenshots");
@@ -16,15 +16,18 @@ var SCREENSHOTS_PATH = artifacts.pathSync("/screenshots");
try {
browser.addCommand('setValueSafe', function(selector, text) {
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) {
throw "Too many elements found";
}
browser.setValue(selector, text);
var browserText = browser.getValue(selector);
const elem2 = $(selector);
elem2.setValue(text);
var browserText = elem2.getValue();
if(browserText == text) {
return;
@@ -39,17 +42,17 @@ try {
})
browser.addCommand('takeScreenShot', function(filepath) {
var data = browser.screenshot();
var data = browser.takeScreenshot();
fs.writeFileSync(path.join(SCREENSHOTS_PATH, filepath), data.value, 'base64');
});
browser.addCommand('flushReactUpdates', function() {
browser.executeAsync(function(done) {
// For any events to propogate
setImmediate(function() {
setTimeout(function() {
// For the DOM to be updated.
setImmediate(done);
})
setTimeout(done, 0);
}, 0)
})
})