Compare commits

...

817 Commits

Author SHA1 Message Date
orangemug c1a59200e2 1.6.1 2019-10-13 12:02:26 +01:00
Orange Mug 369cc23a30 Merge pull request #553 from orangemug/maintenance/update-deps-20191009
Update dependencies
2019-10-12 11:56:38 +01:00
orangemug 019c6a0086 Merge remote-tracking branch 'origin/maintenance/update-deps-20191009' into maintenance/update-deps-20191009 2019-10-12 09:10:42 +01:00
orangemug c1bee74b57 Switched back from using yarn to npm for lockfiles. 2019-10-12 09:09:39 +01:00
Orange Mug b794279304 Bumped mapbox-gl to 1.4.1
Co-Authored-By: pathmapper <pathmapper@posteo.de>
2019-10-12 07:08:30 +01:00
Orange Mug 935dfa1704 Merge pull request #555 from pathmapper/update_omt_styles
Update OMT styles
2019-10-12 07:03:17 +01:00
pathmapper bda7a0e659 Update OMT styles 2019-10-11 16:13:08 +02:00
orangemug 338c6b59a8 Lock deps in package.json 2019-10-09 22:06:59 +01:00
orangemug 021f8ab400 Update all the dependencies 2019-10-09 19:51:57 +01:00
Orange Mug f305db9e3e Merge pull request #551 from pathmapper/update_readme
Remove 1.6-beta from readme
2019-10-08 21:25:02 +01:00
pathmapper e916b25594 Remove 1.6-beta from readme 2019-10-08 21:59:34 +02:00
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 7c00775515 1.3.0-beta 2018-07-11 08:22:30 +01:00
Orange Mug 4b5536b282 Merge pull request #335 from gregorywolanski/survey
Survey
2018-07-08 15:50:29 +01:00
Gregory Wolanski fb84cfee1c Survey (#328): Proper contrast ratio 2018-07-08 16:27:59 +02:00
Gregory Wolanski 9132262106 Merge branch 'survey' of https://github.com/gregorywolanski/editor into survey 2018-07-08 14:43:03 +02:00
Gregory Wolanski 5de9e708e9 Survey (#328): Cleaning 2018-07-08 14:42:49 +02:00
Gregory Wolanski 4df63c7287 Update _base.scss 2018-07-08 14:38:52 +02:00
Gregory Wolanski a88ca031d0 Survey (#328)
Elements promoting the survey inside Maputnik after feedback
2018-07-08 14:34:46 +02:00
Gregory Wolanski 452706f35c Survey (#328) 2018-06-30 10:17:14 +02:00
Gregory Wolanski 8b0aa194cf Survey (#328)
Elements promoting the survey inside Maputnik after feedback
2018-06-30 10:09:23 +02:00
Orange Mug b9aa7e9206 Merge pull request #333 from pathmapper/master
Update repository for OSM Liberty
2018-06-30 07:09:15 +02:00
pathmapper e35f106482 Update repository for OSM Liberty 2018-06-29 11:20:32 +02:00
Gregory Wolanski b7a97cf8ee Survey (#328)
Elements promoting the survey inside Maputnik
2018-06-25 19:52:48 +02:00
orangemug 85a28999fb Initial color accessibility UI 2018-06-18 20:28:24 +01:00
Orange Mug 9208115981 Merge pull request #330 from orangemug/feature/loading-modal
Loading dialog
2018-06-18 20:27:39 +01:00
orangemug afbdaecd0a Abstracted out <LoadingModal/> 2018-06-18 19:06:16 +01:00
orangemug 558f3d649d Added dialog styling. 2018-06-18 18:17:33 +01:00
Orange Mug 417511d577 Merge pull request #329 from orangemug/feature/osm-donate-readme
Added link to <https://maputnik.github.io/donate>
2018-06-16 09:48:18 +01:00
orangemug df350534ce Added link to <https://maputnik.github.io/donate> 2018-06-16 09:46:30 +01:00
orangemug 7167235146 Added loading modal when opening styles. 2018-06-15 20:57:39 +01:00
Orange Mug 7a7f2eb7de Merge pull request #315 from orangemug/feature/option-to-display-tile-boundaries
Added option to display tile boundaries
2018-06-03 20:26:17 +01:00
orangemug cd28a53f6a Fixed failing tests, these weren't flaky tests... ooops! 2018-06-03 18:28:55 +01:00
orangemug 1fe31ac0ec Fix for bad lint error. 2018-06-03 17:55:46 +01:00
orangemug ffce8e3ba5 Added missing file. 2018-06-03 17:37:54 +01:00
Orange Mug a28a417ebc Merge pull request #314 from orangemug/fix/various-fixes
Small bug fixes
2018-06-03 17:35:44 +01:00
orangemug 6cdb56d13f Improved showTileBoundaries and query string support 2018-06-03 17:33:08 +01:00
orangemug 0516e587b4 Added option to display tile boundries (issue #202) 2018-06-03 17:17:45 +01:00
orangemug 5b4063105b Added missing 'noopener noreferrer' 2018-06-03 16:59:41 +01:00
orangemug d9a5548762 Small bug fixes
- Logo DOM sctrucutre now valid, no longer <a/> within </a>
 - `data-wd-key` not longer required
 - `maputnik-doc-popup` not longer hidden by LayerEditor accordion
2018-06-03 16:37:46 +01:00
Orange Mug cae6cffb7b Merge pull request #313 from orangemug/feature/shortcuts
Keyboard shortcuts
2018-06-03 11:18:16 +01:00
orangemug ede782abed Fixed typo. 2018-06-03 10:18:55 +01:00
orangemug 00afbad7ac Fixed lint errors. 2018-06-03 10:00:50 +01:00
Orange Mug edd09ef585 Merge pull request #306 from orangemug/feature/accessibility-list-reorder
Keyboard accessible layer options
2018-06-03 09:57:00 +01:00
orangemug 1e09066779 Merge branch 'feature/accessibility-list-reorder' into feature/shortcuts
Conflicts:
	src/components/App.jsx
2018-06-03 09:41:07 +01:00
orangemug 32edb48e16 Fix for when 'layout.visibility' is undefined 2018-06-03 09:31:02 +01:00
orangemug b116eef147 Merge remote-tracking branch 'upstream/master' into feature/accessibility-list-reorder
Conflicts:
	src/components/App.jsx
2018-06-03 09:22:02 +01:00
orangemug 74d1cd2d01 Renamed 'Sources' -> 'Data Sources' to make it clearer and make shortcuts easier to remember. 2018-06-03 09:17:53 +01:00
Orange Mug fd48d82e42 Merge pull request #312 from orangemug/feature/color-filters
Color blindness emulation
2018-06-02 10:21:39 +01:00
orangemug 480d54c2d8 Finished shortcuts modal styling 2018-06-02 10:17:39 +01:00
orangemug ab9c39b862 Removed additional close button 2018-06-01 20:51:42 +01:00
orangemug dd122d1bac Hide hidden FileReaderInput from keyboard focus 2018-06-01 20:45:05 +01:00
orangemug f9f5e8f925 Changed close button from <a> to <button> 2018-06-01 20:40:51 +01:00
orangemug aa2f4a091c Initial attempt at color blindness emulation 2018-06-01 09:22:18 +01:00
orangemug 13fc699d4a Styling fixes. 2018-05-31 21:09:31 +01:00
orangemug f5e8d473ad Changed toggle visibility text from hide to show/hide 2018-05-31 20:40:21 +01:00
orangemug 35353d75f5 Added application shortcuts and shortcut modal.
Also moved modals into App.jsx to move the business logic to one place.
2018-05-29 17:06:00 +01:00
Orange Mug 0f103c3c00 Merge pull request #309 from orangemug/feature/skip-menu
Added skip-menu link for keyboard users
2018-05-28 13:17:02 +01:00
orangemug 019428a241 Added missing prop-types. 2018-05-28 12:06:22 +01:00
orangemug 6200edea25 Added initial shortcuts. 2018-05-28 12:03:47 +01:00
orangemug fc7395df96 Fixed CircleCI cache to include {{arch}} 2018-05-28 11:34:12 +01:00
orangemug 272f662a34 Changed 'skip' wording
As outlined in <https://webaim.org/techniques/skipnav/>
2018-05-28 11:29:49 +01:00
orangemug d59d9cde95 Fixed OSX working directory if CircleCI config. 2018-05-28 11:19:04 +01:00
orangemug c71fbcf436 Tidy 2018-05-28 11:15:16 +01:00
Orange Mug 54c79445db Merge pull request #307 from orangemug/fix/public-source-button-size
Fixed public source button size
2018-05-28 10:52:23 +01:00
orangemug a82ba26f86 Added skip-menu link for keyboard users. 2018-05-28 10:50:19 +01:00
orangemug 28af87391d Fixed public source button size. 2018-05-22 21:43:35 +01:00
orangemug 0aabd33538 Remove empty scss blocks 2018-05-22 21:26:11 +01:00
orangemug bd9076c4ff Added additional menu in <LayerEditor/>
This is to make the following options accessible to keyboard users

 - reorder layers
 - duplicate layer
 - delete layer
 - hide/show layer
2018-05-22 21:16:46 +01:00
Orange Mug 1aed761893 Merge pull request #305 from orangemug/feature/public-style-aria-labels
Added aria-label to public styles
2018-05-19 09:39:13 +01:00
orangemug a2a6f6dcab Added aria-label to public styles, also fixed button to reserve space in DOM (fixes #245) 2018-05-19 08:23:41 +01:00
Orange Mug db5dd0f6ee Merge pull request #304 from orangemug/fix/disable-spellcheck-v2
Disable spellcheck on <input/>'s
2018-05-19 07:56:06 +01:00
orangemug 42c3dcf258 Updated package-lock.json 2018-05-17 13:49:24 +01:00
orangemug 51a115d65a Disable spell checking on <input>'s 2018-05-17 13:44:54 +01:00
Orange Mug fc0fbd6a37 Merge pull request #302 from orangemug/feature/terrarium-encoding
Added support for encoding to raster-dem source
2018-05-17 13:42:01 +01:00
orangemug d80d76724c Fixed more lint errors. 2018-05-17 11:46:33 +01:00
orangemug 77da0a6d30 React v16.3.0 fixes. 2018-05-17 11:24:39 +01:00
orangemug 79b251d8b9 DRY up the code. 2018-05-17 10:55:55 +01:00
orangemug 4f19f6a08c Added support for encoding to raster-dem source, enabling terrarium tiles. 2018-05-17 10:44:54 +01:00
Orange Mug d2a6eab1e6 Merge pull request #291 from orangemug/feature/circle-ci-osx-builds
CircleCI OSX builds
2018-05-11 15:50:48 +01:00
Orange Mug c7cf051502 Merge pull request #296 from orangemug/feature/prefers-reduced-motion
Added prefers-reduced-motion support
2018-05-11 15:50:08 +01:00
Orange Mug 6e21503e6b Merge pull request #297 from orangemug/accessibility/larger-color-swatch
Make color swatch larger so its easier to see
2018-05-11 15:49:32 +01:00
orangemug 78d71a4e7e Fixed duplicate definition. 2018-05-11 14:53:06 +01:00
orangemug b8f32d46cf Rename <CollapseReducedMotion/> to <Collapse/> 2018-05-11 14:03:46 +01:00
Orange Mug 443782decf Merge pull request #300 from orangemug/accessibility/react-aria-modal
Added accessible modal via react-aria-modal
2018-05-11 13:54:39 +01:00
orangemug 54e79e5eb8 Added missing data-wd-key attribute. 2018-05-11 11:26:43 +01:00
orangemug 221cd4ffd2 Added accessible modal via react-aria-modal 2018-05-11 10:56:34 +01:00
Orange Mug 354b2fb3cb Merge pull request #298 from orangemug/fix/keyboard-accessible-buttons
Made buttons keyboard accessible
2018-05-11 10:47:11 +01:00
orangemug 7cb2c36ac9 Move accessibility checks into module. 2018-05-11 09:32:57 +01:00
orangemug 11d73595fc Made buttons keyboard accessible. 2018-05-10 16:50:37 +01:00
orangemug c241a6e280 Ignore 'prefers-reduced-motion' in stylelint 2018-05-10 16:30:23 +01:00
orangemug 198ff143f6 Make color swatch larger so its easier to see. 2018-05-10 16:18:13 +01:00
orangemug 7b8b797f9c Fixed typo. 2018-05-10 16:07:34 +01:00
orangemug a41b25eea7 Added 'prefers-reduced-motion' css support. 2018-05-10 16:05:55 +01:00
Orange Mug 06eac68f9d Merge pull request #293 from orangemug/maintenance/update-deps-20180509
Updated deps
2018-05-10 08:33:18 +01:00
orangemug 8abf84ebc0 Updated deps. 2018-05-09 09:39:03 +01:00
orangemug e9aa1f6dd6 Fixed typo 2018-05-08 17:34:09 +01:00
orangemug 8e7b838bf7 Altered versions for node.js release schedule
See <https://github.com/nodejs/Release>
2018-05-08 17:30:42 +01:00
orangemug 32db3c3c9b Added build-osx-node-v9 to CircleCI 2018-05-08 17:23:03 +01:00
orangemug 502586e5d5 1.2.0 2018-05-08 16:11:13 +01:00
Orange Mug d92d599d8a Merge pull request #290 from orangemug/fix/disable-gist
Disable gist export
2018-05-08 15:55:23 +01:00
orangemug 3487056c7d Disable gist, see <https://github.com/maputnik/editor/issues/269> 2018-05-08 15:21:14 +01:00
orangemug dbcfb08c15 1.2.0-beta2 2018-04-20 15:31:00 +01:00
Orange Mug e96141090e Merge pull request #287 from orangemug/fix/beta-version-wrapping
Fix to allow beta version strings to not wrap
2018-04-20 15:27:48 +01:00
orangemug 5bd25fc2ed Fix to allow beta version strings to not wrap. 2018-04-20 15:09:37 +01:00
orangemug 334932b298 1.2.0-beta 2018-04-20 14:53:51 +01:00
Orange Mug 661006d7fb Merge pull request #284 from pjsier/fix/276-null-zoom
Handle data functions without zoom
2018-04-20 14:14:02 +01:00
Orange Mug c917249517 Merge pull request #286 from orangemug/maintenance/update-stylelint
Updated stylelint
2018-04-17 15:56:01 +01:00
orangemug d0ca732fe7 Updated stylelint and fixed scss for 'stylelint-config-recommended-scss' 2018-04-17 14:55:33 +01:00
Orange Mug 52821cd1df Merge pull request #285 from orangemug/maintenance/update-deps-20180417
Updated deps
2018-04-17 12:11:22 +01:00
orangemug 328e0b8ff7 Updated deps. 2018-04-17 11:35:30 +01:00
Orange Mug f0147cc89a Merge pull request #280 from orangemug/fix/web-driver-tests-v8
Improved tests
2018-04-16 20:48:56 +01:00
orangemug 78a7f152e7 Merge remote-tracking branch 'upstream/master' into fix/web-driver-tests-v8
Conflicts:
	src/styles/index.scss
2018-04-16 15:31:27 +01:00
pjsier e936dd16bf Fix style linting error 2018-04-16 07:44:00 -05:00
pjsier 3d4579288c Handle data functions without zoom 2018-04-16 06:59:01 -05:00
Orange Mug b60df8b074 Merge pull request #283 from orangemug/fix/issue-244
Fix to allow layer sections to expand smoothly
2018-04-15 14:56:21 +01:00
orangemug c4b92fa0a9 Updated test instructions in README 2018-04-15 09:17:07 +01:00
orangemug 9808d44c71 Fix to allow layers sections to expand smoothly. Fixes #244 2018-04-13 17:00:51 +01:00
Orange Mug 1bdd135386 Merge pull request #282 from oterral/teo_fixed
Use a fixed position for autocomplete menu
2018-04-13 15:53:27 +01:00
Orange Mug 740a75f2e6 Merge pull request #281 from oterral/master
Block the popup on click in inspect mode
2018-04-13 15:06:28 +01:00
oterral b62533fa3e Use a fixed position for autocomplete menu 2018-04-13 15:55:16 +02:00
oterral 044349e65f Block popup on click in inspect mode 2018-04-13 14:25:08 +02:00
oterral e8b0bd4d0a Update mapbox-gl-inspect dependency 2018-04-13 14:24:39 +02: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
orangemug 1805aee7ba Removed lint-styles in appveyor
It doesn't work in windows and should be addressed in another PR
2018-04-10 16:16:51 +01:00
orangemug 8ba2123a26 Added missing propType. 2018-04-10 15:15:29 +01:00
orangemug 687c08527d Added test docs. 2018-04-10 15:13:55 +01:00
orangemug f0744f024d Moved commit. 2018-04-10 15:07:36 +01:00
orangemug 9e82599464 Removed old comments. 2018-04-10 14:23:11 +01:00
orangemug 7a60df370e Changed url to be local (although not used) 2018-04-10 14:20:13 +01:00
orangemug aee4a041fe Removed node:10 from appveyor 2018-04-10 14:03:09 +01:00
orangemug 6fa06e5483 Removed un-useful comments 2018-04-10 14:02:36 +01:00
orangemug 15962481ee Disable OSX until we get a open source plan for maputnik/editor 2018-04-10 13:35:02 +01:00
orangemug 6bf695cd4b Removed linux from travis. CircleCI now takes care of that 2018-04-10 13:34:20 +01:00
orangemug 7ecbc14c39 Added OSX build to tests. 2018-04-10 13:29:48 +01:00
orangemug fb0e531f4a Removed node:10 as it doesn't exist yet. 2018-04-10 13:17:59 +01:00
orangemug bd44e6d071 Fixed typo. 2018-04-10 13:11:42 +01:00
orangemug 3ae37f1c46 Updated appveyor to no longer test, only build/lint 2018-04-10 13:08:55 +01:00
orangemug 8c7a1f7075 Updated build config for circleci to only test webdriver in one job 2018-04-10 13:05:58 +01:00
orangemug 3e97d8a5f1 Merge remote-tracking branch 'upstream/master' into fix/web-driver-tests-v8 2018-04-10 12:56:43 +01:00
orangemug 6138257a89 Remove logging. 2018-04-10 12:52:59 +01:00
orangemug 0bd62985b9 Revert change to undo/redo 2018-04-10 12:45:44 +01:00
orangemug a346d757fd Don't assume docker for mac. 2018-04-09 18:18:15 +01:00
orangemug 84f3970730 Updated selenium-standalone & webdriverio 2018-04-09 17:51:12 +01:00
orangemug 050e22918a Fix for running within docker. 2018-04-09 17:49:56 +01:00
Orange Mug f205776695 Merge pull request #277 from maputnik/revert-275-maintenance/update-ol-mapbox-style
Revert "Update ol-mapbox-style ^2.10.1 -> ^2.11.2"
2018-04-09 13:54:12 +01:00
Orange Mug 4d427bcbc3 Revert "Update ol-mapbox-style ^2.10.1 -> ^2.11.2" 2018-04-09 13:53:25 +01:00
Orange Mug 0b4910e3c3 Merge pull request #275 from orangemug/maintenance/update-ol-mapbox-style
Update ol-mapbox-style ^2.10.1 -> ^2.11.2
2018-04-09 12:16:48 +01:00
orangemug 11a59debdf Update ol-mapbox-style ^2.10.1 -> ^2.11.2 2018-04-09 11:10:46 +01:00
orangemug dbe2c2637e Better onPrepare for wdio 2018-04-09 10:20:37 +01:00
Orange Mug d6ce13c356 Merge pull request #273 from cmarqu/patch-1
Fix small typo.
2018-04-09 09:42:46 +01:00
Orange Mug 6d094a8b3e Merge pull request #271 from ziveo/master
Adding mac keyboard bindings
2018-04-09 09:37:32 +01:00
Colin Marquardt 4d0456fd68 Fix small typo. 2018-03-27 00:45:42 +02:00
ziveo ad83f940a7 Merge branch 'master' into master 2018-03-18 20:02:20 -04:00
ziveo edc7e02f58 Merge pull request #2 from ziveo/develop__mac-keyboard-bindings
Improving keyboard bindings code
2018-03-16 23:01:34 -04:00
Bojan Zivkovic 7dfc5029a3 Improving keyboard bindings code 2018-03-16 23:00:33 -04:00
ziveo 8e02722b52 Merge pull request #1 from ziveo/develop__mac-keyboard-bindings
Adding mac keyboard bindings
2018-03-15 23:41:48 -04:00
Bojan Zivkovic 984581e01a Adding mac keyboard bindings 2018-03-15 23:39:32 -04:00
orangemug 1de7ba7e86 Use dev settings for test. 2018-03-06 21:11:58 +00:00
orangemug a3fa86f7ee Merge remote-tracking branch 'upstream/master' into fix/web-driver-tests-v7
Conflicts:
	config/webpack.production.config.js
	package-lock.json
	package.json
2018-03-06 07:22:26 +00:00
Orange Mug a589f89c4c Merge pull request #268 from orangemug/fix/update-mapbox-gl-inspect
Updated mapbox-gl-inspect to v1.3.0
2018-02-19 09:40:53 +00:00
orangemug 3b599aed4c Updated mapbox-gl-inspect to v1.3.0 2018-02-19 08:28:08 +00:00
Orange Mug 6953db74c6 Merge pull request #266 from orangemug/maintenance/added-circle-symbol-pitch-alignment-paint-props
Added [symbol|circle]-pitch-alignment props
2018-02-18 17:59:13 +00:00
Orange Mug 1ad473a539 Merge pull request #267 from orangemug/feature/heatmap
Added heatmap layer support
2018-02-18 16:34:42 +00:00
orangemug fafda9ec92 Merge remote-tracking branch 'upstream/master' into maintenance/added-circle-symbol-pitch-alignment-paint-props 2018-02-18 15:00:22 +00:00
Orange Mug 11b85bf565 Merge pull request #263 from orangemug/feature/hillshading
Added hillshading support
2018-02-18 14:53:58 +00:00
orangemug 6ecc6670dc Added [symbol|circle]-pitch-alignment paint props 2018-02-18 13:23:04 +00:00
orangemug 553f0fe23e Drop support for 'heatmap-color'
See <https://github.com/maputnik/editor/issues/265#issuecomment-366511333>
2018-02-18 12:07:34 +00:00
orangemug 77ddf67201 Added heatmap layer type. 2018-02-18 11:50:04 +00:00
orangemug a092bc2689 Moved to using orangemug/mapbox-gl-inspect#fix/only-vector-sources
While <https://github.com/lukasmartinelli/mapbox-gl-inspect/pull/11> is
waiting to be merged/released.
2018-02-18 11:22:01 +00:00
orangemug 38e0786463 Added missing hillshade / raster-dem guards. 2018-02-17 07:45:24 +00:00
orangemug 180b17d315 Fixed typo raster -> raster-dem 2018-02-16 20:34:50 +00:00
orangemug 8acbd784a0 Added hillshading support. 2018-02-16 19:52:19 +00:00
Orange Mug 07efe1e1b8 Merge pull request #253 from orangemug/maintenance/openlayers-update
Updated openlayers
2018-02-07 22:56:01 +00:00
orangemug 7ea53cc3a1 Increased build timeout. 2018-02-07 11:38:32 +00:00
orangemug de21eea21b Some modules aren't ES5 so we much compile them 2018-02-07 11:00:24 +00:00
orangemug 8f8ed6dff3 Changed to uglifyjs-webpack-plugin for es2015 support. 2018-02-06 10:50:15 +00:00
orangemug 8915bbfeb4 Updated openlayers.
openlayers^4.4.2 -> ol^4.6.4
ol-mapbox-style^1.0.1 -> ol-mapbox-style^2.10.1

Fixes #246
2018-02-06 08:28:57 +00:00
Orange Mug df3a42acce Merge pull request #241 from orangemug/feature/private-public-gist
Public/private gists
2018-02-03 15:43:12 +00:00
Orange Mug 2a7ef82d23 Merge pull request #248 from orangemug/feature/nsp
Added nsp (node security project)
2018-02-03 15:41:42 +00:00
orangemug 95168f22e3 Added nsp 2018-02-03 15:30:29 +00:00
Orange Mug 4360753263 Merge pull request #242 from orangemug/feature/update-mapbox-gl-v0.44.0
Updated mapbox-gl 0.43.0 -> 0.44.0
2018-02-03 14:02:45 +00:00
Orange Mug ad491cb465 Merge pull request #240 from orangemug/fix/do-not-expose-fallback-tokens
Do not expose fallback tokens during export
2018-02-03 14:02:06 +00:00
orangemug e5bed80c96 Updated mapbox-gl 0.43.0 -> 0.44.0. Fixes #237 2018-02-02 18:04:57 +00:00
orangemug 9bf3046d4c Public/private gists added. Fixes #238
Gists are now private by default with a option for public.
2018-02-02 17:23:21 +00:00
Orange Mug da8dc0f7a6 Merge pull request #231 from justenPalmer/issues
Issue 229: Adding a style without Glyphs defined throws an exception with no feedback in interface
2018-02-02 16:43:18 +00:00
orangemug b66a4afd28 Do not expose fallback tokens during export. Fixes #230 2018-02-02 15:33:15 +00:00
Orange Mug a94c53534c Merge pull request #235 from orangemug/feature/export-token-fix
Fixes for export to add in mapbox access token
2018-02-02 11:47:41 +00:00
Orange Mug 6b22c9130f Merge pull request #236 from orangemug/fix/issue-234
Added guard in fetchSources
2018-02-02 11:24:23 +00:00
orangemug 7d5927bbc8 Added additional guard
As checking the key name is 'openmaptiles' isn't a guarantee
2018-02-01 22:00:26 +00:00
jPalmer 240d02a124 Merge branch 'master' of https://github.com/maputnik/editor into issues 2018-02-01 13:44:23 -08:00
jPalmer 92ef1c4cbb added more robust handling of glyphs in styles - addresses #229 2018-02-01 13:44:15 -08:00
orangemug 5ce57d0803 Added guard in fetchSources.
This will mean that autocomplete is broken for sources without vector_layers key present.
2018-02-01 21:37:17 +00:00
orangemug 1c134d757c Fixes for export to add in mapbox access token. 2018-02-01 19:54:44 +00:00
Orange Mug 32d808b230 Merge pull request #233 from orangemug/feature/version-in-ui
Added version number to the UI
2018-02-01 08:10:21 +00:00
orangemug ee3def492a Fixed toolbar version position. 2018-01-31 21:36:47 +00:00
orangemug 41bd91fcd2 Center the toolbar button text. 2018-01-31 21:22:12 +00:00
orangemug 02c8542848 Added version number to the UI. Fixes #232 2018-01-31 21:04:49 +00:00
jPalmer 844abd38ce added missing glyphs property check on styleChanged - addresses #229 2018-01-31 11:28:09 -08:00
jPalmer d9b6f28bb5 added missing glyphs property check on styleChanged - addresses #229 2018-01-31 11:26:10 -08:00
orangemug ed85b838ec v1.1.0 2018-01-30 20:57:24 +00:00
Orange Mug f82b138a3d Merge pull request #228 from orangemug/fix/source-layer-guard
Added guard to <LayerSourceLayerBlock/> sourceLayerIds
2018-01-30 17:49:41 +00:00
orangemug 89c38991b9 Added guard to <LayerSourceLayerBlock/> sourceLayerIds 2018-01-29 17:18:30 +00:00
orangemug 0e4c06cc3e Merge remote-tracking branch 'origin/master' 2018-01-26 15:33:01 +00:00
orangemug 7e510a2582 v1.1.0-beta4 2018-01-26 15:31:33 +00:00
Orange Mug f3cb9c4fdd Merge pull request #227 from orangemug/fix/mapbox-urls
Added support for mapbox:// urls to fetchSources
2018-01-26 12:37:56 +00:00
orangemug f0f6130272 Fixed typo. 2018-01-25 19:58:01 +00:00
orangemug 0ebb299fd0 Added try/catch around mapboxUtil.normalizeSourceURL 2018-01-25 19:40:54 +00:00
orangemug 9d96525f12 Added support for mapbox:// urls. 2018-01-25 19:16:06 +00:00
Orange Mug fc6f9251f7 Merge pull request #226 from orangemug/fix/modal-safari
Fix for safari modal appearing below the overlay
2018-01-25 18:17:18 +00:00
orangemug 53cb317155 Fix for safari modal appearing below the overlay. Fixes #225 2018-01-25 08:30:23 +00:00
orangemug 4215b5808f Merge remote-tracking branch 'upstream/master' into fix/web-driver-tests-v6-circleci-config
Conflicts:
	package-lock.json
	src/components/inputs/AutocompleteInput.jsx
2018-01-22 09:57:54 +00:00
orangemug 2d2f9744e2 v1.1.0-beta3 2018-01-22 09:13:42 +00:00
Orange Mug d0b835ee52 Merge pull request #222 from gregorywolanski/95
Source change handling bug fix (#95)
2018-01-22 08:52:28 +00:00
Grzegorz Wolański 1798305f9c Source change handling bug fix (#95) 2018-01-22 08:49:39 +01:00
Orange Mug 4b0768d0a6 Merge pull request #221 from orangemug/fix/added-guard-to-get-sources
Added guard to getSources
2018-01-21 19:08:10 +00:00
orangemug 2e79a8ff4c Added guard to getSources 2018-01-20 09:39:18 +00:00
orangemug e64ca3eb93 Added back in other jobs. 2018-01-19 18:15:46 +00:00
orangemug 094c4747d3 Update selenium/standalone-chrome to 3.8.1 2018-01-19 18:02:08 +00:00
orangemug 62f0843283 Moved back to workflows. 2018-01-19 17:55:37 +00:00
orangemug 8062e304b7 Update selenium/standalone-chrome 2018-01-19 17:51:08 +00:00
orangemug 18e7ead78a Revert to old config. 2018-01-19 17:35:19 +00:00
orangemug 3cab1dc49f Remove special directory. 2018-01-19 17:29:55 +00:00
orangemug f8dcbb8fb7 Reduce to single job. 2018-01-19 17:24:29 +00:00
orangemug c82f38c103 Multiple working directories for test versions. 2018-01-19 17:14:56 +00:00
orangemug fe0e7af033 Added multiple nodejs versions. 2018-01-19 15:13:46 +00:00
orangemug ac51902435 Added missing workflow to .circleci/config.yml 2018-01-19 15:08:45 +00:00
orangemug e0ff342702 Added yaml inheritance to .circleci/config.yml 2018-01-19 15:06:23 +00:00
orangemug 664125d820 v1.1.0-beta2 2018-01-19 13:56:38 +00:00
Orange Mug 9ae2f2c5af Merge pull request #219 from orangemug/fix/autocomplete-menu-issues
Autocomplete fixes
2018-01-19 13:45:38 +00:00
orangemug 721f9b36b3 Added missing shouldItemRender to <Autocomplete/>. #219 2018-01-19 12:11:34 +00:00
orangemug a33d1b819c Autocomplete fixes #218 2018-01-19 11:58:25 +00:00
orangemug cb4f5ea963 Updated to react/react-dom v16.2.0 2018-01-18 23:15:59 +00:00
Orange Mug 3c0ebfabab Merge pull request #213 from gregorywolanski/60
Clickable layer tooltips (#60)
2018-01-18 23:03:19 +00:00
orangemug a822430e1d Merge remote-tracking branch 'upstream/master' into fix/web-driver-tests-v6
Conflicts:
	package-lock.json
2018-01-18 22:59:59 +00:00
Gregory Wolanski 0ba11b94c8 Merge branch 'master' into 60 2018-01-18 23:58:06 +01:00
Orange Mug 390e90e8c2 Merge pull request #209 from gregorywolanski/41
Remove duplicated features from popups (#41)
2018-01-18 22:40:51 +00:00
orangemug 59ef8eb4e4 v1.1.0-beta 2018-01-18 22:19:05 +00:00
Orange Mug 2b382a9946 Merge pull request #217 from orangemug/pr-214
Updated to mapbox-gl@0.43.0
2018-01-18 21:48:31 +00:00
orangemug d52d55dd6a Updated to mapbox-gl-js v0.43.0. Fixes issue #212 2018-01-18 21:22:18 +00:00
orangemug dc40ce7d9e Fixed lint errors. 2018-01-17 17:58:01 +00:00
orangemug 383a119127 Added linting to circleci tests. 2018-01-17 17:55:21 +00:00
orangemug 3f492e6208 Change artifacts destination. 2018-01-17 17:43:35 +00:00
orangemug 0cec0cf595 Fix coverage in tests. 2018-01-17 17:36:46 +00:00
orangemug bc19aea438 CircleCI test now just calls npm test 2018-01-17 17:01:55 +00:00
orangemug 211850c813 Added cross-env 2018-01-17 16:53:42 +00:00
orangemug c1312fb288 Added '/build' to .gitignore 2018-01-17 15:46:48 +00:00
orangemug 0c2934c489 Code to store artifacts on circle ci 2018-01-17 15:44:00 +00:00
orangemug ad34147f28 Fixed screenshots. 2018-01-17 15:39:17 +00:00
orangemug 1eb6c28617 Removed logging. 2018-01-17 14:59:48 +00:00
orangemug 2e8a188bce Increased timeouts. 2018-01-17 14:51:25 +00:00
Tobin Bradley ed495c3216 Update export to use GL JS 0.43.0
Fixes #212 broken bl.ocks preview.
2018-01-12 11:37:05 -05:00
orangemug a773958403 Tidy tests. 2018-01-10 15:06:11 +00:00
Grzegorz Wolański 6a6595d971 Clickable layer tooltips (#60) 2018-01-08 22:18:30 +01:00
orangemug 942b2240a7 Added more webdriver tests testing against a real browser. 2018-01-05 17:45:55 +00:00
Orange Mug 6e86c60f89 Merge pull request #185 from gregorywolanski/130
Add expand/collapse all layer groups feature (#130)
2018-01-04 15:52:38 +00:00
Grzegorz Wolański ace6812e89 Remove duplicated features from popups (#41) 2017-12-05 23:27:17 +01:00
Grzegorz Wolański 604fa6317c Expand/Collapse button position bug fix 2017-11-30 20:40:56 +01:00
Orange Mug 4479473b37 Merge pull request #207 from orangemug/fix/update-codemirror
Update to react-codemirror2
2017-11-30 09:14:54 +00:00
Orange Mug 4dc8fc9696 Merge pull request #205 from orangemug/fix/fetch-sources-get-called-on-each-change
Fix to stop fetchSources getting called on each change
2017-11-30 07:31:32 +00:00
Orange Mug bac59d595d Merge pull request #204 from orangemug/fix/catch-invalid-color
Catch invalid color
2017-11-29 21:58:06 +00:00
Orange Mug ed98db8ae3 Merge pull request #199 from orangemug/fix/issue-97-layer-list-cutoff
Fixed layer list cutoff (#97)
2017-11-29 19:30:19 +00:00
Orange Mug b66eb66358 Merge pull request #201 from orangemug/fix/lighthouse-errors
Google lighthouse audit fixes
2017-11-29 19:29:23 +00:00
orangemug 934a994ac5 Update to react-codemirror2
react-codemirror is no longer maintained
2017-11-29 19:22:41 +00:00
orangemug 199a989f7d Fix to stop fetchSources getting called on each change. 2017-11-29 18:13:47 +00:00
orangemug a50b09e5a2 Tidy logic. 2017-11-29 18:07:03 +00:00
orangemug b20c69b15a Catch invalid color during parse. 2017-11-29 18:00:52 +00:00
Yuri Astrakhan 25be173487 Merge pull request #200 from orangemug/fix/issue-116-zoom-field-v3
Fix zoom field input ordering (#116)
2017-11-29 10:50:18 -05:00
orangemug 61808d5939 Fixed lint errors. 2017-11-29 15:03:37 +00:00
orangemug de24227b1f Updated package-lock.json 2017-11-29 11:34:08 +00:00
orangemug 1f5608ec77 Added manifest.json to production webpack config. 2017-11-29 11:29:48 +00:00
orangemug 2d87e162f1 Merge remote-tracking branch 'upstream/master' into fix/lighthouse-errors 2017-11-29 11:14:54 +00:00
orangemug 1941fdf8a0 Merge remote-tracking branch 'upstream/master' into fix/issue-116-zoom-field-v3 2017-11-29 10:56:56 +00:00
orangemug 33fdc52667 Added MAX_HEIGHT constant. 2017-11-29 10:29:11 +00:00
orangemug e11a5a823a Only limit AutoComplete to window bounds if element is fixed. 2017-11-29 10:20:07 +00:00
orangemug b60d101d42 Fixed PropTypes typo. 2017-11-29 10:19:22 +00:00
orangemug 5e9263b787 Merge remote-tracking branch 'upstream/master' into fix/issue-97-layer-list-cutoff 2017-11-29 10:09:56 +00:00
Orange Mug 949bd783f5 Merge pull request #195 from orangemug/feature/webpack-bundle-analyzer
Webpack bundle analyzer
2017-11-28 08:58:23 +00:00
Orange Mug 7fe3137fd0 Merge pull request #196 from orangemug/fix/source-layer-autocomplete
Improved source/layer autocomplete
2017-11-28 08:57:24 +00:00
orangemug 3c97fbe587 tabs -> spaces. 2017-11-17 17:27:16 +00:00
orangemug 030d469d7c Broke <FunctionSpecField/> into smaller parts. 2017-11-17 17:17:53 +00:00
orangemug 135ef8ed89 Merge remote-tracking branch 'upstream/master' into fix/issue-116-zoom-field-v2 2017-11-17 13:23:48 +00:00
orangemug 002e9c4647 Fix for new sources definition. 2017-11-17 13:06:26 +00:00
orangemug a4fbe55012 Added type to sources list and now filtering in modal autocomplete. 2017-11-17 11:43:56 +00:00
orangemug 63ac707415 Call fetchSources after component mount. 2017-11-17 10:53:46 +00:00
orangemug b5dc04bb4f Merge remote-tracking branch 'upstream/master' into fix/source-layer-autocomplete 2017-11-17 10:41:23 +00:00
orangemug f3ae20f3aa Updated package-lock. 2017-11-17 10:26:49 +00:00
orangemug 1838b8aefd Merge remote-tracking branch 'upstream/master' into feature/webpack-bundle-analyzer
Conflicts:
	config/webpack.production.config.js
2017-11-17 10:22:28 +00:00
Orange Mug e9c65e1ada Merge pull request #193 from orangemug/fix/tooltip-pointer-events
Disable pointer-events on doc tooltips
2017-11-16 16:20:52 +00:00
orangemug 9ea5d213f7 Merge remote-tracking branch 'upstream/master' into fix/tooltip-pointer-events 2017-11-16 13:06:40 +00:00
Orange Mug 7dcd6d5552 Merge pull request #192 from orangemug/fix/update-deps
Updated dependencies
2017-11-16 13:04:56 +00:00
orangemug 0de8f2d633 Moved node.js test versions to match latest LTS
https://github.com/nodejs/Release
2017-11-15 14:45:50 +00:00
orangemug cb2f854dd5 Sub-dependencies broke their API without a major version bump.
This fixes those errors and adds a package-lock.json
2017-11-15 14:29:56 +00:00
orangemug 401c920e47 Fix to keep autocomplete menu within window bounds. 2017-11-08 15:44:43 +00:00
orangemug 40235fe473 Initial work to reorder zoom fields. 2017-11-08 13:45:34 +00:00
orangemug a76e08aee7 Fixed source layer autocomplete to fetch from the sources json definition 2017-11-08 11:11:36 +00:00
orangemug dfe7282510 Fixed some errors reported in google lighthouse audit tool. 2017-11-08 10:13:02 +00:00
orangemug 3aae2e976f Merge remote-tracking branch 'upstream/master' into fix/update-deps
Conflicts:
	src/components/Toolbar.jsx
2017-11-08 09:01:35 +00:00
orangemug f79a945fa4 Fixed more eslint errors. 2017-11-08 08:51:24 +00:00
orangemug 8234c51412 Fixed eslint errors. 2017-11-08 08:47:36 +00:00
orangemug f464f997d1 Added 'pointer-events: none' to prevent tooltips from overlapping the UI. 2017-11-07 18:29:55 +00:00
orangemug e0b7cdf9dd Removed nodejs v4 tests temporarily 2017-11-07 18:20:35 +00:00
orangemug a819154145 Moved to testing from webpack.production.config also added verbose logging. 2017-11-07 18:13:06 +00:00
Orange Mug 616f45c586 Merge pull request #191 from orangemug/fix/console-errors
Fixed startup errors/warnings
2017-11-07 15:05:16 +00:00
orangemug 203aaf51b7 Removed old plugins from webpack config. 2017-11-07 11:50:00 +00:00
orangemug 392d1fe26d Added webpack-bundle-analyzer 2017-11-07 11:48:01 +00:00
orangemug f452ea0d26 Fixed webpack production conf after updated deps.
See <https://webpack.js.org/guides/migrating/#occurrenceorderplugin-is-now-on-by-default>
2017-11-07 11:35:46 +00:00
Orange Mug 97dbb74486 Merge pull request #189 from orangemug/feature/disable-polling-by-default
Disable polling by default
2017-11-07 11:15:50 +00:00
Orange Mug 1f80cfcaa6 Merge pull request #190 from orangemug/feature/overflow-toolbar-actions
Added css overflow scroll to toolbar actions
2017-11-07 11:14:16 +00:00
orangemug 5d0fbabb6a Updated mocha dep. 2017-11-07 11:07:44 +00:00
orangemug b5ca0fa17b Updated webpack deps. 2017-11-07 11:05:30 +00:00
orangemug 41e1704d08 Updated extract-text-webpack-plugin 2017-11-07 10:56:08 +00:00
orangemug d4569237f5 Changed es2015 to env in babelrc. 2017-11-07 10:55:25 +00:00
orangemug b6ae51b5e5 Updated eslint deps. 2017-11-07 10:52:02 +00:00
orangemug 3015ba605d Switched to babel-preset-env from babel-preset-es2015
See <http://babeljs.io/env>
2017-11-07 10:48:15 +00:00
orangemug eb589d4039 Updated the webpack loader deps. 2017-11-07 10:44:52 +00:00
orangemug 271190f434 Updated more babel deps.
babel-loader fixed at 7.1.1 due to <https://github.com/babel/babel-loader/issues/505>
2017-11-07 10:41:40 +00:00
orangemug 0836790daf Upgraded babel dev deps. 2017-11-07 10:34:08 +00:00
orangemug b3b665fcb9 Bumped react/react-dom deps. 2017-11-07 10:25:24 +00:00
orangemug c050b02b8b Updated '@mapbox/mapbox-gl-style-spec' 2017-11-07 10:21:39 +00:00
orangemug a791403a6a Updated deps fixed for clean install. 2017-11-07 10:11:42 +00:00
orangemug a4c6a18353 Updated react-sortable-hoc to 0.6.8 to remove prop-types warnings. 2017-11-06 15:35:29 +00:00
orangemug 9bc603a510 Update to use prop-types module in components. 2017-11-06 15:32:04 +00:00
orangemug af25fb926b Bumped react-codemirror to 1.0.0 2017-11-06 15:12:51 +00:00
orangemug 365a0518a5 Removed the console.warn because the logging wasn't helpful. 2017-11-06 15:05:00 +00:00
orangemug 9801f49f4e Added noParse for prebuilt openlayers and mapbox-gl modules. 2017-11-06 14:58:23 +00:00
orangemug bb4f3482ad Removed required from minzoom/maxzoom as it can be undefined.
See <https://www.mapbox.com/mapbox-gl-js/style-spec/#layer-minzoom>
2017-11-06 14:45:03 +00:00
orangemug e148607c7a Removed required prop and fixed component name. 2017-11-06 14:13:45 +00:00
orangemug ae370f04c1 Added css overflow scroll to toolbar actions. 2017-11-06 10:23:51 +00:00
orangemug 89f6343abd Removed ignoring node_modules in webpack watch 2017-11-04 14:24:36 +00:00
orangemug ea55687171 Added note to the docs regarding WEBPACK_DEV_SERVER_POLLING 2017-11-03 11:12:23 +00:00
orangemug da0b4d7911 Disable webpack-dev-server polling by default. 2017-11-03 11:04:15 +00:00
Orange Mug e303283098 Merge pull request #187 from orangemug/feature/more-badges
Added more badges
2017-11-02 13:01:30 +00:00
orangemug 1119ff06c9 Added more badges. 2017-11-01 16:54:17 +00:00
Orange Mug adc8ed26c1 Merge pull request #179 from gregorywolanski/87
Display zoom level #87
2017-10-30 08:00:34 +00:00
Gregory Wolanski 06554b83dc Add expand/collapse all layer groups feature (#130) 2017-10-25 21:35:19 +02:00
Gregory Wolanski 06ea1d1697 Display zoom level #87 2017-10-19 20:41:38 +02:00
Orange Mug ddb3bcde43 Merge pull request #178 from pjsier/fix/layer-function-type
Assigning default function type from spec
2017-10-18 17:31:13 +01:00
Orange Mug db2f9efb93 Merge pull request #176 from orangemug/fix/noopener-noreferrer
Added rel="noopener noreferrer" to external links.
2017-10-17 16:43:48 +01:00
pjsier d32b15d425 Assigning default function type from spec 2017-10-16 14:19:19 -05:00
Orange Mug a67f9b2edb Merge pull request #175 from orangemug/feature/change-link-to-new-domain
Changed link from maputnik.com -> maputnik.github.io
2017-10-16 19:48:13 +01:00
orangemug c38547d4e7 Removed {} from props. 2017-10-16 15:18:29 +01:00
orangemug 3f350c30da Added rel="noopener noreferrer" to external links. 2017-10-16 15:01:35 +01:00
Orange Mug d502d9b1bb Merge pull request #174 from pjsier/feature/share-style-link
Add share style link, copy button
2017-10-16 14:55:12 +01:00
orangemug 06e1be716e Changed link from maputnik.com -> maputnik.github.io 2017-10-16 09:34:05 +01:00
pjsier cda855f1b7 Add share style link, copy button
This adds a copy to clipboard button and input with the style parameter
pre-populated after exporting a style to an anonymous gist. Also
includes the URL as an input next to the button.
2017-10-15 20:04:43 -05:00
Orange Mug 36def799c0 Merge pull request #172 from pjsier/fix/insecure-assets
Fix insecure asset loading
2017-10-14 20:08:19 +01:00
pjsier 2e671250b9 Remove tilezen tileset and style 2017-10-12 15:13:57 -05:00
pjsier c881534554 Fix insecure asset loading 2017-10-12 14:33:24 -05:00
Orange Mug e1f7336aa9 Merge pull request #166 from orangemug/feature/load-from-url
Added 'Load from URL' option in open modal
2017-10-12 19:14:31 +01:00
orangemug aa92e9da02 Merge remote-tracking branch 'upstream/master' into feature/load-from-url 2017-10-12 15:15:20 +01:00
Orange Mug 232b48ff62 Merge pull request #165 from gregorywolanski/master
Modal scrolling #156
2017-10-12 10:29:07 +01:00
Grzegorz Wolański a95b2932db Modal scrolling #156: Issue fixed 2017-10-11 23:11:40 +02:00
Orange Mug aa288a1e11 Merge pull request #161 from pjsier/feature/data-driven-styles
Add data-driven styling
2017-10-11 16:55:04 +01:00
Orange Mug 7e6efcb9b9 Merge pull request #169 from orangemug/feature/comments-field-docs
Added comments field doc and updated react-collapse to fix styling
2017-10-11 14:37:43 +01:00
Orange Mug 817d0a7e63 Merge pull request #170 from orangemug/fix/maputnik-logo-stretch
Fixes logo styling in toolbar
2017-10-11 14:34:43 +01:00
pjsier fa0067ce7b Update mapbox deps, clarify data prop scope 2017-10-11 08:01:55 -05:00
orangemug 9beacf7ef3 Fixed image path in test 2017-10-11 13:16:04 +01:00
pjsier b4292028c2 Fix default field bug 2017-10-11 05:58:32 -05:00
orangemug d7c099bcbb Fixed logo stying in toolbar, also switched to the logo in github:maputnik/design. 2017-10-11 11:17:02 +01:00
Orange Mug 36cd15f4f1 Merge pull request #168 from orangemug/feature/issue-114-boolean-types
Added true/false conversion to filter field
2017-10-11 10:27:43 +01:00
orangemug 92ff1a8499 Added comments field doc and updated react-collapse to fix styling. 2017-10-10 22:34:16 +01:00
pjsier 4af7a71220 Rename ZoomSpecField to FunctionSpecField 2017-10-10 14:23:20 -05:00
orangemug 611e170b5e Added true/false conversion to filter fields. Fixes #114 2017-10-10 18:41:54 +01:00
pjsier 148f64c261 Restrict data function types, reorder buttons
Checking the Mapbox style spec properties to see whether or not
exponential should be allowed as the property type, defaulting to
categorical which appears to work for either type. Also re-orders zoom
and data function buttons, aligning zoom right if data not supplied.
2017-10-10 10:30:06 -05:00
orangemug 2c3f47d3cb Added 'Load from URL' option in open modal. Fixes #120 2017-10-10 16:27:16 +01:00
Gregory Wolanski 8a6e24e5e7 Modal scrolling #156 2017-10-08 21:42:04 +02:00
pjsier 1d29f67065 Check for property-function support on data styles 2017-10-05 06:08:55 -05:00
pjsier 2ffb3e73e1 Re-add default field after style update 2017-10-05 05:50:47 -05:00
pjsier bba7aa3177 Merge branch 'master' into feature/data-driven-styles 2017-10-05 05:42:17 -05:00
Orange Mug c950a33031 Merge pull request #124 from orangemug/fix/issue-110-update-mapbox-style-spec
Update style spec
2017-10-05 06:16:04 +01:00
Orange Mug c9ab3bdbfc Merge pull request #163 from chriswhong/patch-1
Add related projects section
2017-10-05 06:00:36 +01:00
Chris Whong e32c2e865c Add 2017-10-04 22:42:04 -04:00
pjsier 9e52b0b7dc Remove default from data properties
It looks like default is not supported in this version of the style
spec, so pending the PR to update it I'm removing it as an input.
2017-10-01 21:07:55 -05:00
pjsier d731fb2cae Fix scss linter errors 2017-10-01 19:17:43 -05:00
pjsier e057fcaea1 Add data-driven styling to editor
Builds off of the ZoomSpecField component with separate options for
handling data-driven properties. Reuses most of the zoom field
functionality with tweaks that I tried to keep as small as possible, and
the layout is based off of comments on the existing issue.
2017-10-01 18:20:57 -05:00
Orange Mug fff1363134 Merge pull request #152 from orangemug/feature/circle-ci-artifact-builds
Per branch / commit builds
2017-07-22 13:43:47 +01:00
orangemug 4bbfe1040e Fixed regexp for windows. 2017-07-11 06:30:37 +01:00
Orange Mug bc6e2dc81b Merge pull request #149 from bartvde/proptype-warnings
Make sure propTypes accept what gets passed in
2017-07-07 09:07:25 +01:00
orangemug 0005698c10 Added build to post test step. 2017-07-06 19:59:12 +01:00
orangemug 53711966d2 Added circleci build artifacts. 2017-07-06 19:53:39 +01:00
Orange Mug d3b991aad4 Merge pull request #146 from bartvde/babel-preset
Add transform-class-properties plugin to babel config
2017-07-06 19:34:26 +01:00
Orange Mug 4ef19c321d Merge pull request #151 from bartvde/key-warning
Put key on the right element
2017-07-05 19:32:28 +01:00
bartvde a3e3b9dfe3 Put key on the right element 2017-06-29 10:20:24 -05:00
bartvde abbce3e9d1 Make sure propTypes accept what gets passed in 2017-06-26 16:54:46 -05:00
bartvde 0edbfd89ff Add transform-class-properties plugin to babel config 2017-06-26 16:24:07 -05:00
Orange Mug 040d585d57 Merge pull request #133 from tschaub/loader
Animated loading indicator
2017-06-23 17:46:05 +01:00
Orange Mug c74ef7b0d3 Merge pull request #134 from tschaub/close-open-dialog
Close the "open" dialog after choosing a new style
2017-06-23 17:38:07 +01:00
Orange Mug 23ef937100 Merge pull request #139 from tbarsballe/dynamic-arrays
Add DynamicArrayInput for handling variable-length array fields
2017-06-23 17:35:42 +01:00
Orange Mug 5157742009 Merge pull request #141 from tbarsballe/autocomplete-style
Improve autocomplete styling
2017-06-23 17:22:02 +01:00
Orange Mug 96d96edc9e Merge pull request #145 from bartvde/style-warning
Prevent warning for ColorField
2017-06-23 17:03:53 +01:00
Orange Mug 2a10edcc25 Merge pull request #144 from bartvde/babelrc
Move babel config out of package.json
2017-06-23 16:47:47 +01:00
bartvde e4477db413 Prevent warning for ColorField 2017-06-23 16:00:43 +02:00
bartvde b32d926b56 Move babel config out of package.json 2017-06-23 15:47:15 +02:00
Lukas Martinelli 6b3b5a8b6f Merge pull request #142 from tbarsballe/ol
Improved OpenLayers support
2017-06-09 13:19:36 -04:00
Tim Schaub a7df8afd6e Animated loading indicator 2017-06-02 15:12:21 -06:00
Tim Schaub b8205f4c38 Close the "open" dialog after choosing a new style 2017-06-02 15:11:29 -06:00
Torben Barsballe 2adb1bf917 Add DynamicArrayInput for handling variable-length array fields 2017-05-29 14:32:05 -07:00
Torben Barsballe 2825dd7e04 Improve autocomplete styling
Use "position: absolute" so that autocomplete follows the field when you scroll.
2017-05-29 13:22:50 -07:00
Torben Barsballe df04064e81 Improved OpenLayers support
Added support for gejson sources
Fix toVectorLayer map callback (this was undefined)
Improved ol css; show map controls
2017-05-29 13:18:09 -07:00
Lukas Martinelli 0555fc48ad Merge pull request #140 from tbarsballe/ci-fix
Update react-collapse to ^4.0.2
2017-05-26 14:10:53 -04:00
Torben Barsballe cd425bd26d Update react-collapse to ^4.0.2 2017-05-26 09:30:27 -07:00
Orange Mug a98444b4e7 Merge pull request #135 from orangemug/fix/react-height-peer-dep
Update react-height peer dependency version
2017-04-29 13:59:40 +01:00
orangemug 31d05cefbe Update react-height peer dependency version. 2017-04-28 18:39:56 +01:00
orangemug c552838fdd Merge remote-tracking branch 'upstream/master' into fix/issue-110-update-mapbox-style-spec
Conflicts:
	package.json
2017-04-13 08:26:25 +01:00
orangemug 45942e604b Updated @mapbox/mapbox-gl-style-spec to v9 2017-04-13 08:24:49 +01:00
Lukas Martinelli 9b1dd44b9d Merge pull request #128 from orangemug/feature/rtl-plugin-issue-126
Added @mapbox/mapbox-gl-rtl-text plugin
2017-04-11 16:42:17 -04:00
orangemug df56faa55a Added missing file-loader dep. 2017-04-11 20:54:12 +01:00
orangemug 14cdeae3eb Switch to using object urls so we don't need ajax for mapbox-gl-rtl-text plugin (issue #126) 2017-04-11 17:29:28 +01:00
orangemug f97d2b0e88 Added @mapbox/mapbox-gl-rtl-text plugin (fixes #126) 2017-04-11 08:18:29 +01:00
Lukas Martinelli a7e2154422 Merge pull request #127 from orangemug/fix/build-path
Fixed build path since move of webpack.config.* to ./config
2017-04-10 09:04:49 -05:00
orangemug d8e84d67da Revert to mapbox-gl 0.34 because of mapbox-gl-inspect. 2017-04-10 13:58:08 +01:00
orangemug c3174a0c72 Moved to using @mapbox/mapbox-gl-style-spec 2017-04-10 13:29:57 +01:00
orangemug 0b05284340 Fixed build path since move of webpack.config.* to ./config 2017-04-10 09:39:36 +01:00
orangemug ac8ae0da66 Merge branch 'master' into fix/issue-110-update-mapbox-style-spec 2017-04-08 22:41:26 +01:00
Lukas Martinelli 4517a8a36a Merge pull request #125 from orangemug/feature/ui-tests
Initial webdriver test
2017-04-05 14:11:06 -05:00
orangemug 8ba7eadcb9 Removed tests for node v5 2017-04-05 18:01:26 +01:00
orangemug 0700e5b05b Yet another attempt at the build matrix. 2017-04-05 14:13:16 +01:00
orangemug 3485b7bfb0 Fixed matrix typo. 2017-04-04 21:42:08 +01:00
orangemug c71c50a729 Also need os in yaml. 2017-04-04 20:21:14 +01:00
orangemug 2651ab891d Moved linux env into matrix. 2017-04-04 20:18:50 +01:00
orangemug 1e429550c6 Moved to mapbox-gl-inspect^1.2.3 2017-04-04 19:18:41 +01:00
orangemug 44e4ae3740 Added node 7 to appveyor and also installed windows-build-tools for odd nodejs versions (issue #125) 2017-04-04 18:58:03 +01:00
orangemug b1552248c3 Added C compiler for odd versions of node.js
See <https://github.com/laverdet/node-fibers/issues/333#issuecomment-282312238>
2017-04-04 18:29:15 +01:00
orangemug 5efd2caeb8 Added nodejs 7 to travis and updated to latest patch versions 2017-04-04 18:09:43 +01:00
orangemug bed012cb9c Updated web driver config. 2017-04-04 16:03:02 +01:00
orangemug 319d9024db Removed .runner-opts from gitignore 2017-04-04 15:53:37 +01:00
orangemug ff7e371404 Initial webdriver test. 2017-04-04 15:51:30 +01:00
orangemug d94ee2ba98 Merge remote-tracking branch 'upstream/master' into fix/issue-110-update-mapbox-style-spec 2017-04-04 08:57:33 +01:00
orangemug a112c29c21 Moved mapbox-gl-inspect to point at lukasmartinelli/mapbox-gl-inspect repo. 2017-04-04 08:55:45 +01:00
Lukas Martinelli c7d6734a26 Merge pull request #122 from orangemug/fix/issue-54
Fix to add error notice when uploading invalid JSON
2017-04-02 16:20:23 -04:00
orangemug 32aa8b0e1f Fix lint errors. 2017-04-02 18:42:27 +01:00
orangemug 6b22ba2707 Removed duplicate css rules. 2017-04-02 16:11:14 +01:00
orangemug 2400c8ed00 Clear error on modal toggle. 2017-04-02 16:02:57 +01:00
orangemug 396022e8ea Styling fixes for firefox. 2017-04-02 16:01:56 +01:00
orangemug 0d4449b9c2 Added css flexbox to modal to allow for a max-height. 2017-04-02 15:13:07 +01:00
Lukas Martinelli 32ac92f901 Merge pull request #105 from orangemug/fix/issue-89
Remove overflow hidden to allow modal to show in safari
2017-03-28 23:30:37 -04:00
Lukas Martinelli f70026b702 Merge pull request #119 from nyurik/nyurik-patch-1
Increase popup Z-index
2017-03-28 23:30:23 -04:00
Yuri Astrakhan 87acc3362d Increase popup Z-index
Per https://github.com/maputnik/editor/issues/118
2017-03-28 23:03:13 -04:00
Lukas Martinelli 732d231c78 Do not barf if glyphs url not set #113 2017-03-28 11:00:58 -04:00
orangemug a76ce64e1d Temp updated to fix peer dependency issue 2017-03-22 10:54:11 +00:00
orangemug 5433a4193b Move from mapbox-gl-style-spec -> mapbox-gl. 2017-03-22 10:36:20 +00:00
Lukas Martinelli 56f1e58df0 Upgrade to Mapbox GL 0.33 #108 2017-03-19 16:15:38 -04:00
Helge Fahrnberger d0c9db41ce Merge pull request #104 from orangemug/feature/issue-47
Added JSON linting (#47)
2017-03-15 15:22:34 +01:00
Helge Fahrnberger f162ffd9be Merge pull request #107 from orangemug/feature/layer-comments
Added layer comments via 'metadata.maputnik:comment' (issue #28)
2017-03-15 15:19:45 +01:00
orangemug decc390777 Fix to add error notice when uploading invalid JSON (issue #54) 2017-03-09 21:29:25 +00:00
orangemug ad8fa7563a Added JSON linting (fixes #47) 2017-03-08 21:35:19 +00:00
Lukas Martinelli 68859d279d Merge pull request #101 from orangemug/fix/issue-44
Alpha value slider fix (#44)
2017-03-07 10:12:17 -05:00
Lukas Martinelli 5792a531ce Merge pull request #102 from orangemug/feature/color-swatch
Added color swatch to color field
2017-03-07 10:07:02 -05:00
orangemug 03af10f850 Fixed maputnik-color-swatch size. 2017-03-07 13:02:09 +00:00
orangemug 2f059874aa Fixed css lint errors. 2017-03-07 12:59:08 +00:00
orangemug a53d7763ba Added layer comments via 'metadata.maputnik:comment' (issue #28) 2017-03-07 12:25:23 +00:00
orangemug eb526a6186 Remove overflow hidden to allow modal to show in safari (issue #89) 2017-03-07 10:47:46 +00:00
orangemug 6095f871ed Added color swatch to color field. 2017-03-07 10:17:45 +00:00
orangemug e3b4fe582b Fixed alpha value slider (fixes #44) 2017-03-07 09:59:11 +00:00
jirik bbf26a3f38 Update fontstacks URL according to https://github.com/klokantech/tileserver-gl/pull/104#issuecomment-274444087 2017-02-02 13:19:52 +01:00
Lukas Martinelli fd291490d0 Merge pull request #93 from albertov/toolbar-offset
Configurable toolbar top offset.
2017-01-26 23:18:17 +01:00
Lukas Martinelli 767d68d905 Replace 0px with 0 2017-01-26 21:12:05 +01:00
Alberto Valverde 32b18e9141 Configurable toolbar top offset.
For facilitate embeding in other apps.
2017-01-26 20:43:06 +01:00
144 changed files with 21691 additions and 1938 deletions
+20
View File
@@ -0,0 +1,20 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"static-fs",
"react-hot-loader/babel",
"@babel/plugin-proposal-class-properties"
],
"env": {
"test": {
"plugins": [
["istanbul", {
"exclude": ["node_modules/**", "test/**"]
}]
]
}
}
}
+102
View File
@@ -0,0 +1,102 @@
version: 2
templates:
# Test the build **only** no webdriver
build-steps: &build-steps
- checkout
- run:
name: "Create artifacts directory"
command: mkdir /tmp/artifacts
- restore_cache:
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: mkdir -p /tmp/artifacts/logs
- run: npm run build
- run: npm run lint
- run: npm run lint-styles
- store_artifacts:
path: /tmp/artifacts
destination: /artifacts
# Test in webdriver
wdio-steps: &wdio-steps
- checkout
- run:
name: "Create artifacts directory"
command: mkdir /tmp/artifacts
- restore_cache:
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: mkdir -p /tmp/artifacts/logs
- run: npm run build
- run: npm run lint
- run: npm run lint-styles
- run: DOCKER_HOST=localhost npm test
- run: ./node_modules/.bin/istanbul report --include /tmp/artifacts/coverage/coverage.json --dir /tmp/artifacts/coverage html lcov
- store_artifacts:
path: /tmp/artifacts
destination: /artifacts
jobs:
build-linux-node-v8:
docker:
- image: node:8
working_directory: ~/repo-linux-node-v8
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: *wdio-steps
build-linux-node-v12:
docker:
- image: node:12
working_directory: ~/repo-linux-node-v12
steps: *build-steps
build-osx-node-v8:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@8
working_directory: ~/repo-osx-node-v8
steps: *build-steps
build-osx-node-v10:
macos:
xcode: "9.0"
dependencies:
override:
- 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-v8
- build-linux-node-v10
- build-linux-node-v12
- build-osx-node-v8
- build-osx-node-v10
- build-osx-node-v12
+3
View File
@@ -30,3 +30,6 @@ node_modules
# Ignore build files # Ignore build files
public public
/errorShots
/old
/build
+15
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
}
}
-22
View File
@@ -1,22 +0,0 @@
language: node_js
addons:
firefox: latest
os:
- linux
- osx
node_js:
- "4.6"
- "5.11"
- "6.1"
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh -e /etc/init.d/xvfb start; fi
install:
- npm install
script:
- mkdir public
- node --stack_size=100000 $(which npm) run build
- npm run lint
- npm run lint-styles
- npm run test
+9 -3
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 EXPOSE 8888
@@ -9,7 +14,8 @@ COPY . ${HOME}/
WORKDIR ${HOME} WORKDIR ${HOME}
RUN npm install -d --dev RUN npm install -d
RUN npm run build RUN npm run build
CMD npm run start -- --host 0.0.0.0 WORKDIR ${HOME}/build/build
CMD python -m SimpleHTTPServer 8888
+72 -16
View File
@@ -1,14 +1,31 @@
# Maputnik [![Build Status](https://travis-ci.org/maputnik/editor.svg?branch=master)](https://travis-ci.org/maputnik/editor) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/anelbgv6jdb3qnh9/branch/master?svg=true)](https://ci.appveyor.com/project/lukasmartinelli/editor) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://tldrlegal.com/license/mit-license) # Maputnik
<img width="200" align="right" alt="Maputnik" src="src/img/maputnik.png" /> [![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]
[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?type=dev
[license]: https://tldrlegal.com/license/mit-license
<img width="200" align="right" alt="Maputnik" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/src/img/maputnik.png" />
A free and open visual editor for the [Mapbox GL styles](https://www.mapbox.com/mapbox-gl-style-spec/) A free and open visual editor for the [Mapbox GL styles](https://www.mapbox.com/mapbox-gl-style-spec/)
targeted at developers and map designers. targeted at developers and map designers.
- :link: Design your maps online at **http://maputnik.com/editor/** (all in local storage) - :link: Design your maps online at **<https://maputnik.github.io/editor/>** (all in local storage)
- :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development - :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development
Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independance is an OSS map designer. Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independence is an OSS map designer.
## Donations
If you or your organisation has seen value from Maputnik, please consider donating at <https://maputnik.github.io/donate>
## Documentation ## Documentation
@@ -23,10 +40,7 @@ The documentation can be found in the [Wiki](https://github.com/maputnik/editor/
Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/). Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/).
We ensure building and developing Maputnik works with We ensure building and developing Maputnik works with the [current active LTS Node.js version and above](https://github.com/nodejs/Release#release-schedule).
- Linux, OSX and Windows
- Node >4
Install the deps, start the dev server and open the web browser on `http://localhost:8888/`. Install the deps, start the dev server and open the web browser on `http://localhost:8888/`.
@@ -37,7 +51,18 @@ npm install
npm start npm start
``` ```
Build a production package for distribution. If you want Maputnik to be accessible externally use the [`--host` option](https://webpack.js.org/configuration/dev-server/#devserverhost):
```bash
# start externally accessible dev server
npm start -- --host 0.0.0.0
```
The build process will watch for changes to the filesystem, rebuild and autoreload the editor. However note this from the [webpack-dev-server docs](https://webpack.js.org/configuration/dev-server/):
> webpack uses the file system to get notified of file changes. In some cases this does not work. For example, when using Network File System (NFS). Vagrant also has a lot of problems with this. ([snippet source](https://webpack.js.org/configuration/dev-server/#devserverwatchoptions-))
To enable polling add `export WEBPACK_DEV_SERVER_POLLING=1` to your environment.
``` ```
npm run build npm run build
@@ -51,6 +76,37 @@ npm run lint
npm run lint-styles npm run lint-styles
``` ```
## Tests
For testing we use [webdriverio](http://webdriver.io) and [selenium-standalone](https://github.com/vvo/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 a terminal and run the following. This will install the drivers on your local machine
```
./node_modules/.bin/selenium-standalone install
```
Now start the standalone server
```
./node_modules/.bin/selenium-standalone start
```
Then open another terminal and run
```
npm test
```
After some time you should see a browser launch which will be automated by the test runner.
## Related Projects
- [maputnik-dev-server](https://github.com/nycplanning/labs-maputnik-dev-server) - An express.js server that allows for quickly loading the style from any mapboxGL map into mapuntnik.
## Sponsors ## Sponsors
Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter.com/projects/174808720/maputnik-visual-map-editor-for-mapbox-gl)**. This project would not be possible without these commercial and individual sponsors. Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter.com/projects/174808720/maputnik-visual-map-editor-for-mapbox-gl)**. This project would not be possible without these commercial and individual sponsors.
@@ -62,13 +118,13 @@ Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter
- [Terranodo](http://terranodo.io/) - [Terranodo](http://terranodo.io/)
<a href="https://getwemap.com/"> <a href="https://getwemap.com/">
<img width="33%" alt="Wemap" style="display:inline" src="media/sponsors/wemap.jpg" /> <img width="33%" alt="Wemap" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/wemap.jpg" />
</a> </a>
<a href="http://terranodo.io/"> <a href="http://terranodo.io/">
<img width="33%" alt="Terranodo" style="display:inline" src="media/sponsors/terranodo.png" /> <img width="33%" alt="Terranodo" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/terranodo.png" />
</a> </a>
<a href="https://www.orbiconinformatik.dk/"> <a href="https://www.orbiconinformatik.dk/">
<img width="32%" alt="Terranodo" style="display:inline" src="media/sponsors/orbicon_informatik.png" /> <img width="32%" alt="Terranodo" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/orbicon_informatik.png" />
</a> </a>
<br/> <br/>
@@ -80,13 +136,13 @@ Thanks to the supporters of the **[Kickstarter campaign](https://www.kickstarter
- [Dreipol](https://www.dreipol.ch/) - [Dreipol](https://www.dreipol.ch/)
<a href="https://www.klokantech.com/"> <a href="https://www.klokantech.com/">
<img width="18%" alt="Klokan Technologies" style="display:inline-block" src="media/sponsors/klokantech.png" /> <img width="18%" alt="Klokan Technologies" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/klokantech.png" />
</a> </a>
<a href="http://www.geofabrik.de/"> <a href="http://www.geofabrik.de/">
<img width="18%" alt="Geofabrik" style="display:inline-block" src="media/sponsors/geofabrik.png" /> <img width="18%" alt="Geofabrik" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/geofabrik.png" />
</a> </a>
<a href="https://www.dreipol.ch/"> <a href="https://www.dreipol.ch/">
<img width="18%" alt="Dreipol" style="display:inline-block" src="media/sponsors/dreipol.png" /> <img width="18%" alt="Dreipol" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/dreipol.png" />
</a> </a>
<br/> <br/>
@@ -109,6 +165,6 @@ Sina Martinelli, Nicholas Doiron, Neil Cawse, Urs42, Benedikt Groß, Manuel Roth
Maputnik is [licensed under MIT](LICENSE) and is Copyright (c) Lukas Martinelli and contributors. Maputnik is [licensed under MIT](LICENSE) and is Copyright (c) Lukas Martinelli and contributors.
**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is a independent style editor for the **Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is an independent style editor for the
open source technology in the Mapbox GL ecosystem. open source technology in the Mapbox GL ecosystem.
As contributor please take extra care of not violating any Mapbox trademarks. Do not get inspired by Mapbox Studio and make your own decisions for a good style editor. As contributor please take extra care of not violating any Mapbox trademarks. Do not get inspired by Mapbox Studio and make your own decisions for a good style editor.
+17 -5
View File
@@ -1,14 +1,26 @@
image: Visual Studio 2015
environment: environment:
matrix: matrix:
- nodejs_version: "4.6" - nodejs_version: "8"
- nodejs_version: "5.11" - nodejs_version: "10"
- nodejs_version: "6.1" - nodejs_version: "12"
platform:
- x86
- x64
install: install:
- ps: Install-Product node $env:nodejs_version # https://github.com/appveyor/ci/issues/2921#issuecomment-501016533
- ps: |
try {
Install-Product node $env:nodejs_version $env:platform
} catch {
echo "Unable to install node $env:nodejs_version, trying update..."
Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
}
- md public - md public
- npm --vs2015 install --global windows-build-tools
- npm install - npm install
build_script: build_script:
- npm run build - npm run build
test_script: test_script:
- npm run lint - npm run lint
- npm test - npm run lint-styles
+288
View File
@@ -0,0 +1,288 @@
var webpack = require("webpack");
var WebpackDevServer = require("webpack-dev-server");
var webpackConfig = require("./webpack.config");
var testConfig = require("../test/config/specs");
var artifacts = require("../test/artifacts");
var isDocker = require("is-docker");
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',
// If outputDir is provided WebdriverIO can capture driver session logs
// it is possible to configure which logTypes to include/exclude.
// excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs
// excludeDriverLogs: ['bugreport', 'server'],
}],
//
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here
//
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: 'info',
//
// Set specific log levels per logger
// loggers:
// - webdriver, webdriverio
// - @wdio/applitools-service, @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
// - @wdio/mocha-framework, @wdio/jasmine-framework
// - @wdio/local-runner, @wdio/lambda-runner
// - @wdio/sumologic-reporter
// - @wdio/cli, @wdio/config, @wdio/sync, @wdio/utils
// Level of logging verbosity: trace | debug | info | warn | error | silent
// logLevels: {
// webdriver: 'debug',
// '@wdio/applitools-service': 'info'
// },
//
// If you only want to run your tests until a specific amount of tests have failed use
// bail (default is 0 - don't bail, run all tests).
bail: 0,
//
screenshotPath: SCREENSHOT_PATH,
// Note: This is here because @orangemug currently runs Maputnik inside a docker container.
host: process.env.DOCKER_HOST || "0.0.0.0",
// Set a base URL in order to shorten url command calls. If your `url` parameter starts
// with `/`, the base url gets prepended, not including the path portion of your baseUrl.
// If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
// gets prepended directly.
baseUrl: 'http://localhost',
//
// Default timeout for all waitFor* commands.
waitforTimeout: 10000,
//
// Default timeout in milliseconds for request
// if Selenium Grid doesn't send response
connectionRetryTimeout: 90000,
//
// Default request retries count
connectionRetryCount: 3,
//
// Test runner services
// Services take over a specific job you don't want to take care of. They enhance
// your test setup with almost no effort. Unlike plugins, they don't add new
// commands. Instead, they hook themselves up into the test process.
services: ['selenium-standalone'],
//
// Framework you want to run your specs with.
// The following are supported: Mocha, Jasmine, and Cucumber
// see also: https://webdriver.io/docs/frameworks.html
//
// Make sure you have the wdio adapter package for the specific framework installed
// before running any tests.
framework: 'mocha',
//
// The number of times to retry the entire specfile when it fails as a whole
// specFileRetries: 1,
//
// Test reporter for stdout.
// The only one supported by default is 'dot'
// see also: https://webdriver.io/docs/dot-reporter.html
reporters: ['spec'],
//
// Options to be passed to Mocha.
// See the full list at http://mochajs.org/
mochaOpts: {
ui: 'bdd',
// Because we don't know how long the initial build will take...
timeout: 4*60*1000
},
onPrepare: function (config, capabilities) {
return new Promise(function(resolve, reject) {
var compiler = webpack(webpackConfig);
server = new WebpackDevServer(compiler, {
stats: {
colors: true
}
});
server.listen(testConfig.port, (isDocker() ? "0.0.0.0" : "localhost"), function(err) {
if(err) {
reject(err);
}
else {
resolve();
}
});
})
},
onComplete: function(exitCode) {
server.close()
}
//
// =====
// Hooks
// =====
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
// it and to build services around it. You can either apply a single function or an array of
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
// resolved to continue.
/**
* Gets executed once before all workers get launched.
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
*/
// onPrepare: function (config, capabilities) {
// },
/**
* Gets executed just before initialising the webdriver session and test framework. It allows you
* to manipulate configurations depending on the capability or spec.
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
*/
// beforeSession: function (config, capabilities, specs) {
// },
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
*/
// before: function (capabilities, specs) {
// },
/**
* Runs before a WebdriverIO command gets executed.
* @param {String} commandName hook command name
* @param {Array} args arguments that command would receive
*/
// beforeCommand: function (commandName, args) {
// },
/**
* Hook that gets executed before the suite starts
* @param {Object} suite suite details
*/
// beforeSuite: function (suite) {
// },
/**
* Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
* @param {Object} test test details
*/
// beforeTest: function (test) {
// },
/**
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
* beforeEach in Mocha)
*/
// beforeHook: function () {
// },
/**
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
* afterEach in Mocha)
*/
// afterHook: function () {
// },
/**
* Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
* @param {Object} test test details
*/
// afterTest: function (test) {
// },
/**
* Hook that gets executed after the suite has ended
* @param {Object} suite suite details
*/
// afterSuite: function (suite) {
// },
/**
* Runs after a WebdriverIO command gets executed
* @param {String} commandName hook command name
* @param {Array} args arguments that command would receive
* @param {Number} result 0 - command success, 1 - command error
* @param {Object} error error object if any
*/
// afterCommand: function (commandName, args, result, error) {
// },
/**
* Gets executed after all tests are done. You still have access to all global variables from
* the test.
* @param {Number} result 0 - test pass, 1 - test fail
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// after: function (result, capabilities, specs) {
// },
/**
* Gets executed right after terminating the webdriver session.
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// afterSession: function (config, capabilities, specs) {
// },
/**
* Gets executed after all workers got shut down and the process is about to exit. An error
* thrown in the onComplete hook will result in the test run failing.
* @param {Object} exitCode 0 - success, 1 - fail
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {<Object>} results object containing test results
*/
// onComplete: function(exitCode, config, capabilities, results) {
// },
/**
* Gets executed when a refresh happens.
* @param {String} oldSessionId session ID of the old session
* @param {String} newSessionId session ID of the new session
*/
//onReload: function(oldSessionId, newSessionId) {
//}
}
+23 -6
View File
@@ -1,14 +1,16 @@
"use strict"; "use strict";
var webpack = require('webpack'); var webpack = require('webpack');
var path = require('path'); var path = require('path');
var loaders = require('./webpack.loaders'); var rules = require('./webpack.rules');
var HtmlWebpackPlugin = require('html-webpack-plugin'); var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
const HOST = process.env.HOST || "127.0.0.1"; const HOST = process.env.HOST || "127.0.0.1";
const PORT = process.env.PORT || "8888"; const PORT = process.env.PORT || "8888";
module.exports = { module.exports = {
target: 'web', target: 'web',
mode: 'development',
entry: [ entry: [
`webpack-dev-server/client?http://${HOST}:${PORT}`, `webpack-dev-server/client?http://${HOST}:${PORT}`,
`webpack/hot/only-dev-server`, `webpack/hot/only-dev-server`,
@@ -16,14 +18,17 @@ module.exports = {
], ],
devtool: process.env.WEBPACK_DEVTOOL || 'cheap-module-source-map', devtool: process.env.WEBPACK_DEVTOOL || 'cheap-module-source-map',
output: { output: {
path: path.join(__dirname, 'public'), path: path.join(__dirname, '..', 'public'),
filename: 'bundle.js' filename: 'bundle.js'
}, },
resolve: { resolve: {
extensions: ['', '.js', '.jsx'] extensions: ['.js', '.jsx']
}, },
module: { module: {
loaders noParse: [
/mapbox-gl\/dist\/mapbox-gl.js/
],
rules: rules
}, },
node: { node: {
fs: "empty", fs: "empty",
@@ -41,14 +46,26 @@ module.exports = {
// serve index.html in place of 404 responses to allow HTML5 history // serve index.html in place of 404 responses to allow HTML5 history
historyApiFallback: true, historyApiFallback: true,
port: PORT, port: PORT,
host: HOST host: HOST,
watchOptions: {
// Disabled polling by default as it causes lots of CPU usage and hence drains laptop batteries. To enable polling add WEBPACK_DEV_SERVER_POLLING to your environment
// See <https://webpack.js.org/configuration/watch/#watchoptions-poll> for details
poll: (!!process.env.WEBPACK_DEV_SERVER_POLLING ? true : false),
watch: false
}
}, },
plugins: [ plugins: [
new webpack.NoErrorsPlugin(), new webpack.NoEmitOnErrorsPlugin(),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
title: 'Maputnik', title: 'Maputnik',
template: './src/template.html' template: './src/template.html'
}), }),
new CopyWebpackPlugin([
{
from: './src/manifest.json',
to: 'manifest.json'
}
])
] ]
}; };
+62
View File
@@ -0,0 +1,62 @@
var webpack = require('webpack');
var path = require('path');
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 OUTPATH = artifacts.pathSync("/build");
module.exports = {
entry: {
app: './src/index.jsx',
},
output: {
path: OUTPATH,
filename: '[name].[contenthash].js',
chunkFilename: '[contenthash].js'
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
noParse: [
/mapbox-gl\/dist\/mapbox-gl.js/
],
rules: rules
},
node: {
fs: "empty",
net: 'empty',
tls: 'empty'
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new WebpackCleanupPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new HtmlWebpackPlugin({
template: './src/template.html',
title: 'Maputnik'
}),
new CopyWebpackPlugin([
{
from: './src/manifest.json',
to: 'manifest.json'
}
]),
new BundleAnalyzerPlugin({
analyzerMode: 'static',
defaultSizes: 'gzip',
openAnalyzer: false,
generateStatsFile: true,
reportFilename: 'bundle-stats.html',
statsFilename: 'bundle-stats.json',
})
]
};
+38
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'
]
}
];
-34
View File
@@ -1,34 +0,0 @@
var webpackConfig = require('./webpack.config.js');
// Karma configuration
module.exports = function(config) {
var browsers = ['Chrome'];
if (process.env.TRAVIS) {
browsers = ['Firefox'];
}
config.set({
browsers: browsers,
frameworks: ['mocha'],
// ... normal karma configuration
files: [
// all files ending in "_test"
{pattern: 'test/*_test.js', watched: false},
{pattern: 'test/**/*_test.js', watched: false}
// each file acts as entry point for the webpack configuration
],
preprocessors: {
// add webpack as preprocessor
'test/*_test.js': ['webpack'],
'test/**/*_test.js': ['webpack']
},
webpack: webpackConfig,
webpackMiddleware: {
// webpack-dev-middleware configuration
// i. e.
stats: 'errors-only'
}
});
};
+14836
View File
File diff suppressed because it is too large Load Diff
+101 -82
View File
@@ -1,16 +1,16 @@
{ {
"name": "maputnik", "name": "maputnik",
"version": "1.0.1", "version": "1.6.1",
"description": "A MapboxGL visual style editor", "description": "A MapboxGL visual style editor",
"main": "''", "main": "''",
"scripts": { "scripts": {
"stats": "webpack --config webpack.production.config.js --profile --json > stats.json", "stats": "webpack --config config/webpack.production.config.js --profile --json > stats.json",
"build": "webpack --config webpack.production.config.js --progress --profile --colors", "build": "webpack --config config/webpack.production.config.js --progress --profile --colors",
"test": "karma start --single-run", "test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
"test-watch": "karma start", "test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
"start": "webpack-dev-server --progress --profile --colors --watch-poll", "start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
"lint": "eslint --ext js --ext jsx {src,test}", "lint": "eslint --ext js --ext jsx src test",
"lint-styles": "stylelint 'src/styles/*.scss'" "lint-styles": "stylelint \"src/styles/*.scss\""
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -20,58 +20,67 @@
"license": "MIT", "license": "MIT",
"homepage": "https://github.com/maputnik/editor#readme", "homepage": "https://github.com/maputnik/editor#readme",
"dependencies": { "dependencies": {
"classnames": "^2.2.5", "@babel/runtime": "^7.6.3",
"codemirror": "^5.18.2", "@mapbox/mapbox-gl-rtl-text": "^0.2.3",
"color": "^1.0.3", "@mapbox/mapbox-gl-style-spec": "^13.9.0",
"file-saver": "^1.3.2", "classnames": "^2.2.6",
"github-api": "^3.0.0", "codemirror": "^5.49.0",
"color": "^3.1.2",
"detect-browser": "^4.7.0",
"file-saver": "^2.0.2",
"jsonlint": "github:josdejong/jsonlint#85a19d7",
"lodash.capitalize": "^4.2.1", "lodash.capitalize": "^4.2.1",
"lodash.clamp": "^4.0.3",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.4.0", "lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.31.0", "mapbox-gl": "^1.4.1",
"mapbox-gl-inspect": "^1.2.1", "mapbox-gl-inspect": "^1.3.1",
"mapbox-gl-style-spec": "^8.11.0", "maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.0", "ol": "^6.0.1",
"ol-mapbox-style": "1.0.1", "ol-mapbox-style": "^5.0.2",
"openlayers": "^3.19.1", "prop-types": "^15.7.2",
"react": "^15.4.0", "react": "^16.10.2",
"react-addons-pure-render-mixin": "^15.4.0", "react-aria-menubutton": "^6.2.0",
"react-autocomplete": "^1.4.0", "react-aria-modal": "^4.0.0",
"react-codemirror": "^0.3.0", "react-autobind": "^1.0.6",
"react-collapse": "^2.3.3", "react-autocomplete": "^1.8.1",
"react-color": "^2.10.0", "react-codemirror2": "^6.0.0",
"react-dom": "^15.4.0", "react-collapse": "^4.0.3",
"react-file-reader-input": "^1.1.0", "react-color": "^2.17.3",
"react-height": "^2.1.1", "react-dom": "^16.10.2",
"react-icon-base": "^2.0.4", "react-file-reader-input": "^2.0.0",
"react-icons": "^2.2.1", "react-icon-base": "^2.1.2",
"react-motion": "^0.4.7", "react-icons": "^3.7.0",
"react-sortable-hoc": "^0.4.5", "react-motion": "^0.5.2",
"reconnecting-websocket": "^3.0.3", "react-sortable-hoc": "^1.10.1",
"request": "^2.79.0", "reconnecting-websocket": "^4.2.0",
"slugify": "^1.3.5",
"url": "^0.11.0" "url": "^0.11.0"
}, },
"babel": {
"presets": [
"es2015",
"react"
],
"plugins": [
"transform-object-rest-spread"
]
},
"jshintConfig": { "jshintConfig": {
"esversion": 6 "esversion": 6
}, },
"stylelint": { "stylelint": {
"extends": "stylelint-config-standard" "extends": "stylelint-config-recommended-scss",
"rules": {
"no-descending-specificity": null,
"media-feature-name-no-unknown": [
true,
{
"ignoreMediaFeatureNames": [
"prefers-reduced-motion"
]
}
]
}
}, },
"eslintConfig": { "eslintConfig": {
"plugins": [ "plugins": [
"react" "react"
], ],
"extend": [ "extends": [
"plugin:react/recommended" "plugin:react/recommended"
], ],
"env": { "env": {
@@ -91,41 +100,51 @@
} }
}, },
"devDependencies": { "devDependencies": {
"babel-core": "6.21.0", "@babel/core": "^7.6.3",
"babel-eslint": "^7.1.1", "@babel/plugin-proposal-class-properties": "^7.5.5",
"babel-loader": "6.2.10", "@babel/plugin-transform-runtime": "^7.6.2",
"babel-plugin-transform-class-properties": "^6.11.5", "@babel/preset-env": "^7.6.3",
"babel-plugin-transform-decorators-legacy": "^1.3.4", "@babel/preset-flow": "^7.0.0",
"babel-plugin-transform-flow-strip-types": "^6.21.0", "@babel/preset-react": "^7.6.3",
"babel-plugin-transform-object-rest-spread": "^6.8.0", "@wdio/cli": "^5.14.5",
"babel-plugin-transform-runtime": "^6.15.0", "@wdio/local-runner": "^5.14.5",
"babel-preset-es2015": "6.18.0", "@wdio/mocha-framework": "^5.14.4",
"babel-preset-react": "6.16.0", "@wdio/selenium-standalone-service": "^5.13.2",
"babel-runtime": "^6.11.6", "@wdio/spec-reporter": "^5.14.5",
"css-loader": "0.26.1", "@wdio/sync": "^5.14.4",
"eslint": "^3.5.0", "babel-eslint": "^10.0.3",
"eslint-plugin-react": "^6.2.0", "babel-loader": "8.0.6",
"extract-text-webpack-plugin": "^1.0.1", "babel-plugin-istanbul": "^5.2.0",
"file-loader": "0.9.0", "babel-plugin-static-fs": "^3.0.0",
"html-webpack-plugin": "^2.22.0", "copy-webpack-plugin": "^5.0.4",
"json-loader": "^0.5.4", "cors": "^2.8.5",
"karma": "^1.3.0", "cross-env": "^6.0.3",
"karma-chrome-launcher": "^2.0.0", "css-loader": "^3.2.0",
"karma-firefox-launcher": "^1.0.0", "eslint": "^6.5.1",
"karma-mocha": "^1.3.0", "eslint-plugin-react": "^7.16.0",
"karma-webpack": "^2.0.1", "express": "^4.17.1",
"mocha": "^3.1.2", "file-loader": "^4.2.0",
"mocha-loader": "^1.0.0", "html-webpack-plugin": "^3.2.0",
"node-sass": "^4.2.0", "is-docker": "^2.0.0",
"react-hot-loader": "^3.0.0-beta.6", "istanbul": "^0.4.5",
"sass-loader": "^4.0.1", "istanbul-lib-coverage": "^2.0.5",
"style-loader": "0.13.1", "mkdirp": "^0.5.1",
"stylelint": "^7.7.1", "mocha": "^6.2.1",
"stylelint-config-standard": "^15.0.1", "node-sass": "^4.12.0",
"transform-loader": "^0.2.3", "react-hot-loader": "^4.12.15",
"url-loader": "0.5.7", "sass-loader": "^8.0.0",
"webpack": "1.14.0", "selenium-standalone": "^6.16.0",
"webpack-cleanup-plugin": "^0.4.1", "style-loader": "^1.0.0",
"webpack-dev-server": "1.16.2" "stylelint": "^11.0.0",
"stylelint-config-recommended-scss": "^4.0.0",
"stylelint-scss": "^3.11.1",
"transform-loader": "^0.2.4",
"uuid": "^3.3.3",
"webdriverio": "^5.14.5",
"webpack": "^4.41.0",
"webpack-bundle-analyzer": "^3.5.2",
"webpack-cleanup-plugin": "^0.5.1",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2"
} }
} }
+9 -1
View File
@@ -4,10 +4,18 @@
} }
.cm-s-maputnik.CodeMirror, .cm-s-maputnik .CodeMirror-gutters { .cm-s-maputnik.CodeMirror, .cm-s-maputnik .CodeMirror-gutters {
background: transparent;
color: #8e8e8e; color: #8e8e8e;
border: none; border: none;
} }
.cm-s-maputnik.CodeMirror {
background: transparent;
}
.cm-s-maputnik .CodeMirror-gutters {
background: #212328;
}
.cm-s-maputnik .CodeMirror-cursor { .cm-s-maputnik .CodeMirror-cursor {
border-left: solid thin #8e8e8e !important; border-left: solid thin #8e8e8e !important;
} }
+519 -52
View File
@@ -1,26 +1,76 @@
import autoBind from 'react-autobind';
import React from 'react' 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 MapboxGlMap from './map/MapboxGlMap'
import OpenLayers3Map from './map/OpenLayers3Map' import OpenLayersMap from './map/OpenLayersMap'
import LayerList from './layers/LayerList' import LayerList from './layers/LayerList'
import LayerEditor from './layers/LayerEditor' import LayerEditor from './layers/LayerEditor'
import Toolbar from './Toolbar' import Toolbar from './Toolbar'
import AppLayout from './AppLayout' import AppLayout from './AppLayout'
import MessagePanel from './MessagePanel' import MessagePanel from './MessagePanel'
import SettingsModal from './modals/SettingsModal'
import ExportModal from './modals/ExportModal'
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 { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest, validate} from '@mapbox/mapbox-gl-style-spec'
import validateStyleMin from 'mapbox-gl-style-spec/lib/validate_style.min' import style from '../libs/style'
import formatStyle from 'mapbox-gl-style-spec/lib/format' import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
import style from '../libs/style.js'
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
import { undoMessages, redoMessages } from '../libs/diffmessage' import { undoMessages, redoMessages } from '../libs/diffmessage'
import { loadDefaultStyle, StyleStore } from '../libs/stylestore' import { StyleStore } from '../libs/stylestore'
import { ApiStyleStore } from '../libs/apistore' import { ApiStyleStore } from '../libs/apistore'
import { RevisionStore } from '../libs/revisions' import { RevisionStore } from '../libs/revisions'
import LayerWatcher from '../libs/layerwatcher' import LayerWatcher from '../libs/layerwatcher'
import tokens from '../config/tokens.json' import tokens from '../config/tokens.json'
import isEqual from 'lodash.isequal'
import Debug from '../libs/debug'
import queryUtil from '../libs/query-util'
import MapboxGl from 'mapbox-gl'
// Similar functionality as <https://github.com/mapbox/mapbox-gl-js/blob/7e30aadf5177486c2cfa14fe1790c60e217b5e56/src/util/mapbox.js>
function normalizeSourceURL (url, apiToken="") {
const matches = url.match(/^mapbox:\/\/(.*)/);
if (matches) {
// mapbox://mapbox.mapbox-streets-v7
return `https://api.mapbox.com/v4/${matches[1]}.json?secure&access_token=${apiToken}`
}
else {
return url;
}
}
function setFetchAccessToken(url, mapStyle) {
const matchesTilehosting = url.match(/\.tilehosting\.com/);
const matchesMaptiler = url.match(/\.maptiler\.com/);
const matchesThunderforest = url.match(/\.thunderforest\.com/);
if (matchesTilehosting || matchesMaptiler) {
const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true})
if (accessToken) {
return url.replace('{key}', accessToken)
}
}
else if (matchesThunderforest) {
const accessToken = style.getAccessToken("thunderforest", mapStyle, {allowFallback: true})
if (accessToken) {
return url.replace('{key}', accessToken)
}
}
else {
return url;
}
}
function updateRootSpec(spec, fieldName, newValues) { function updateRootSpec(spec, fieldName, newValues) {
return { return {
@@ -38,25 +88,121 @@ function updateRootSpec(spec, fieldName, newValues) {
export default class App extends React.Component { export default class App extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
autoBind(this);
this.revisionStore = new RevisionStore() this.revisionStore = new RevisionStore()
const params = new URLSearchParams(window.location.search.substring(1))
let port = params.get("localport")
if (port == null && (window.location.port != 80 && window.location.port != 443)) {
port = window.location.port
}
this.styleStore = new ApiStyleStore({ this.styleStore = new ApiStyleStore({
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false) onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false),
port: port,
host: params.get("localhost")
})
const shortcuts = [
{
key: "?",
handler: () => {
this.toggleModal("shortcuts");
}
},
{
key: "o",
handler: () => {
this.toggleModal("open");
}
},
{
key: "e",
handler: () => {
this.toggleModal("export");
}
},
{
key: "d",
handler: () => {
this.toggleModal("sources");
}
},
{
key: "s",
handler: () => {
this.toggleModal("settings");
}
},
{
key: "i",
handler: () => {
this.setMapState(
this.state.mapState === "map" ? "inspect" : "map"
);
}
},
{
key: "m",
handler: () => {
document.querySelector(".mapboxgl-canvas").focus();
}
},
{
key: "!",
handler: () => {
this.toggleModal("debug");
}
},
]
document.body.addEventListener("keyup", (e) => {
if(e.key === "Escape") {
e.target.blur();
document.body.focus();
}
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
const shortcut = shortcuts.find((shortcut) => {
return (shortcut.key === e.key)
})
if(shortcut) {
this.setModal("shortcuts", false);
shortcut.handler(e);
}
}
}) })
const styleUrl = initialStyleUrl() const styleUrl = initialStyleUrl()
if(styleUrl) { if(styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?")) {
this.styleStore = new StyleStore() this.styleStore = new StyleStore()
loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle)) loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle))
removeStyleQuerystring()
} else { } else {
if(styleUrl) {
removeStyleQuerystring()
}
this.styleStore.init(err => { this.styleStore.init(err => {
if(err) { if(err) {
console.log('Falling back to local storage for storing styles') console.log('Falling back to local storage for storing styles')
this.styleStore = new StyleStore() this.styleStore = new StyleStore()
} }
this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle)) this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle))
if(Debug.enabled()) {
Debug.set("maputnik", "styleStore", this.styleStore);
Debug.set("maputnik", "revisionStore", this.revisionStore);
}
}) })
} }
if(Debug.enabled()) {
Debug.set("maputnik", "revisionStore", this.revisionStore);
Debug.set("maputnik", "styleStore", this.styleStore);
}
const queryObj = url.parse(window.location.href, true).query;
this.state = { this.state = {
errors: [], errors: [],
infos: [], infos: [],
@@ -64,29 +210,61 @@ export default class App extends React.Component {
selectedLayerIndex: 0, selectedLayerIndex: 0,
sources: {}, sources: {},
vectorLayers: {}, vectorLayers: {},
inspectModeEnabled: false, mapState: "map",
spec: GlSpec, spec: latest,
isOpen: {
settings: false,
sources: false,
open: false,
shortcuts: false,
export: false,
survey: localStorage.hasOwnProperty('survey') ? false : true,
debug: false,
},
mapboxGlDebugOptions: {
showTileBoundaries: false,
showCollisionBoxes: false,
showOverdrawInspector: false,
},
openlayersDebugOptions: {
debugToolbox: false,
},
} }
this.layerWatcher = new LayerWatcher({ this.layerWatcher = new LayerWatcher({
onSourcesChange: v => this.setState({ sources: v }),
onVectorLayersChange: v => this.setState({ vectorLayers: v }) onVectorLayersChange: v => this.setState({ vectorLayers: v })
}) })
} }
handleKeyPress = (e) => {
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
e.preventDefault();
this.onRedo(e);
}
else if(e.metaKey && e.keyCode === 90) {
e.preventDefault();
this.onUndo(e);
}
}
else {
if(e.ctrlKey && e.keyCode === 90) {
e.preventDefault();
this.onUndo(e);
}
else if(e.ctrlKey && e.keyCode === 89) {
e.preventDefault();
this.onRedo(e);
}
}
}
componentDidMount() { componentDidMount() {
Mousetrap.bind(['ctrl+z'], this.onUndo.bind(this)); window.addEventListener("keydown", this.handleKeyPress);
Mousetrap.bind(['ctrl+y'], this.onRedo.bind(this));
} }
componentWillUnmount() { componentWillUnmount() {
Mousetrap.unbind(['ctrl+z'], this.onUndo.bind(this)); window.removeEventListener("keydown", this.handleKeyPress);
Mousetrap.unbind(['ctrl+y'], this.onRedo.bind(this));
}
onReset() {
this.styleStore.purge()
loadDefaultStyle(mapStyle => this.onStyleOpen(mapStyle))
} }
saveStyle(snapshotStyle) { saveStyle(snapshotStyle) {
@@ -96,7 +274,9 @@ export default class App extends React.Component {
updateFonts(urlTemplate) { updateFonts(urlTemplate) {
const metadata = this.state.mapStyle.metadata || {} const metadata = this.state.mapStyle.metadata || {}
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
downloadGlyphsMetadata(urlTemplate.replace('{key}', accessToken), fonts => {
let glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate;
downloadGlyphsMetadata(glyphUrl, fonts => {
this.setState({ spec: updateRootSpec(this.state.spec, 'glyphs', fonts)}) this.setState({ spec: updateRootSpec(this.state.spec, 'glyphs', fonts)})
}) })
} }
@@ -107,7 +287,32 @@ 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 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) { if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
this.updateFonts(newStyle.glyphs) this.updateFonts(newStyle.glyphs)
} }
@@ -115,8 +320,6 @@ export default class App extends React.Component {
this.updateIcons(newStyle.sprite) this.updateIcons(newStyle.sprite)
} }
const errors = validateStyleMin(newStyle, GlSpec)
if(errors.length === 0) {
this.revisionStore.addRevision(newStyle) this.revisionStore.addRevision(newStyle)
if(save) this.saveStyle(newStyle) if(save) this.saveStyle(newStyle)
this.setState({ this.setState({
@@ -128,9 +331,11 @@ export default class App extends React.Component {
errors: errors.map(err => err.message) errors: errors.map(err => err.message)
}) })
} }
this.fetchSources();
} }
onUndo() { onUndo = () => {
const activeStyle = this.revisionStore.undo() const activeStyle = this.revisionStore.undo()
const messages = undoMessages(this.state.mapStyle, activeStyle) const messages = undoMessages(this.state.mapStyle, activeStyle)
this.saveStyle(activeStyle) this.saveStyle(activeStyle)
@@ -140,7 +345,7 @@ export default class App extends React.Component {
}) })
} }
onRedo() { onRedo = () => {
const activeStyle = this.revisionStore.redo() const activeStyle = this.revisionStore.redo()
const messages = redoMessages(this.state.mapStyle, activeStyle) const messages = redoMessages(this.state.mapStyle, activeStyle)
this.saveStyle(activeStyle) this.saveStyle(activeStyle)
@@ -150,7 +355,25 @@ export default class App extends React.Component {
}) })
} }
onLayersChange(changedLayers) { onMoveLayer = (move) => {
let { oldIndex, newIndex } = move;
let layers = this.state.mapStyle.layers;
oldIndex = clamp(oldIndex, 0, layers.length-1);
newIndex = clamp(newIndex, 0, layers.length-1);
if(oldIndex === newIndex) return;
if (oldIndex === this.state.selectedLayerIndex) {
this.setState({
selectedLayerIndex: newIndex
});
}
layers = layers.slice(0);
layers = arrayMove(layers, oldIndex, newIndex);
this.onLayersChange(layers);
}
onLayersChange = (changedLayers) => {
const changedStyle = { const changedStyle = {
...this.state.mapStyle, ...this.state.mapStyle,
layers: changedLayers layers: changedLayers
@@ -158,7 +381,41 @@ export default class App extends React.Component {
this.onStyleChanged(changedStyle) this.onStyleChanged(changedStyle)
} }
onLayerIdChange(oldId, newId) { onLayerDestroy = (layerId) => {
let layers = this.state.mapStyle.layers;
const remainingLayers = layers.slice(0);
const idx = style.indexOfLayer(remainingLayers, layerId)
remainingLayers.splice(idx, 1);
this.onLayersChange(remainingLayers);
}
onLayerCopy = (layerId) => {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const clonedLayer = cloneDeep(changedLayers[idx])
clonedLayer.id = clonedLayer.id + "-copy"
changedLayers.splice(idx, 0, clonedLayer)
this.onLayersChange(changedLayers)
}
onLayerVisibilityToggle = (layerId) => {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const layer = { ...changedLayers[idx] }
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
layer.layout = changedLayout
changedLayers[idx] = layer
this.onLayersChange(changedLayers)
}
onLayerIdChange = (oldId, newId) => {
const changedLayers = this.state.mapStyle.layers.slice(0) const changedLayers = this.state.mapStyle.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, oldId) const idx = style.indexOfLayer(changedLayers, oldId)
@@ -170,7 +427,7 @@ export default class App extends React.Component {
this.onLayersChange(changedLayers) this.onLayersChange(changedLayers)
} }
onLayerChanged(layer) { onLayerChanged = (layer) => {
const changedLayers = this.state.mapStyle.layers.slice(0) const changedLayers = this.state.mapStyle.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layer.id) const idx = style.indexOfLayer(changedLayers, layer.id)
changedLayers[idx] = layer changedLayers[idx] = layer
@@ -178,55 +435,210 @@ export default class App extends React.Component {
this.onLayersChange(changedLayers) this.onLayersChange(changedLayers)
} }
changeInspectMode() { setMapState = (newState) => {
this.setState({ this.setState({
inspectModeEnabled: !this.state.inspectModeEnabled mapState: newState
}) })
} }
setDefaultValues = (styleObj) => {
const metadata = styleObj.metadata || {}
if(metadata['maputnik:renderer'] === undefined) {
const changedStyle = {
...styleObj,
metadata: {
...styleObj.metadata,
'maputnik:renderer': 'mbgljs'
}
}
return changedStyle
} else {
return styleObj
}
}
openStyle = (styleObj) => {
styleObj = this.setDefaultValues(styleObj)
this.onStyleChanged(styleObj)
}
fetchSources() {
const sourceList = {...this.state.sources};
for(let [key, val] of Object.entries(this.state.mapStyle.sources)) {
if(sourceList.hasOwnProperty(key)) {
continue;
}
sourceList[key] = {
type: val.type,
layers: []
};
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) {
let url = val.url;
try {
url = normalizeSourceURL(url, MapboxGl.accessToken);
} catch(err) {
console.warn("Failed to normalizeSourceURL: ", err);
}
try {
url = setFetchAccessToken(url, this.state.mapStyle)
} catch(err) {
console.warn("Failed to setFetchAccessToken: ", err);
}
fetch(url, {
mode: 'cors',
})
.then((response) => {
return response.json();
})
.then((json) => {
if(!json.hasOwnProperty("vector_layers")) {
return;
}
// Create new objects before setState
const sources = Object.assign({}, this.state.sources);
for(let layer of json.vector_layers) {
sources[key].layers.push(layer.id)
}
console.debug("Updating source: "+key);
this.setState({
sources: sources
});
})
.catch((err) => {
console.error("Failed to process sources for '%s'", url, err);
})
}
}
if(!isEqual(this.state.sources, sourceList)) {
console.debug("Setting sources");
this.setState({
sources: sourceList
})
}
}
_getRenderer () {
const metadata = this.state.mapStyle.metadata || {};
return metadata['maputnik:renderer'] || 'mbgljs';
}
mapRenderer() { mapRenderer() {
const metadata = this.state.mapStyle.metadata || {};
const mapProps = { const mapProps = {
mapStyle: style.replaceAccessToken(this.state.mapStyle), mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}),
onDataChange: (e) => { onDataChange: (e) => {
this.layerWatcher.analyzeMap(e.map) this.layerWatcher.analyzeMap(e.map)
this.fetchSources();
}, },
} }
const metadata = this.state.mapStyle.metadata || {} const renderer = this._getRenderer();
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
// Check if OL3 code has been loaded? let mapElement;
if(renderer === 'ol3') {
return <OpenLayers3Map {...mapProps} /> // Check if OL code has been loaded?
if(renderer === 'ol') {
mapElement = <OpenLayersMap
{...mapProps}
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
onLayerSelect={this.onLayerSelect}
/>
} else { } else {
return <MapboxGlMap {...mapProps} mapElement = <MapboxGlMap {...mapProps}
inspectModeEnabled={this.state.inspectModeEnabled} options={this.state.mapboxGlDebugOptions}
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]} /> inspectModeEnabled={this.state.mapState === "inspect"}
} highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
onLayerSelect={this.onLayerSelect} />
} }
onLayerSelect(layerId) { 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} className="maputnik-map__container">
{mapElement}
</div>
}
onLayerSelect = (layerId) => {
const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId) const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
this.setState({ selectedLayerIndex: idx }) this.setState({ selectedLayerIndex: idx })
} }
setModal(modalName, value) {
if(modalName === 'survey' && value === false) {
localStorage.setItem('survey', '');
}
this.setState({
isOpen: {
...this.state.isOpen,
[modalName]: value
}
})
}
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() { render() {
const layers = this.state.mapStyle.layers || [] const layers = this.state.mapStyle.layers || []
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
const metadata = this.state.mapStyle.metadata || {} const metadata = this.state.mapStyle.metadata || {}
const toolbar = <Toolbar const toolbar = <Toolbar
renderer={this._getRenderer()}
mapState={this.state.mapState}
mapStyle={this.state.mapStyle} mapStyle={this.state.mapStyle}
inspectModeEnabled={this.state.inspectModeEnabled} inspectModeEnabled={this.state.mapState === "inspect"}
sources={this.state.sources} sources={this.state.sources}
onStyleChanged={this.onStyleChanged.bind(this)} onStyleChanged={this.onStyleChanged}
onStyleOpen={this.onStyleChanged.bind(this)} onStyleOpen={this.onStyleChanged}
onInspectModeToggle={this.changeInspectMode.bind(this)} onSetMapState={this.setMapState}
onToggleModal={this.toggleModal.bind(this)}
/> />
const layerList = <LayerList const layerList = <LayerList
onLayersChange={this.onLayersChange.bind(this)} onMoveLayer={this.onMoveLayer}
onLayerSelect={this.onLayerSelect.bind(this)} onLayerDestroy={this.onLayerDestroy}
onLayerCopy={this.onLayerCopy}
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
onLayersChange={this.onLayersChange}
onLayerSelect={this.onLayerSelect}
selectedLayerIndex={this.state.selectedLayerIndex} selectedLayerIndex={this.state.selectedLayerIndex}
layers={layers} layers={layers}
sources={this.state.sources} sources={this.state.sources}
@@ -234,11 +646,18 @@ export default class App extends React.Component {
const layerEditor = selectedLayer ? <LayerEditor const layerEditor = selectedLayer ? <LayerEditor
layer={selectedLayer} layer={selectedLayer}
layerIndex={this.state.selectedLayerIndex}
isFirstLayer={this.state.selectedLayerIndex < 1}
isLastLayer={this.state.selectedLayerIndex === this.state.mapStyle.layers.length-1}
sources={this.state.sources} sources={this.state.sources}
vectorLayers={this.state.vectorLayers} vectorLayers={this.state.vectorLayers}
spec={this.state.spec} spec={this.state.spec}
onLayerChanged={this.onLayerChanged.bind(this)} onMoveLayer={this.onMoveLayer}
onLayerIdChange={this.onLayerIdChange.bind(this)} onLayerChanged={this.onLayerChanged}
onLayerDestroy={this.onLayerDestroy}
onLayerCopy={this.onLayerCopy}
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
onLayerIdChange={this.onLayerIdChange}
/> : null /> : null
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
@@ -246,12 +665,60 @@ export default class App extends React.Component {
infos={this.state.infos} infos={this.state.infos}
/> : null /> : null
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}
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}
isOpen={this.state.isOpen.export}
onOpenToggle={this.toggleModal.bind(this, 'export')}
/>
<OpenModal
isOpen={this.state.isOpen.open}
onStyleOpen={this.openStyle}
onOpenToggle={this.toggleModal.bind(this, 'open')}
/>
<SourcesModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged}
isOpen={this.state.isOpen.sources}
onOpenToggle={this.toggleModal.bind(this, 'sources')}
/>
<SurveyModal
isOpen={this.state.isOpen.survey}
onOpenToggle={this.toggleModal.bind(this, 'survey')}
/>
</div>
return <AppLayout return <AppLayout
toolbar={toolbar} toolbar={toolbar}
layerList={layerList} layerList={layerList}
layerEditor={layerEditor} layerEditor={layerEditor}
map={this.mapRenderer()} map={this.mapRenderer()}
bottom={bottomPanel} bottom={bottomPanel}
modals={modals}
/> />
} }
} }
+9 -6
View File
@@ -1,17 +1,19 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import ScrollContainer from './ScrollContainer' import ScrollContainer from './ScrollContainer'
class AppLayout extends React.Component { class AppLayout extends React.Component {
static propTypes = { static propTypes = {
toolbar: React.PropTypes.element.isRequired, toolbar: PropTypes.element.isRequired,
layerList: React.PropTypes.element.isRequired, layerList: PropTypes.element.isRequired,
layerEditor: React.PropTypes.element, layerEditor: PropTypes.element,
map: React.PropTypes.element.isRequired, map: PropTypes.element.isRequired,
bottom: React.PropTypes.element, bottom: PropTypes.element,
modals: PropTypes.node,
} }
static childContextTypes = { static childContextTypes = {
reactIconBase: React.PropTypes.object reactIconBase: PropTypes.object
} }
getChildContext() { getChildContext() {
@@ -38,6 +40,7 @@ class AppLayout extends React.Component {
{this.props.bottom} {this.props.bottom}
</div> </div>
} }
{this.props.modals}
</div> </div>
} }
} }
+13 -5
View File
@@ -1,20 +1,28 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
class Button extends React.Component { class Button extends React.Component {
static propTypes = { static propTypes = {
onClick: React.PropTypes.func, "data-wd-key": PropTypes.string,
style: React.PropTypes.object, "aria-label": PropTypes.string,
className: React.PropTypes.string, onClick: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string,
children: PropTypes.node,
disabled: PropTypes.bool,
} }
render() { render() {
return <a return <button
onClick={this.props.onClick} onClick={this.props.onClick}
disabled={this.props.disabled}
aria-label={this.props["aria-label"]}
className={classnames("maputnik-button", this.props.className)} className={classnames("maputnik-button", this.props.className)}
data-wd-key={this.props["data-wd-key"]}
style={this.props.style}> style={this.props.style}>
{this.props.children} {this.props.children}
</a> </button>
} }
} }
+5 -4
View File
@@ -1,18 +1,19 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
class MessagePanel extends React.Component { class MessagePanel extends React.Component {
static propTypes = { static propTypes = {
errors: React.PropTypes.array, errors: PropTypes.array,
infos: React.PropTypes.array, infos: PropTypes.array,
} }
render() { render() {
const errors = this.props.errors.map((m, i) => { const errors = this.props.errors.map((m, i) => {
return <p className="maputnik-message-panel-error">{m}</p> return <p key={"error-"+i} className="maputnik-message-panel-error">{m}</p>
}) })
const infos = this.props.infos.map((m, i) => { const infos = this.props.infos.map((m, i) => {
return <p key={i}>{m}</p> return <p key={"info-"+i}>{m}</p>
}) })
return <div className="maputnik-message-panel"> return <div className="maputnik-message-panel">
+9 -2
View File
@@ -1,9 +1,16 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
const ScrollContainer = (props) => { class ScrollContainer extends React.Component {
static propTypes = {
children: PropTypes.node
}
render() {
return <div className="maputnik-scroll-container"> return <div className="maputnik-scroll-container">
{props.children} {this.props.children}
</div> </div>
}
} }
export default ScrollContainer export default ScrollContainer
+182 -91
View File
@@ -1,67 +1,123 @@
import React from 'react' import React from 'react'
import FileReaderInput from 'react-file-reader-input' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import {detect} from 'detect-browser';
import MdFileDownload from 'react-icons/lib/md/file-download' import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md'
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 logoImage from '../img/maputnik.png'
import SettingsModal from './modals/SettingsModal'
import ExportModal from './modals/ExportModal'
import SourcesModal from './modals/SourcesModal'
import OpenModal from './modals/OpenModal'
import style from '../libs/style' import logoImage from 'maputnik-design/logos/logo-color.svg'
import pkgJson from '../../package.json'
function IconText(props) {
return <span className="maputnik-icon-text">{props.children}</span> // 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 = {
children: PropTypes.node,
}
render() {
return <span className="maputnik-icon-text">{this.props.children}</span>
}
} }
function ToolbarLink(props) { class ToolbarLink extends React.Component {
static propTypes = {
className: PropTypes.string,
children: PropTypes.node,
href: PropTypes.string,
onToggleModal: PropTypes.func,
}
render() {
return <a return <a
className={classnames('maputnik-toolbar-link', props.className)} className={classnames('maputnik-toolbar-link', this.props.className)}
href={props.href} href={this.props.href}
target={"blank"} rel="noopener noreferrer"
target="_blank"
> >
{props.children} {this.props.children}
</a> </a>
}
} }
function ToolbarAction(props) { class ToolbarLinkHighlighted extends React.Component {
static propTypes = {
className: PropTypes.string,
children: PropTypes.node,
href: PropTypes.string,
onToggleModal: PropTypes.func
}
render() {
return <a return <a
className={classnames('maputnik-toolbar-link', "maputnik-toolbar-link--highlighted", this.props.className)}
href={this.props.href}
rel="noopener noreferrer"
target="_blank"
>
<span className="maputnik-toolbar-link-wrapper">
{this.props.children}
</span>
</a>
}
}
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,
onClick: PropTypes.func,
wdKey: PropTypes.string
}
render() {
return <button
className='maputnik-toolbar-action' className='maputnik-toolbar-action'
onClick={props.onClick} data-wd-key={this.props.wdKey}
onClick={this.props.onClick}
> >
{props.children} {this.props.children}
</a> </button>
}
} }
export default class Toolbar extends React.Component { export default class Toolbar extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: PropTypes.object.isRequired,
inspectModeEnabled: React.PropTypes.bool.isRequired, inspectModeEnabled: PropTypes.bool.isRequired,
onStyleChanged: React.PropTypes.func.isRequired, onStyleChanged: PropTypes.func.isRequired,
// A new style has been uploaded // A new style has been uploaded
onStyleOpen: React.PropTypes.func.isRequired, onStyleOpen: PropTypes.func.isRequired,
// A dict of source id's and the available source layers // A dict of source id's and the available source layers
sources: React.PropTypes.object.isRequired, sources: PropTypes.object.isRequired,
onInspectModeToggle: React.PropTypes.func.isRequired children: PropTypes.node,
onToggleModal: PropTypes.func,
onSetMapState: PropTypes.func,
mapState: PropTypes.string,
renderer: PropTypes.string,
} }
constructor(props) { state = {
super(props)
this.state = {
isOpen: { isOpen: {
settings: false, settings: false,
sources: false, sources: false,
@@ -70,76 +126,111 @@ export default class Toolbar extends React.Component {
export: false, export: false,
} }
} }
}
toggleModal(modalName) { handleSelection(val) {
this.setState({ this.props.onSetMapState(val);
isOpen: {
...this.state.isOpen,
[modalName]: !this.state.isOpen[modalName]
}
})
} }
render() { render() {
const views = [
{
id: "map",
title: "Map",
},
{
id: "inspect",
title: "Inspect",
disabled: this.props.renderer !== 'mbgljs',
},
{
id: "filter-deuteranopia",
title: "Map (deuteranopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-protanopia",
title: "Map (protanopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-tritanopia",
title: "Map (tritanopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-achromatopsia",
title: "Map (achromatopsia)",
disabled: !colorAccessibilityFiltersEnabled,
},
];
const currentView = views.find((view) => {
return view.id === this.props.mapState;
});
return <div className='maputnik-toolbar'> return <div className='maputnik-toolbar'>
<SettingsModal <div className="maputnik-toolbar__inner">
mapStyle={this.props.mapStyle} <div
onStyleChanged={this.props.onStyleChanged} className="maputnik-toolbar-logo-container"
isOpen={this.state.isOpen.settings} >
onOpenToggle={this.toggleModal.bind(this, 'settings')} <a className="maputnik-toolbar-skip" href="#skip-menu">
/> Skip navigation
<ExportModal </a>
mapStyle={this.props.mapStyle} <a
onStyleChanged={this.props.onStyleChanged} href="https://github.com/maputnik/editor"
isOpen={this.state.isOpen.export} rel="noopener noreferrer"
onOpenToggle={this.toggleModal.bind(this, 'export')} target="_blank"
/>
<OpenModal
isOpen={this.state.isOpen.open}
onStyleOpen={this.props.onStyleOpen}
onOpenToggle={this.toggleModal.bind(this, 'open')}
/>
<SourcesModal
mapStyle={this.props.mapStyle}
onStyleChanged={this.props.onStyleChanged}
isOpen={this.state.isOpen.sources}
onOpenToggle={this.toggleModal.bind(this, 'sources')}
/>
<ToolbarLink
href={"https://github.com/maputnik/editor"}
className="maputnik-toolbar-logo" className="maputnik-toolbar-logo"
> >
<img src={logoImage} alt="Maputnik" /> <img src={logoImage} alt="Maputnik" />
<h1>Maputnik</h1> <h1>
</ToolbarLink> <span className="maputnik-toolbar-name">{pkgJson.name}</span>
<ToolbarAction onClick={this.toggleModal.bind(this, 'open')}> <span className="maputnik-toolbar-version">v{pkgJson.version}</span>
<OpenIcon /> </h1>
</a>
</div>
<div className="maputnik-toolbar__actions">
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
<MdOpenInBrowser />
<IconText>Open</IconText> <IconText>Open</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'export')}> <ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
<MdFileDownload /> <MdFileDownload />
<IconText>Export</IconText> <IconText>Export</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'sources')}> <ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
<SourcesIcon /> <MdLayers />
<IconText>Sources</IconText> <IconText>Data Sources</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'settings')}> <ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
<SettingsIcon /> <MdSettings />
<IconText>Style Settings</IconText> <IconText>Style Settings</IconText>
</ToolbarAction> </ToolbarAction>
<ToolbarAction onClick={this.props.onInspectModeToggle}>
<InspectionIcon /> <ToolbarSelect wdKey="nav:inspect">
<IconText> <MdFindInPage />
{ this.props.inspectModeEnabled && <span>Map Mode</span> } <IconText>View </IconText>
{ !this.props.inspectModeEnabled && <span>Inspect Mode</span> } <select onChange={(e) => this.handleSelection(e.target.value)} value={currentView.id}>
</IconText> {views.map((item) => {
</ToolbarAction> return (
<option key={item.id} value={item.id} disabled={item.disabled}>
{item.title}
</option>
);
})}
</select>
</ToolbarSelect>
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}> <ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
<HelpIcon /> <MdHelpOutline />
<IconText>Help</IconText> <IconText>Help</IconText>
</ToolbarLink> </ToolbarLink>
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
<MdAssignmentTurnedIn />
<IconText>Take the Maputnik Survey</IconText>
</ToolbarLinkHighlighted>
</div>
</div>
</div> </div>
} }
} }
+44 -21
View File
@@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import Color from 'color' import Color from 'color'
import ChromePicker from 'react-color/lib/components/chrome/Chrome' import ChromePicker from 'react-color/lib/components/chrome/Chrome'
import PropTypes from 'prop-types'
function formatColor(color) { function formatColor(color) {
const rgb = color.rgb const rgb = color.rgb
@@ -10,26 +11,23 @@ function formatColor(color) {
/*** Number fields with support for min, max and units and documentation*/ /*** Number fields with support for min, max and units and documentation*/
class ColorField extends React.Component { class ColorField extends React.Component {
static propTypes = { static propTypes = {
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
name: React.PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: React.PropTypes.string, value: PropTypes.string,
doc: React.PropTypes.string, doc: PropTypes.string,
style: React.PropTypes.object, style: PropTypes.object,
default: React.PropTypes.string, default: PropTypes.string,
} }
constructor(props) { state = {
super(props) pickerOpened: false
this.state = {
pickerOpened: false,
}
} }
//TODO: I much rather would do this with absolute positioning //TODO: I much rather would do this with absolute positioning
//but I am too stupid to get it to work together with fixed position //but I am too stupid to get it to work together with fixed position
//and scrollbars so I have to fallback to JavaScript //and scrollbars so I have to fallback to JavaScript
calcPickerOffset() { calcPickerOffset = () => {
const elem = this.refs.colorInput const elem = this.colorInput
if(elem) { if(elem) {
const pos = elem.getBoundingClientRect() const pos = elem.getBoundingClientRect()
return { return {
@@ -37,7 +35,6 @@ class ColorField extends React.Component {
left: pos.left + 196, left: pos.left + 196,
} }
} else { } else {
console.warn('Color field has no element to adjust position')
return { return {
top: 160, top: 160,
left: 555, left: 555,
@@ -45,16 +42,36 @@ class ColorField extends React.Component {
} }
} }
togglePicker() { togglePicker = () => {
this.setState({ pickerOpened: !this.state.pickerOpened }) this.setState({ pickerOpened: !this.state.pickerOpened })
} }
get color() { get color() {
return Color(this.props.value || '#fff') // Catch invalid color.
try {
return Color(this.props.value).rgb()
}
catch(err) {
console.warn("Error parsing color: ", err);
return Color("rgb(255,255,255)");
}
}
onChange (v) {
this.props.onChange(v === "" ? undefined : v);
} }
render() { render() {
const offset = this.calcPickerOffset() const offset = this.calcPickerOffset()
var currentColor = this.color.object()
currentColor = {
r: currentColor.r,
g: currentColor.g,
b: currentColor.b,
// Rename alpha -> a for ChromePicker
a: currentColor.alpha
}
const picker = <div const picker = <div
className="maputnik-color-picker-offset" className="maputnik-color-picker-offset"
style={{ style={{
@@ -64,12 +81,12 @@ class ColorField extends React.Component {
top: offset.top, top: offset.top,
}}> }}>
<ChromePicker <ChromePicker
color={this.color.object()} color={currentColor}
onChange={c => this.props.onChange(formatColor(c))} onChange={c => this.props.onChange(formatColor(c))}
/> />
<div <div
className="maputnik-color-picker-offset" className="maputnik-color-picker-offset"
onClick={this.togglePicker.bind(this)} onClick={this.togglePicker}
style={{ style={{
zIndex: -1, zIndex: -1,
position: 'fixed', position: 'fixed',
@@ -81,17 +98,23 @@ class ColorField extends React.Component {
/> />
</div> </div>
var swatchStyle = {
backgroundColor: this.props.value
};
return <div className="maputnik-color-wrapper"> return <div className="maputnik-color-wrapper">
{this.state.pickerOpened && picker} {this.state.pickerOpened && picker}
<div className="maputnik-color-swatch" style={swatchStyle}></div>
<input <input
spellCheck="false"
className="maputnik-color" className="maputnik-color"
ref="colorInput" ref={(input) => this.colorInput = input}
onClick={this.togglePicker.bind(this)} onClick={this.togglePicker}
style={this.props.style} style={this.props.style}
name={this.props.name} name={this.props.name}
placeholder={this.props.default} placeholder={this.props.default}
value={this.props.value ? this.props.value : ""} value={this.props.value ? this.props.value : ""}
onChange={(e) => this.props.onChange(e.target.value)} onChange={(e) => this.onChange(e.target.value)}
/> />
</div> </div>
} }
+6 -5
View File
@@ -1,12 +1,13 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
export default class DocLabel extends React.Component { export default class DocLabel extends React.Component {
static propTypes = { static propTypes = {
label: React.PropTypes.oneOfType([ label: PropTypes.oneOfType([
React.PropTypes.object, PropTypes.object,
React.PropTypes.string PropTypes.string
]).isRequired, ]).isRequired,
doc: React.PropTypes.string.isRequired, doc: PropTypes.string.isRequired,
} }
render() { render() {
@@ -16,7 +17,7 @@ export default class DocLabel extends React.Component {
<div className="maputnik-doc-popup"> <div className="maputnik-doc-popup">
{this.props.doc} {this.props.doc}
</div> </div>
</div > </div>
</label> </label>
} }
} }
+151
View File
@@ -0,0 +1,151 @@
import React from 'react'
import PropTypes from 'prop-types'
import SpecProperty from './_SpecProperty'
import DataProperty from './_DataProperty'
import ZoomProperty from './_ZoomProperty'
function isZoomField(value) {
return typeof value === 'object' && value.stops && typeof value.property === 'undefined'
}
function isDataField(value) {
return typeof value === 'object' && value.stops && typeof value.property !== 'undefined'
}
/** Supports displaying spec field for zoom function objects
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
*/
export default class FunctionSpecProperty extends React.Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
fieldName: PropTypes.string.isRequired,
fieldSpec: PropTypes.object.isRequired,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
}
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") {
stops.push([
{zoom: lastStop[0].zoom + 1, value: lastStop[0].value},
lastStop[1]
])
}
else {
stops.push([lastStop[0] + 1, lastStop[1]])
}
const changedValue = {
...this.props.value,
stops: stops,
}
this.props.onChange(this.props.fieldName, changedValue)
}
deleteStop = (stopIdx) => {
const stops = this.props.value.stops.slice(0)
stops.splice(stopIdx, 1)
let changedValue = {
...this.props.value,
stops: stops,
}
if(stops.length === 1) {
changedValue = stops[0][1]
}
this.props.onChange(this.props.fieldName, changedValue)
}
makeZoomFunction = () => {
const zoomFunc = {
stops: [
[6, this.props.value],
[10, this.props.value]
]
}
this.props.onChange(this.props.fieldName, zoomFunc)
}
makeDataFunction = () => {
const functionType = this.getFieldFunctionType(this.props.fieldSpec);
const stopValue = functionType === 'categorical' ? '' : 0;
const dataFunc = {
property: "",
type: functionType,
stops: [
[{zoom: 6, value: stopValue}, this.props.value || stopValue],
[{zoom: 10, value: stopValue}, this.props.value || stopValue]
]
}
this.props.onChange(this.props.fieldName, dataFunc)
}
render() {
const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
let specField;
if (isZoomField(this.props.value)) {
specField = (
<ZoomProperty
onChange={this.props.onChange.bind(this)}
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onDeleteStop={this.deleteStop}
onAddStop={this.addStop}
/>
)
}
else if (isDataField(this.props.value)) {
specField = (
<DataProperty
onChange={this.props.onChange.bind(this)}
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onDeleteStop={this.deleteStop}
onAddStop={this.addStop}
/>
)
}
else {
specField = (
<SpecProperty
onChange={this.props.onChange.bind(this)}
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onZoomClick={this.makeZoomFunction}
onDataClick={this.makeDataFunction}
/>
)
}
return <div className={propClass} data-wd-key={"spec-field:"+this.props.fieldName}>
{specField}
</div>
}
}
+9 -8
View File
@@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import ZoomSpecField from './ZoomSpecField' import FunctionSpecField from './FunctionSpecField'
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image'] const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
/** Extract field spec by {@fieldName} from the {@layerType} in the /** Extract field spec by {@fieldName} from the {@layerType} in the
@@ -35,13 +36,13 @@ function getGroupName(spec, layerType, fieldName) {
export default class PropertyGroup extends React.Component { export default class PropertyGroup extends React.Component {
static propTypes = { static propTypes = {
layer: React.PropTypes.object.isRequired, layer: PropTypes.object.isRequired,
groupFields: React.PropTypes.array.isRequired, groupFields: PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
spec: React.PropTypes.object.isRequired, spec: PropTypes.object.isRequired,
} }
onPropertyChange(property, newValue) { onPropertyChange = (property, newValue) => {
const group = getGroupName(this.props.spec, this.props.layer.type, property) const group = getGroupName(this.props.spec, this.props.layer.type, property)
this.props.onChange(group , property, newValue) this.props.onChange(group , property, newValue)
} }
@@ -54,8 +55,8 @@ export default class PropertyGroup extends React.Component {
const layout = this.props.layer.layout || {} const layout = this.props.layer.layout || {}
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName] const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
return <ZoomSpecField return <FunctionSpecField
onChange={this.onPropertyChange.bind(this)} onChange={this.onPropertyChange}
key={fieldName} key={fieldName}
fieldName={fieldName} fieldName={fieldName}
value={fieldValue === undefined ? fieldSpec.default : fieldValue} value={fieldValue === undefined ? fieldSpec.default : fieldValue}
+28 -9
View File
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import color from 'color' import PropTypes from 'prop-types'
import ColorField from './ColorField' import ColorField from './ColorField'
import NumberInput from '../inputs/NumberInput' import NumberInput from '../inputs/NumberInput'
@@ -8,6 +8,7 @@ import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
import MultiButtonInput from '../inputs/MultiButtonInput' import MultiButtonInput from '../inputs/MultiButtonInput'
import ArrayInput from '../inputs/ArrayInput' import ArrayInput from '../inputs/ArrayInput'
import DynamicArrayInput from '../inputs/DynamicArrayInput'
import FontInput from '../inputs/FontInput' import FontInput from '../inputs/FontInput'
import IconInput from '../inputs/IconInput' import IconInput from '../inputs/IconInput'
import capitalize from 'lodash.capitalize' import capitalize from 'lodash.capitalize'
@@ -35,16 +36,17 @@ function optionsLabelLength(options) {
* to display @{value}. */ * to display @{value}. */
export default class SpecField extends React.Component { export default class SpecField extends React.Component {
static propTypes = { static propTypes = {
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
fieldName: React.PropTypes.string.isRequired, fieldName: PropTypes.string.isRequired,
fieldSpec: React.PropTypes.object.isRequired, fieldSpec: PropTypes.object.isRequired,
value: React.PropTypes.oneOfType([ value: PropTypes.oneOfType([
React.PropTypes.string, PropTypes.string,
React.PropTypes.number, PropTypes.number,
React.PropTypes.array, PropTypes.array,
PropTypes.bool
]), ]),
/** Override the style of the field */ /** Override the style of the field */
style: React.PropTypes.object, style: PropTypes.object,
} }
render() { render() {
@@ -55,6 +57,8 @@ export default class SpecField extends React.Component {
name: this.props.fieldName, name: this.props.fieldName,
onChange: newValue => this.props.onChange(this.props.fieldName, newValue) onChange: newValue => this.props.onChange(this.props.fieldName, newValue)
} }
function childNodes() {
switch(this.props.fieldSpec.type) { switch(this.props.fieldSpec.type) {
case 'number': return ( case 'number': return (
<NumberInput <NumberInput
@@ -77,6 +81,7 @@ export default class SpecField extends React.Component {
options={options} options={options}
/> />
} }
case 'formatted':
case 'string': case 'string':
if(iconProperties.indexOf(this.props.fieldName) >= 0) { if(iconProperties.indexOf(this.props.fieldName) >= 0) {
return <IconInput return <IconInput
@@ -105,13 +110,27 @@ export default class SpecField extends React.Component {
fonts={this.props.fieldSpec.values} fonts={this.props.fieldSpec.values}
/> />
} else { } else {
if (this.props.fieldSpec.length) {
return <ArrayInput return <ArrayInput
{...commonProps} {...commonProps}
type={this.props.fieldSpec.value} type={this.props.fieldSpec.value}
length={this.props.fieldSpec.length} length={this.props.fieldSpec.length}
/> />
} else {
return <DynamicArrayInput
{...commonProps}
type={this.props.fieldSpec.value}
/>
}
} }
default: return null default: return null
} }
} }
return (
<div data-wd-key={"spec-field:"+this.props.fieldName}>
{childNodes.call(this)}
</div>
);
}
} }
-180
View File
@@ -1,180 +0,0 @@
import React from 'react'
import Color from 'color'
import Button from '../Button'
import SpecField from './SpecField'
import NumberInput from '../inputs/NumberInput'
import DocLabel from './DocLabel'
import InputBlock from '../inputs/InputBlock'
import AddIcon from 'react-icons/lib/md/add-circle-outline'
import DeleteIcon from 'react-icons/lib/md/delete'
import FunctionIcon from 'react-icons/lib/md/functions'
import capitalize from 'lodash.capitalize'
function isZoomField(value) {
return typeof value === 'object' && value.stops
}
/** Supports displaying spec field for zoom function objects
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
*/
export default class ZoomSpecProperty extends React.Component {
static propTypes = {
onChange: React.PropTypes.func.isRequired,
fieldName: React.PropTypes.string.isRequired,
fieldSpec: React.PropTypes.object.isRequired,
value: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.bool,
]),
}
addStop() {
const stops = this.props.value.stops.slice(0)
const lastStop = stops[stops.length - 1]
stops.push([lastStop[0] + 1, lastStop[1]])
const changedValue = {
...this.props.value,
stops: stops,
}
this.props.onChange(this.props.fieldName, changedValue)
}
deleteStop(stopIdx) {
const stops = this.props.value.stops.slice(0)
stops.splice(stopIdx, 1)
let changedValue = {
...this.props.value,
stops: stops,
}
if(stops.length === 1) {
changedValue = stops[0][1]
}
this.props.onChange(this.props.fieldName, changedValue)
}
makeZoomFunction() {
const zoomFunc = {
stops: [
[6, this.props.value],
[10, this.props.value]
]
}
this.props.onChange(this.props.fieldName, zoomFunc)
}
changeStop(changeIdx, zoomLevel, value) {
const stops = this.props.value.stops.slice(0)
stops[changeIdx] = [zoomLevel, value]
const changedValue = {
...this.props.value,
stops: stops,
}
this.props.onChange(this.props.fieldName, changedValue)
}
renderZoomProperty() {
const zoomFields = this.props.value.stops.map((stop, idx) => {
const zoomLevel = stop[0]
const value = stop[1]
const deleteStopBtn= <DeleteStopButton onClick={this.deleteStop.bind(this, idx)} />
return <InputBlock
key={zoomLevel}
doc={this.props.fieldSpec.doc}
label={labelFromFieldName(this.props.fieldName)}
action={deleteStopBtn}
>
<div>
<div className="maputnik-zoom-spec-property-stop-edit">
<NumberInput
value={zoomLevel}
onChange={changedStop => this.changeStop(idx, changedStop, value)}
min={0}
max={22}
/>
</div>
<div className="maputnik-zoom-spec-property-stop-value">
<SpecField
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={value}
onChange={(_, newValue) => this.changeStop(idx, zoomLevel, newValue)}
/>
</div>
</div>
</InputBlock>
})
return <div className="maputnik-zoom-spec-property">
{zoomFields}
<Button
className="maputnik-add-stop"
onClick={this.addStop.bind(this)}
>
Add stop
</Button>
</div>
}
renderProperty() {
let zoomBtn = null
if(this.props.fieldSpec['zoom-function']) {
zoomBtn = <MakeZoomFunctionButton onClick={this.makeZoomFunction.bind(this)} />
}
return <InputBlock
doc={this.props.fieldSpec.doc}
label={labelFromFieldName(this.props.fieldName)}
action={zoomBtn}
>
<SpecField {...this.props} />
</InputBlock>
}
render() {
const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
return <div className={propClass}>
{isZoomField(this.props.value) ? this.renderZoomProperty() : this.renderProperty()}
</div>
}
}
function MakeZoomFunctionButton(props) {
return <Button
className="maputnik-make-zoom-function"
onClick={props.onClick}
>
<DocLabel
label={<FunctionIcon />}
cursorTargetStyle={{ cursor: 'pointer' }}
doc={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
/>
</Button>
}
function DeleteStopButton(props) {
return <Button
className="maputnik-delete-stop"
onClick={props.onClick}
>
<DocLabel
label={<DeleteIcon />}
doc={"Remove zoom level stop."}
/>
</Button>
}
function labelFromFieldName(fieldName) {
let label = fieldName.split('-').slice(1).join(' ')
return capitalize(label)
}
+182
View File
@@ -0,0 +1,182 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import SpecField from './SpecField'
import NumberInput from '../inputs/NumberInput'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import DocLabel from './DocLabel'
import InputBlock from '../inputs/InputBlock'
import labelFromFieldName from './_labelFromFieldName'
import DeleteStopButton from './_DeleteStopButton'
export default class DataProperty extends React.Component {
static propTypes = {
onChange: PropTypes.func,
onDeleteStop: PropTypes.func,
onAddStop: PropTypes.func,
fieldName: PropTypes.string,
fieldSpec: PropTypes.object,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
}
getFieldFunctionType(fieldSpec) {
if (fieldSpec.expression.interpolated) {
return "exponential"
}
if (fieldSpec.type === "number") {
return "interval"
}
return "categorical"
}
getDataFunctionTypes(fieldSpec) {
if (fieldSpec.expression.interpolated) {
return ["categorical", "interval", "exponential"]
}
else {
return ["categorical", "interval"]
}
}
changeStop(changeIdx, stopData, value) {
const stops = this.props.value.stops.slice(0)
const changedStop = stopData.zoom === undefined ? stopData.value : stopData
stops[changeIdx] = [changedStop, value]
const changedValue = {
...this.props.value,
stops: stops,
}
this.props.onChange(this.props.fieldName, changedValue)
}
changeDataProperty(propName, propVal) {
if (propVal) {
this.props.value[propName] = propVal
}
else {
delete this.props.value[propName]
}
this.props.onChange(this.props.fieldName, this.props.value)
}
render() {
if (typeof this.props.value.type === "undefined") {
this.props.value.type = this.getFieldFunctionType(this.props.fieldSpec)
}
const dataFields = this.props.value.stops.map((stop, idx) => {
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
const value = stop[1]
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
const dataProps = {
label: "Data value",
value: dataLevel,
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
}
let dataInput;
if(this.props.value.type === "categorical") {
dataInput = <StringInput {...dataProps} />
}
else {
dataInput = <NumberInput {...dataProps} />
}
let zoomInput = null;
if(zoomLevel !== undefined) {
zoomInput = <div className="maputnik-data-spec-property-stop-edit">
<NumberInput
value={zoomLevel}
onChange={newZoom => this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
min={0}
max={22}
/>
</div>
}
return <InputBlock key={idx} action={deleteStopBtn} label="">
{zoomInput}
<div className="maputnik-data-spec-property-stop-data">
{dataInput}
</div>
<div className="maputnik-data-spec-property-stop-value">
<SpecField
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={value}
onChange={(_, newValue) => this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
/>
</div>
</InputBlock>
})
return <div className="maputnik-data-spec-block">
<div className="maputnik-data-spec-property">
<InputBlock
doc={this.props.fieldSpec.doc}
label={labelFromFieldName(this.props.fieldName)}
>
<div className="maputnik-data-spec-property-group">
<DocLabel
label="Property"
doc={"Input a data property to base styles off of."}
/>
<div className="maputnik-data-spec-property-input">
<StringInput
value={this.props.value.property}
onChange={propVal => this.changeDataProperty("property", propVal)}
/>
</div>
</div>
<div className="maputnik-data-spec-property-group">
<DocLabel
label="Type"
doc={"Select a type of data scale (default is 'categorical')."}
/>
<div className="maputnik-data-spec-property-input">
<SelectInput
value={this.props.value.type}
onChange={propVal => this.changeDataProperty("type", propVal)}
options={this.getDataFunctionTypes(this.props.fieldSpec)}
/>
</div>
</div>
<div className="maputnik-data-spec-property-group">
<DocLabel
label="Default"
doc={"Input a default value for data if not covered by the scales."}
/>
<div className="maputnik-data-spec-property-input">
<SpecField
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value.default}
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
/>
</div>
</div>
</InputBlock>
</div>
{dataFields}
<Button
className="maputnik-add-stop"
onClick={this.props.onAddStop.bind(this)}
>
Add stop
</Button>
</div>
}
}
@@ -0,0 +1,25 @@
import React from 'react'
import PropTypes from 'prop-types'
import DocLabel from './DocLabel'
import Button from '../Button'
import {MdDelete} from 'react-icons/md'
export default class DeleteStopButton extends React.Component {
static propTypes = {
onClick: PropTypes.func,
}
render() {
return <Button
className="maputnik-delete-stop"
onClick={this.props.onClick}
>
<DocLabel
label={<MdDelete />}
doc={"Remove zoom level stop."}
/>
</Button>
}
}
@@ -0,0 +1,48 @@
import React from 'react'
import PropTypes from 'prop-types'
import DocLabel from './DocLabel'
import Button from '../Button'
import {MdFunctions, MdInsertChart} from 'react-icons/md'
export default class FunctionButtons extends React.Component {
static propTypes = {
fieldSpec: PropTypes.object,
onZoomClick: PropTypes.func,
onDataClick: PropTypes.func,
}
render() {
let makeZoomButton, makeDataButton
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
makeZoomButton = <Button
className="maputnik-make-zoom-function"
onClick={this.props.onZoomClick}
>
<DocLabel
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-type'] === 'data-driven') {
makeDataButton = <Button
className="maputnik-make-data-function"
onClick={this.props.onDataClick}
>
<DocLabel
label={<MdInsertChart />}
cursorTargetStyle={{ cursor: 'pointer' }}
doc={"Turn property into a data function to enable a map feature to change according to data properties and the map's zoom level."}
/>
</Button>
}
return <div>{makeDataButton}{makeZoomButton}</div>
}
else {
return null
}
}
}
+34
View File
@@ -0,0 +1,34 @@
import React from 'react'
import PropTypes from 'prop-types'
import SpecField from './SpecField'
import FunctionButtons from './_FunctionButtons'
import InputBlock from '../inputs/InputBlock'
import labelFromFieldName from './_labelFromFieldName'
export default class SpecProperty extends React.Component {
static propTypes = {
onZoomClick: PropTypes.func.isRequired,
onDataClick: PropTypes.func.isRequired,
fieldName: PropTypes.string,
fieldSpec: PropTypes.object
}
render() {
const functionBtn = <FunctionButtons
fieldSpec={this.props.fieldSpec}
onZoomClick={this.props.onZoomClick}
onDataClick={this.props.onDataClick}
/>
return <InputBlock
doc={this.props.fieldSpec.doc}
label={labelFromFieldName(this.props.fieldName)}
action={functionBtn}
>
<SpecField {...this.props} />
</InputBlock>
}
}
+163
View File
@@ -0,0 +1,163 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import SpecField from './SpecField'
import NumberInput from '../inputs/NumberInput'
import InputBlock from '../inputs/InputBlock'
import DeleteStopButton from './_DeleteStopButton'
import labelFromFieldName from './_labelFromFieldName'
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,
onDeleteStop: PropTypes.func,
onAddStop: PropTypes.func,
fieldName: PropTypes.string,
fieldSpec: PropTypes.object,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
}
state = {
refs: {}
}
componentDidMount() {
const newRefs = setStopRefs(this.props, this.state);
if(newRefs) {
this.setState({
refs: newRefs
})
}
}
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
.map((stop, idx) => {
return {
ref: this.state.refs[idx],
data: stop
}
})
// Sort by zoom
.sort((a, b) => sortNumerically(a.data[0], b.data[0]));
// Fetch the new position of the stops
const newRefs = {};
mappedWithRef
.forEach((stop, idx) =>{
newRefs[idx] = stop.ref;
})
this.setState({
refs: newRefs
});
return mappedWithRef.map((item) => item.data);
}
changeZoomStop(changeIdx, stopData, value) {
const stops = this.props.value.stops.slice(0);
stops[changeIdx] = [stopData, value];
const orderedStops = this.orderStopsByZoom(stops);
const changedValue = {
...this.props.value,
stops: orderedStops
}
this.props.onChange(this.props.fieldName, changedValue)
}
render() {
const zoomFields = this.props.value.stops.map((stop, idx) => {
const zoomLevel = stop[0]
const key = this.state.refs[idx];
const value = stop[1]
const deleteStopBtn= <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
return <InputBlock
key={key}
doc={this.props.fieldSpec.doc}
label={labelFromFieldName(this.props.fieldName)}
action={deleteStopBtn}
>
<div>
<div className="maputnik-zoom-spec-property-stop-edit">
<NumberInput
value={zoomLevel}
onChange={changedStop => this.changeZoomStop(idx, changedStop, value)}
min={0}
max={22}
/>
</div>
<div className="maputnik-zoom-spec-property-stop-value">
<SpecField
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={value}
onChange={(_, newValue) => this.changeZoomStop(idx, zoomLevel, newValue)}
/>
</div>
</div>
</InputBlock>
});
return <div className="maputnik-zoom-spec-property">
{zoomFields}
<Button
className="maputnik-add-stop"
onClick={this.props.onAddStop.bind(this)}
>
Add stop
</Button>
</div>
}
}
@@ -0,0 +1,6 @@
import capitalize from 'lodash.capitalize'
export default function labelFromFieldName(fieldName) {
let label = fieldName.split('-').slice(1).join(' ')
return capitalize(label)
}
+10 -11
View File
@@ -1,16 +1,14 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import { combiningFilterOps } from '../../libs/filterops.js' import { combiningFilterOps } from '../../libs/filterops.js'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import DocLabel from '../fields/DocLabel' import DocLabel from '../fields/DocLabel'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
import SingleFilterEditor from './SingleFilterEditor' import SingleFilterEditor from './SingleFilterEditor'
import FilterEditorBlock from './FilterEditorBlock' import FilterEditorBlock from './FilterEditorBlock'
import Button from '../Button' import Button from '../Button'
import DeleteIcon from 'react-icons/lib/md/delete'
import AddIcon from 'react-icons/lib/fa/plus'
function hasCombiningFilter(filter) { function hasCombiningFilter(filter) {
return combiningFilterOps.indexOf(filter[0]) >= 0 return combiningFilterOps.indexOf(filter[0]) >= 0
} }
@@ -26,9 +24,9 @@ function hasNestedCombiningFilter(filter) {
export default class CombiningFilterEditor extends React.Component { export default class CombiningFilterEditor extends React.Component {
static propTypes = { static propTypes = {
/** Properties of the vector layer and the available fields */ /** Properties of the vector layer and the available fields */
properties: React.PropTypes.object, properties: PropTypes.object,
filter: React.PropTypes.array, filter: PropTypes.array,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
// Convert filter to combining filter // Convert filter to combining filter
@@ -59,7 +57,7 @@ export default class CombiningFilterEditor extends React.Component {
this.props.onChange(newFilter) this.props.onChange(newFilter)
} }
addFilterItem() { addFilterItem = () => {
const newFilterItem = this.combiningFilter().slice(0) const newFilterItem = this.combiningFilter().slice(0)
newFilterItem.push(['==', 'name', '']) newFilterItem.push(['==', 'name', ''])
this.props.onChange(newFilterItem) this.props.onChange(newFilterItem)
@@ -88,10 +86,10 @@ export default class CombiningFilterEditor extends React.Component {
} }
return <div className="maputnik-filter-editor"> return <div className="maputnik-filter-editor">
<div className="maputnik-filter-editor-compound-select"> <div className="maputnik-filter-editor-compound-select" data-wd-key="layer-filter">
<DocLabel <DocLabel
label={"Compound Filter"} label={"Compound Filter"}
doc={GlSpec.layer.filter.doc + " Combine multiple filters together by using a compound filter."} doc={latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."}
/> />
<SelectInput <SelectInput
value={combiningOp} value={combiningOp}
@@ -102,8 +100,9 @@ export default class CombiningFilterEditor extends React.Component {
{editorBlocks} {editorBlocks}
<div className="maputnik-filter-editor-add-wrapper"> <div className="maputnik-filter-editor-add-wrapper">
<Button <Button
data-wd-key="layer-filter-button"
className="maputnik-add-filter" className="maputnik-add-filter"
onClick={this.addFilterItem.bind(this)}> onClick={this.addFilterItem}>
Add filter Add filter
</Button> </Button>
</div> </div>
+5 -4
View File
@@ -1,11 +1,12 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button' import Button from '../Button'
import DeleteIcon from 'react-icons/lib/md/delete' import {MdDelete} from 'react-icons/md'
class FilterEditorBlock extends React.Component { class FilterEditorBlock extends React.Component {
static propTypes = { static propTypes = {
onDelete: React.PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
children: React.PropTypes.element.isRequired, children: PropTypes.element.isRequired,
} }
render() { render() {
@@ -15,7 +16,7 @@ class FilterEditorBlock extends React.Component {
className="maputnik-delete-filter" className="maputnik-delete-filter"
onClick={this.props.onDelete} onClick={this.props.onDelete}
> >
<DeleteIcon /> <MdDelete />
</Button> </Button>
</div> </div>
<div className="maputnik-filter-editor-block-content"> <div className="maputnik-filter-editor-block-content">
+28 -4
View File
@@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import { otherFilterOps } from '../../libs/filterops.js' import { otherFilterOps } from '../../libs/filterops.js'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
@@ -11,11 +12,34 @@ function tryParseInt(v) {
return parseFloat(v) return parseFloat(v)
} }
function tryParseBool(v) {
const isString = (typeof(v) === "string");
if(!isString) {
return v;
}
if(v.match(/^\s*true\s*$/)) {
return true;
}
else if(v.match(/^\s*false\s*$/)) {
return false;
}
else {
return v;
}
}
function parseFilter(v) {
v = tryParseInt(v);
v = tryParseBool(v);
return v;
}
class SingleFilterEditor extends React.Component { class SingleFilterEditor extends React.Component {
static propTypes = { static propTypes = {
filter: React.PropTypes.array.isRequired, filter: PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
properties: React.PropTypes.object, properties: PropTypes.object,
} }
static defaultProps = { static defaultProps = {
@@ -23,7 +47,7 @@ class SingleFilterEditor extends React.Component {
} }
onFilterPartChanged(filterOp, propertyName, filterArgs) { onFilterPartChanged(filterOp, propertyName, filterArgs) {
let newFilter = [filterOp, propertyName, ...filterArgs.map(tryParseInt)] let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)]
if(filterOp === 'has' || filterOp === '!has') { if(filterOp === 'has' || filterOp === '!has') {
newFilter = [filterOp, propertyName] newFilter = [filterOp, propertyName]
} else if(filterArgs.length === 0) { } else if(filterArgs.length === 0) {
+5 -3
View File
@@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import LineIcon from './LineIcon.jsx' import LineIcon from './LineIcon.jsx'
import FillIcon from './FillIcon.jsx' import FillIcon from './FillIcon.jsx'
@@ -8,8 +9,8 @@ import CircleIcon from './CircleIcon.jsx'
class LayerIcon extends React.Component { class LayerIcon extends React.Component {
static propTypes = { static propTypes = {
type: React.PropTypes.string.isRequired, type: PropTypes.string.isRequired,
style: React.PropTypes.object, style: PropTypes.object,
} }
render() { render() {
@@ -17,12 +18,13 @@ class LayerIcon extends React.Component {
switch(this.props.type) { switch(this.props.type) {
case 'fill-extrusion': return <BackgroundIcon {...iconProps} /> case 'fill-extrusion': return <BackgroundIcon {...iconProps} />
case 'raster': return <FillIcon {...iconProps} /> case 'raster': return <FillIcon {...iconProps} />
case 'hillshade': return <FillIcon {...iconProps} />
case 'heatmap': return <FillIcon {...iconProps} />
case 'fill': return <FillIcon {...iconProps} /> case 'fill': return <FillIcon {...iconProps} />
case 'background': return <BackgroundIcon {...iconProps} /> case 'background': return <BackgroundIcon {...iconProps} />
case 'line': return <LineIcon {...iconProps} /> case 'line': return <LineIcon {...iconProps} />
case 'symbol': return <SymbolIcon {...iconProps} /> case 'symbol': return <SymbolIcon {...iconProps} />
case 'circle': return <CircleIcon {...iconProps} /> case 'circle': return <CircleIcon {...iconProps} />
default: return null
} }
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ export default class FillIcon extends React.Component {
render() { render() {
return ( return (
<IconBase viewBox="0 0 20 20" {...this.props}> <IconBase viewBox="0 0 20 20" {...this.props}>
<path id="path8" d="M 12.34,1.29 C 12.5114,1.1076 12.7497,1.0029 13,1 13.5523,1 14,1.4477 14,2 14.0047,2.2478 13.907,2.4866 13.73,2.66 9.785626,6.5516986 6.6148407,9.7551593 2.65,13.72 2.4793,13.8963 2.2453,13.9971 2,14 1.4477,14 1,13.5523 1,13 0.9953,12.7522 1.093,12.5134 1.27,12.34 4.9761967,8.7018093 9.0356422,4.5930579 12.34,1.29 Z" transform="translate(2,2)" /> <path d="M 12.34,1.29 C 12.5114,1.1076 12.7497,1.0029 13,1 13.5523,1 14,1.4477 14,2 14.0047,2.2478 13.907,2.4866 13.73,2.66 9.785626,6.5516986 6.6148407,9.7551593 2.65,13.72 2.4793,13.8963 2.2453,13.9971 2,14 1.4477,14 1,13.5523 1,13 0.9953,12.7522 1.093,12.5134 1.27,12.34 4.9761967,8.7018093 9.0356422,4.5930579 12.34,1.29 Z" transform="translate(2,2)" />
</IconBase> </IconBase>
) )
} }
+2 -2
View File
@@ -6,8 +6,8 @@ export default class SymbolIcon extends React.Component {
render() { render() {
return ( return (
<IconBase viewBox="0 0 20 20" {...this.props}> <IconBase viewBox="0 0 20 20" {...this.props}>
<g id="svg_1" transform="matrix(1.2718518,0,0,1.2601269,16.559526,-7.4065264)"> <g transform="matrix(1.2718518,0,0,1.2601269,16.559526,-7.4065264)">
<path id="svg_2" d="m -9.7959773,11.060163 c -0.3734787,-0.724437 -0.3580577,-1.2147051 -0.00547,-1.8767873 l 8.6034029,-0.019416 c 0.39670292,0.6865644 0.38365934,1.4750693 -0.011097,1.8864953 l -3.1359613,-0.0033 -0.013695,7.1305 c -0.4055357,0.397083 -1.3146432,0.397083 -1.7769191,-0.02274 l 0.030226,-7.104422 z" /> <path d="m -9.7959773,11.060163 c -0.3734787,-0.724437 -0.3580577,-1.2147051 -0.00547,-1.8767873 l 8.6034029,-0.019416 c 0.39670292,0.6865644 0.38365934,1.4750693 -0.011097,1.8864953 l -3.1359613,-0.0033 -0.013695,7.1305 c -0.4055357,0.397083 -1.3146432,0.397083 -1.7769191,-0.02274 l 0.030226,-7.104422 z" />
</g> </g>
</IconBase> </IconBase>
) )
+6 -5
View File
@@ -1,14 +1,15 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import StringInput from './StringInput' import StringInput from './StringInput'
import NumberInput from './NumberInput' import NumberInput from './NumberInput'
class ArrayInput extends React.Component { class ArrayInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.array, value: PropTypes.array,
type: React.PropTypes.string, type: PropTypes.string,
length: React.PropTypes.number, length: PropTypes.number,
default: React.PropTypes.array, default: PropTypes.array,
onChange: React.PropTypes.func, onChange: PropTypes.func,
} }
changeValue(idx, newValue) { changeValue(idx, newValue) {
+58 -10
View File
@@ -1,13 +1,21 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import Autocomplete from 'react-autocomplete' import Autocomplete from 'react-autocomplete'
const MAX_HEIGHT = 140;
class AutocompleteInput extends React.Component { class AutocompleteInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string, value: PropTypes.string,
options: React.PropTypes.array, options: PropTypes.array,
onChange: React.PropTypes.func, onChange: PropTypes.func,
keepMenuWithinWindowBounds: PropTypes.bool
}
state = {
maxHeight: MAX_HEIGHT
} }
static defaultProps = { static defaultProps = {
@@ -15,23 +23,62 @@ class AutocompleteInput extends React.Component {
options: [], options: [],
} }
render() { calcMaxHeight() {
const AutocompleteMenu = (items, value, style) => <div className={"maputnik-autocomplete-menu"} children={items} /> if(this.props.keepMenuWithinWindowBounds) {
const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
const limitedMaxHeight = Math.min(maxHeight, MAX_HEIGHT);
return <Autocomplete if(limitedMaxHeight != this.state.maxHeight) {
this.setState({
maxHeight: limitedMaxHeight
})
}
}
}
componentDidMount() {
this.calcMaxHeight();
}
componentDidUpdate() {
this.calcMaxHeight();
}
onChange (v) {
this.props.onChange(v === "" ? undefined : v);
}
render() {
return <div
ref={(el) => {
this.autocompleteMenuEl = el;
}}
>
<Autocomplete
menuStyle={{
position: "fixed",
overflow: "auto",
maxHeight: this.state.maxHeight,
zIndex: '998'
}}
wrapperProps={{ wrapperProps={{
className: "maputnik-autocomplete", className: "maputnik-autocomplete",
style: null style: null
}} }}
renderMenu={AutocompleteMenu}
inputProps={{ inputProps={{
className: "maputnik-string" className: "maputnik-string",
spellCheck: false
}} }}
value={this.props.value} value={this.props.value}
items={this.props.options} items={this.props.options}
getItemValue={(item) => item[0]} getItemValue={(item) => item[0]}
onSelect={v => this.props.onChange(v)} onSelect={v => this.onChange(v)}
onChange={(e, v) => this.props.onChange(v)} onChange={(e, v) => this.onChange(v)}
shouldItemRender={(item, value="") => {
if (typeof(value) === "string") {
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
}
}}
renderItem={(item, isHighlighted) => ( renderItem={(item, isHighlighted) => (
<div <div
key={item[0]} key={item[0]}
@@ -44,6 +91,7 @@ class AutocompleteInput extends React.Component {
</div> </div>
)} )}
/> />
</div>
} }
} }
+4 -3
View File
@@ -1,10 +1,11 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
class CheckboxInput extends React.Component { class CheckboxInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.bool.isRequired, value: PropTypes.bool.isRequired,
style: React.PropTypes.object, style: PropTypes.object,
onChange: React.PropTypes.func, onChange: PropTypes.func,
} }
render() { render() {
+105
View File
@@ -0,0 +1,105 @@
import React from 'react'
import PropTypes from 'prop-types'
import StringInput from './StringInput'
import NumberInput from './NumberInput'
import Button from '../Button'
import {MdDelete} from 'react-icons/md'
import DocLabel from '../fields/DocLabel'
class DynamicArrayInput extends React.Component {
static propTypes = {
value: PropTypes.array,
type: PropTypes.string,
default: PropTypes.array,
onChange: PropTypes.func,
style: PropTypes.object,
}
changeValue(idx, newValue) {
console.log(idx, newValue)
const values = this.values.slice(0)
values[idx] = newValue
this.props.onChange(values)
}
get values() {
return this.props.value || this.props.default || []
}
addValue = () => {
const values = this.values.slice(0)
if (this.props.type === 'number') {
values.push(0)
} else {
values.push("")
}
this.props.onChange(values)
}
deleteValue(valueIdx) {
const values = this.values.slice(0)
values.splice(valueIdx, 1)
this.props.onChange(values)
}
render() {
const inputs = this.values.map((v, i) => {
const deleteValueBtn= <DeleteValueButton onClick={this.deleteValue.bind(this, i)} />
const input = this.props.type === 'number'
? <NumberInput
value={v}
onChange={this.changeValue.bind(this, i)}
/>
: <StringInput
value={v}
onChange={this.changeValue.bind(this, i)}
/>
return <div
style={this.props.style}
key={i}
className="maputnik-array-block"
>
<div className="maputnik-array-block-action">
{deleteValueBtn}
</div>
<div className="maputnik-array-block-content">
{input}
</div>
</div>
})
return <div className="maputnik-array">
{inputs}
<Button
className="maputnik-array-add-value"
onClick={this.addValue}
>
Add value
</Button>
</div>
}
}
class DeleteValueButton extends React.Component {
static propTypes = {
onClick: PropTypes.func,
}
render() {
return <Button
className="maputnik-delete-stop"
onClick={this.props.onClick}
>
<DocLabel
label={<MdDelete />}
doc={"Remove array entry."}
/>
</Button>
}
}
export default DynamicArrayInput
+20 -6
View File
@@ -1,12 +1,14 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import AutocompleteInput from './AutocompleteInput' import AutocompleteInput from './AutocompleteInput'
class FontInput extends React.Component { class FontInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.array.isRequired, value: PropTypes.array.isRequired,
fonts: React.PropTypes.array, default: PropTypes.array,
style: React.PropTypes.object, fonts: PropTypes.array,
onChange: React.PropTypes.func.isRequired, style: PropTypes.object,
onChange: PropTypes.func.isRequired,
} }
static defaultProps = { static defaultProps = {
@@ -14,13 +16,25 @@ class FontInput extends React.Component {
} }
get values() { get values() {
return this.props.value || this.props.default.slice(1) || [] const out = this.props.value || this.props.default.slice(1) || [""];
// Always put a "" in the last field to you can keep adding entries
if (out[out.length-1] !== ""){
return out.concat("");
}
else {
return out;
}
} }
changeFont(idx, newValue) { changeFont(idx, newValue) {
const changedValues = this.values.slice(0) const changedValues = this.values.slice(0)
changedValues[idx] = newValue changedValues[idx] = newValue
this.props.onChange(changedValues) const filteredValues = changedValues
.filter(v => v !== undefined)
.filter(v => v !== "")
this.props.onChange(filteredValues);
} }
render() { render() {
+5 -4
View File
@@ -1,13 +1,14 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import AutocompleteInput from './AutocompleteInput' import AutocompleteInput from './AutocompleteInput'
class IconInput extends React.Component { class IconInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.array, value: PropTypes.string,
icons: React.PropTypes.array, icons: PropTypes.array,
style: React.PropTypes.object, style: PropTypes.object,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
static defaultProps = { static defaultProps = {
+12 -8
View File
@@ -1,27 +1,31 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import DocLabel from '../fields/DocLabel' import DocLabel from '../fields/DocLabel'
/** Wrap a component with a label */ /** Wrap a component with a label */
class InputBlock extends React.Component { class InputBlock extends React.Component {
static propTypes = { static propTypes = {
label: React.PropTypes.oneOfType([ "data-wd-key": PropTypes.string,
React.PropTypes.string, label: PropTypes.oneOfType([
React.PropTypes.element, PropTypes.string,
PropTypes.element,
]).isRequired, ]).isRequired,
doc: React.PropTypes.string, doc: PropTypes.string,
action: React.PropTypes.element, action: PropTypes.element,
children: React.PropTypes.element.isRequired, children: PropTypes.node.isRequired,
style: React.PropTypes.object, style: PropTypes.object,
onChange: PropTypes.func,
} }
onChange(e) { onChange(e) {
const value = e.target.value const value = e.target.value
return this.props.onChange(value === "" ? null: value) return this.props.onChange(value === "" ? undefined : value)
} }
render() { render() {
return <div style={this.props.style} return <div style={this.props.style}
data-wd-key={this.props["data-wd-key"]}
className={classnames({ className={classnames({
"maputnik-input-block": true, "maputnik-input-block": true,
"maputnik-action-block": this.props.action "maputnik-action-block": this.props.action
+4 -3
View File
@@ -1,12 +1,13 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import Button from '../Button' import Button from '../Button'
class MultiButtonInput extends React.Component { class MultiButtonInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string.isRequired, value: PropTypes.string.isRequired,
options: React.PropTypes.array.isRequired, options: PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
+20 -12
View File
@@ -1,34 +1,40 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
class NumberInput extends React.Component { class NumberInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.number, value: PropTypes.number,
default: React.PropTypes.number, default: PropTypes.number,
min: React.PropTypes.number, min: PropTypes.number,
max: React.PropTypes.number, max: PropTypes.number,
onChange: React.PropTypes.func, onChange: PropTypes.func,
} }
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
value: props.value editing: false,
value: props.value,
} }
} }
componentWillReceiveProps(nextProps) { static getDerivedStateFromProps(props, state) {
this.setState({ value: nextProps.value }) if (!state.editing) {
return {
value: props.value
};
}
} }
changeValue(newValue) { changeValue(newValue) {
this.setState({editing: true});
const value = parseFloat(newValue) const value = parseFloat(newValue)
const hasChanged = this.state.value !== value const hasChanged = this.state.value !== value
if(this.isValid(value) && hasChanged) { if(this.isValid(value) && hasChanged) {
this.props.onChange(value) this.props.onChange(value)
} else {
this.setState({ value: newValue })
} }
this.setState({ value: newValue })
} }
isValid(v) { isValid(v) {
@@ -48,7 +54,8 @@ class NumberInput extends React.Component {
return true return true
} }
resetValue() { resetValue = () => {
this.setState({editing: false});
// Reset explicitly to default value if value has been cleared // Reset explicitly to default value if value has been cleared
if(this.state.value === "") { if(this.state.value === "") {
return this.changeValue(this.props.default) return this.changeValue(this.props.default)
@@ -66,11 +73,12 @@ class NumberInput extends React.Component {
render() { render() {
return <input return <input
spellCheck="false"
className="maputnik-number" className="maputnik-number"
placeholder={this.props.default} placeholder={this.props.default}
value={this.state.value} value={this.state.value}
onChange={e => this.changeValue(e.target.value)} onChange={e => this.changeValue(e.target.value)}
onBlur={this.resetValue.bind(this)} onBlur={this.resetValue}
/> />
} }
} }
+7 -4
View File
@@ -1,11 +1,13 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
class SelectInput extends React.Component { class SelectInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string.isRequired, value: PropTypes.string.isRequired,
options: React.PropTypes.array.isRequired, "data-wd-key": PropTypes.string,
style: React.PropTypes.object, options: PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired, style: PropTypes.object,
onChange: PropTypes.func.isRequired,
} }
@@ -17,6 +19,7 @@ class SelectInput extends React.Component {
return <select return <select
className="maputnik-select" className="maputnik-select"
data-wd-key={this.props["data-wd-key"]}
style={this.props.style} style={this.props.style}
value={this.props.value} value={this.props.value}
onChange={e => this.props.onChange(e.target.value)} onChange={e => this.props.onChange(e.target.value)}
+51 -16
View File
@@ -1,35 +1,70 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
class StringInput extends React.Component { class StringInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string, "data-wd-key": PropTypes.string,
style: React.PropTypes.object, value: PropTypes.string,
default: React.PropTypes.string, style: PropTypes.object,
onChange: React.PropTypes.func, default: PropTypes.string,
onChange: PropTypes.func,
multi: PropTypes.bool,
} }
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
editing: false,
value: props.value || '' value: props.value || ''
} }
} }
componentWillReceiveProps(nextProps) { static getDerivedStateFromProps(props, state) {
this.setState({ value: nextProps.value || '' }) if (!state.editing) {
return {
value: props.value
};
}
} }
render() { render() {
return <input let tag;
className="maputnik-string" let classes;
style={this.props.style}
value={this.state.value} if(!!this.props.multi) {
placeholder={this.props.default} tag = "textarea"
onChange={e => this.setState({ value: e.target.value })} classes = [
onBlur={() => { "maputnik-string",
if(this.state.value!==this.props.value) this.props.onChange(this.state.value) "maputnik-string--multi"
}} ]
/> }
else {
tag = "input"
classes = [
"maputnik-string"
]
}
return React.createElement(tag, {
"data-wd-key": this.props["data-wd-key"],
spellCheck: !(tag === "input"),
className: classes.join(" "),
style: this.props.style,
value: this.state.value,
placeholder: this.props.default,
onChange: e => {
this.setState({
editing: true,
value: e.target.value
})
},
onBlur: () => {
if(this.state.value!==this.props.value) {
this.setState({editing: false});
this.props.onChange(this.state.value);
}
}
});
} }
} }
+34
View File
@@ -0,0 +1,34 @@
import React from 'react'
import PropTypes from 'prop-types'
import Collapse from 'react-collapse'
import accessibility from '../../libs/accessibility'
export default class CollapseAlt extends React.Component {
static propTypes = {
isActive: PropTypes.bool.isRequired,
children: PropTypes.element.isRequired
}
static defaultProps = {
isActive: true
}
render() {
if (accessibility.reducedMotionEnabled()) {
return (
<div style={{display: this.props.isActive ? "block" : "none"}}>
{this.props.children}
</div>
)
}
else {
return (
<Collapse isOpened={this.props.isActive}>
{this.props.children}
</Collapse>
)
}
}
}
+5 -5
View File
@@ -1,11 +1,11 @@
import React from 'react' import React from 'react'
import CollapseOpenIcon from 'react-icons/lib/md/arrow-drop-down' import PropTypes from 'prop-types'
import CollapseCloseIcon from 'react-icons/lib/md/arrow-drop-up' import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md'
export default class Collapser extends React.Component { export default class Collapser extends React.Component {
static propTypes = { static propTypes = {
isCollapsed: React.PropTypes.bool.isRequired, isCollapsed: PropTypes.bool.isRequired,
style: React.PropTypes.object, style: PropTypes.object,
} }
render() { render() {
@@ -14,7 +14,7 @@ export default class Collapser extends React.Component {
height: 20, height: 20,
...this.props.style, ...this.props.style,
} }
return this.props.isCollapsed ? <CollapseCloseIcon style={iconStyle}/> : <CollapseOpenIcon style={iconStyle} /> return this.props.isCollapsed ? <MdArrowDropUp style={iconStyle}/> : <MdArrowDropDown style={iconStyle} />
} }
} }
+29
View File
@@ -0,0 +1,29 @@
import React from 'react'
import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
class MetadataBlock extends React.Component {
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
}
render() {
return <InputBlock
label={"Comments"}
doc={"Comments for the current layer. This is non-standard and not in the spec."}
data-wd-key="layer-comment"
>
<StringInput
multi={true}
value={this.props.value}
onChange={this.props.onChange}
default="Comment..."
/>
</InputBlock>
}
}
export default MetadataBlock
+17 -18
View File
@@ -1,19 +1,25 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import CodeMirror from 'react-codemirror' import {Controlled as CodeMirror} from 'react-codemirror2'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import 'codemirror/mode/javascript/javascript' import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/addon/lint/lint.css'
import '../../codemirror-maputnik.css' import '../../codemirror-maputnik.css'
import jsonlint from 'jsonlint'
// This is mainly because of this issue <https://github.com/zaach/jsonlint/issues/57> also the API has changed, see comment in file
import '../../vendor/codemirror/addon/lint/json-lint'
class JSONEditor extends React.Component { class JSONEditor extends React.Component {
static propTypes = { static propTypes = {
layer: React.PropTypes.object.isRequired, layer: PropTypes.object.isRequired,
onChange: React.PropTypes.func, onChange: PropTypes.func,
} }
constructor(props) { constructor(props) {
@@ -23,21 +29,12 @@ class JSONEditor extends React.Component {
} }
} }
componentWillReceiveProps(nextProps) { componentDidUpdate(prevProps) {
if (prevProps.layer !== this.props.layer) {
this.setState({ 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) { onCodeUpdate(newCode) {
@@ -66,13 +63,15 @@ class JSONEditor extends React.Component {
tabSize: 2, tabSize: 2,
theme: 'maputnik', theme: 'maputnik',
viewportMargin: Infinity, viewportMargin: Infinity,
lineNumbers: false, lineNumbers: true,
lint: true,
gutters: ["CodeMirror-lint-markers"],
scrollbarStyle: "null", scrollbarStyle: "null",
} }
return <CodeMirror return <CodeMirror
value={this.state.code} value={this.state.code}
onChange={this.onCodeUpdate.bind(this)} onBeforeChange={(editor, data, value) => this.onCodeUpdate(value)}
onFocusChange={focused => focused ? true : this.resetValue()} onFocusChange={focused => focused ? true : this.resetValue()}
options={codeMirrorOptions} options={codeMirrorOptions}
/> />
+114 -22
View File
@@ -1,4 +1,6 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
import JSONEditor from './JSONEditor' import JSONEditor from './JSONEditor'
import FilterEditor from '../filter/FilterEditor' import FilterEditor from '../filter/FilterEditor'
@@ -8,20 +10,15 @@ import LayerTypeBlock from './LayerTypeBlock'
import LayerIdBlock from './LayerIdBlock' import LayerIdBlock from './LayerIdBlock'
import MinZoomBlock from './MinZoomBlock' import MinZoomBlock from './MinZoomBlock'
import MaxZoomBlock from './MaxZoomBlock' import MaxZoomBlock from './MaxZoomBlock'
import CommentBlock from './CommentBlock'
import LayerSourceBlock from './LayerSourceBlock' import LayerSourceBlock from './LayerSourceBlock'
import LayerSourceLayerBlock from './LayerSourceLayerBlock' import LayerSourceLayerBlock from './LayerSourceLayerBlock'
import InputBlock from '../inputs/InputBlock' import {MdMoreVert} from 'react-icons/md'
import MultiButtonInput from '../inputs/MultiButtonInput'
import { changeType, changeProperty } from '../../libs/layer' import { changeType, changeProperty } from '../../libs/layer'
import layout from '../../config/layout.json' import layout from '../../config/layout.json'
class UnsupportedLayer extends React.Component {
render() {
return <div></div>
}
}
function layoutGroups(layerType) { function layoutGroups(layerType) {
const layerGroup = { const layerGroup = {
@@ -42,12 +39,19 @@ function layoutGroups(layerType) {
/** Layer editor supporting multiple types of layers. */ /** Layer editor supporting multiple types of layers. */
export default class LayerEditor extends React.Component { export default class LayerEditor extends React.Component {
static propTypes = { static propTypes = {
layer: React.PropTypes.object.isRequired, layer: PropTypes.object.isRequired,
sources: React.PropTypes.object, sources: PropTypes.object,
vectorLayers: React.PropTypes.object, vectorLayers: PropTypes.object,
spec: React.PropTypes.object.isRequired, spec: PropTypes.object.isRequired,
onLayerChanged: React.PropTypes.func, onLayerChanged: PropTypes.func,
onLayerIdChange: React.PropTypes.func, onLayerIdChange: PropTypes.func,
onMoveLayer: PropTypes.func,
onLayerDestroy: PropTypes.func,
onLayerCopy: PropTypes.func,
onLayerVisibilityToggle: PropTypes.func,
isFirstLayer: PropTypes.bool,
isLastLayer: PropTypes.bool,
layerIndex: PropTypes.number,
} }
static defaultProps = { static defaultProps = {
@@ -57,7 +61,7 @@ export default class LayerEditor extends React.Component {
} }
static childContextTypes = { static childContextTypes = {
reactIconBase: React.PropTypes.object reactIconBase: PropTypes.object
} }
constructor(props) { constructor(props) {
@@ -72,18 +76,18 @@ export default class LayerEditor extends React.Component {
this.state = { editorGroups } this.state = { editorGroups }
} }
componentWillReceiveProps(nextProps) { static getDerivedStateFromProps(props, state) {
const additionalGroups = { ...this.state.editorGroups } const additionalGroups = { ...state.editorGroups }
layout[nextProps.layer.type].groups.forEach(group => { layout[props.layer.type].groups.forEach(group => {
if(!(group.title in additionalGroups)) { if(!(group.title in additionalGroups)) {
additionalGroups[group.title] = true additionalGroups[group.title] = true
} }
}) })
this.setState({ return {
editorGroups: additionalGroups editorGroups: additionalGroups
}) };
} }
getChildContext () { getChildContext () {
@@ -110,10 +114,21 @@ export default class LayerEditor extends React.Component {
} }
renderGroupType(type, fields) { renderGroupType(type, fields) {
let comment = ""
if(this.props.layer.metadata) {
comment = this.props.layer.metadata['maputnik:comment']
}
let sourceLayerIds;
if(this.props.sources.hasOwnProperty(this.props.layer.source)) {
sourceLayerIds = this.props.sources[this.props.layer.source].layers;
}
switch(type) { switch(type) {
case 'layer': return <div> case 'layer': return <div>
<LayerIdBlock <LayerIdBlock
value={this.props.layer.id} value={this.props.layer.id}
wdKey="layer-editor.layer-id"
onChange={newId => this.props.onLayerIdChange(this.props.layer.id, newId)} onChange={newId => this.props.onLayerIdChange(this.props.layer.id, newId)}
/> />
<LayerTypeBlock <LayerTypeBlock
@@ -126,8 +141,9 @@ export default class LayerEditor extends React.Component {
onChange={v => this.changeProperty(null, 'source', v)} onChange={v => this.changeProperty(null, 'source', v)}
/> />
} }
{this.props.layer.type !== 'raster' && this.props.layer.type !== 'background' && <LayerSourceLayerBlock {['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
sourceLayerIds={this.props.sources[this.props.layer.source]} <LayerSourceLayerBlock
sourceLayerIds={sourceLayerIds}
value={this.props.layer['source-layer']} value={this.props.layer['source-layer']}
onChange={v => this.changeProperty(null, 'source-layer', v)} onChange={v => this.changeProperty(null, 'source-layer', v)}
/> />
@@ -140,6 +156,10 @@ export default class LayerEditor extends React.Component {
value={this.props.layer.maxzoom} value={this.props.layer.maxzoom}
onChange={v => this.changeProperty(null, 'maxzoom', v)} onChange={v => this.changeProperty(null, 'maxzoom', v)}
/> />
<CommentBlock
value={comment}
onChange={v => this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)}
/>
</div> </div>
case 'filter': return <div> case 'filter': return <div>
<div className="maputnik-filter-editor-wrapper"> <div className="maputnik-filter-editor-wrapper">
@@ -160,16 +180,23 @@ export default class LayerEditor extends React.Component {
layer={this.props.layer} layer={this.props.layer}
onChange={this.props.onLayerChanged} onChange={this.props.onLayerChanged}
/> />
default: return null
} }
} }
moveLayer(offset) {
this.props.onMoveLayer({
oldIndex: this.props.layerIndex,
newIndex: this.props.layerIndex+offset
})
}
render() { render() {
const layerType = this.props.layer.type const layerType = this.props.layer.type
const groups = layoutGroups(layerType).filter(group => { const groups = layoutGroups(layerType).filter(group => {
return !(layerType === 'background' && group.type === 'source') return !(layerType === 'background' && group.type === 'source')
}).map(group => { }).map(group => {
return <LayerEditorGroup return <LayerEditorGroup
data-wd-key={group.title}
key={group.title} key={group.title}
title={group.title} title={group.title}
isActive={this.state.editorGroups[group.title]} isActive={this.state.editorGroups[group.title]}
@@ -179,8 +206,73 @@ export default class LayerEditor extends React.Component {
</LayerEditorGroup> </LayerEditorGroup>
}) })
const layout = this.props.layer.layout || {}
const items = {
delete: {
text: "Delete",
handler: () => this.props.onLayerDestroy(this.props.layer.id)
},
duplicate: {
text: "Duplicate",
handler: () => this.props.onLayerCopy(this.props.layer.id)
},
hide: {
text: (layout.visibility === "none") ? "Show" : "Hide",
handler: () => this.props.onLayerVisibilityToggle(this.props.layer.id)
},
moveLayerUp: {
text: "Move layer up",
// Not actually used...
disabled: this.props.isFirstLayer,
handler: () => this.moveLayer(-1)
},
moveLayerDown: {
text: "Move layer down",
// Not actually used...
disabled: this.props.isLastLayer,
handler: () => this.moveLayer(+1)
}
}
function handleSelection(id, event) {
event.stopPropagation;
items[id].handler();
}
return <div className="maputnik-layer-editor" return <div className="maputnik-layer-editor"
> >
<header>
<div className="layer-header">
<h2 className="layer-header__title">
Layer: {this.props.layer.id}
</h2>
<div className="layer-header__info">
<Wrapper
className='more-menu'
onSelection={handleSelection}
closeOnSelection={false}
>
<Button className='more-menu__button'>
<MdMoreVert className="more-menu__button__svg" />
</Button>
<Menu>
<ul className="more-menu__menu">
{Object.keys(items).map((id, idx) => {
const item = items[id];
return <li key={id}>
<MenuItem value={id} className='more-menu__menu__item'>
{item.text}
</MenuItem>
</li>
})}
</ul>
</Menu>
</Wrapper>
</div>
</div>
</header>
{groups} {groups}
</div> </div>
} }
+12 -6
View File
@@ -1,26 +1,32 @@
import React from 'react' import React from 'react'
import Collapse from 'react-collapse' import PropTypes from 'prop-types'
import Collapser from './Collapser' import Collapser from './Collapser'
import Collapse from './Collapse'
export default class LayerEditorGroup extends React.Component { export default class LayerEditorGroup extends React.Component {
static propTypes = { static propTypes = {
title: React.PropTypes.string.isRequired, "data-wd-key": PropTypes.string,
isActive: React.PropTypes.bool.isRequired, title: PropTypes.string.isRequired,
children: React.PropTypes.element.isRequired, isActive: PropTypes.bool.isRequired,
onActiveToggle: React.PropTypes.func.isRequired children: PropTypes.element.isRequired,
onActiveToggle: PropTypes.func.isRequired
} }
render() { render() {
return <div> return <div>
<div className="maputnik-layer-editor-group" <div className="maputnik-layer-editor-group"
data-wd-key={"layer-editor-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)} onClick={e => this.props.onActiveToggle(!this.props.isActive)}
> >
<span>{this.props.title}</span> <span>{this.props.title}</span>
<span style={{flexGrow: 1}} /> <span style={{flexGrow: 1}} />
<Collapser isCollapsed={this.props.isActive} /> <Collapser isCollapsed={this.props.isActive} />
</div> </div>
<Collapse isOpened={this.props.isActive}> <Collapse isActive={this.props.isActive}>
<div className="react-collapse-container">
{this.props.children} {this.props.children}
</div>
</Collapse> </Collapse>
</div> </div>
} }
+8 -4
View File
@@ -1,17 +1,21 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
class LayerIdBlock extends React.Component { class LayerIdBlock extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string.isRequired, value: PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired, wdKey: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
} }
render() { render() {
return <InputBlock label={"ID"} doc={GlSpec.layer.id.doc}> return <InputBlock label={"ID"} doc={latest.layer.id.doc}
data-wd-key={this.props.wdKey}
>
<StringInput <StringInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
+64 -63
View File
@@ -1,22 +1,19 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import cloneDeep from 'lodash.clonedeep'
import Button from '../Button'
import LayerListGroup from './LayerListGroup' import LayerListGroup from './LayerListGroup'
import LayerListItem from './LayerListItem' import LayerListItem from './LayerListItem'
import AddIcon from 'react-icons/lib/md/add-circle-outline'
import AddModal from '../modals/AddModal' import AddModal from '../modals/AddModal'
import style from '../../libs/style.js' import {SortableContainer} from 'react-sortable-hoc';
import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
const layerListPropTypes = { const layerListPropTypes = {
layers: React.PropTypes.array.isRequired, layers: PropTypes.array.isRequired,
selectedLayerIndex: React.PropTypes.number.isRequired, selectedLayerIndex: PropTypes.number.isRequired,
onLayersChange: React.PropTypes.func.isRequired, onLayersChange: PropTypes.func.isRequired,
onLayerSelect: React.PropTypes.func, onLayerSelect: PropTypes.func,
sources: React.PropTypes.object.isRequired, sources: PropTypes.object.isRequired,
} }
function layerPrefix(name) { function layerPrefix(name) {
@@ -38,52 +35,19 @@ function findClosestCommonPrefix(layers, idx) {
} }
// List of collapsible layer editors // List of collapsible layer editors
@SortableContainer
class LayerListContainer extends React.Component { class LayerListContainer extends React.Component {
static propTypes = {...layerListPropTypes} static propTypes = {...layerListPropTypes}
static defaultProps = { static defaultProps = {
onLayerSelect: () => {}, onLayerSelect: () => {},
} }
constructor(props) { state = {
super(props)
this.state = {
collapsedGroups: {}, collapsedGroups: {},
areAllGroupsExpanded: false,
isOpen: { isOpen: {
add: false, add: false,
} }
} }
}
onLayerDestroy(layerId) {
const remainingLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(remainingLayers, layerId)
remainingLayers.splice(idx, 1);
this.props.onLayersChange(remainingLayers)
}
onLayerCopy(layerId) {
const changedLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const clonedLayer = cloneDeep(changedLayers[idx])
clonedLayer.id = clonedLayer.id + "-copy"
changedLayers.splice(idx, 0, clonedLayer)
this.props.onLayersChange(changedLayers)
}
onLayerVisibilityToggle(layerId) {
const changedLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const layer = { ...changedLayers[idx] }
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
layer.layout = changedLayout
changedLayers[idx] = layer
this.props.onLayersChange(changedLayers)
}
toggleModal(modalName) { toggleModal(modalName) {
this.setState({ this.setState({
@@ -94,6 +58,31 @@ class LayerListContainer extends React.Component {
}) })
} }
toggleLayers = () => {
let idx=0
let newGroups=[]
this.groupedLayers().forEach(layers => {
const groupPrefix = layerPrefix(layers[0].id)
const lookupKey = [groupPrefix, idx].join('-')
if (layers.length > 1) {
newGroups[lookupKey] = this.state.areAllGroupsExpanded
}
layers.forEach((layer) => {
idx += 1
})
});
this.setState({
collapsedGroups: newGroups,
areAllGroupsExpanded: !this.state.areAllGroupsExpanded
})
}
groupedLayers() { groupedLayers() {
const groups = [] const groups = []
for (let i = 0; i < this.props.layers.length; i++) { for (let i = 0; i < this.props.layers.length; i++) {
@@ -135,9 +124,10 @@ class LayerListContainer extends React.Component {
const groupPrefix = layerPrefix(layers[0].id) const groupPrefix = layerPrefix(layers[0].id)
if(layers.length > 1) { if(layers.length > 1) {
const grp = <LayerListGroup const grp = <LayerListGroup
data-wd-key={[groupPrefix, idx].join('-')}
key={[groupPrefix, idx].join('-')} key={[groupPrefix, idx].join('-')}
title={groupPrefix} title={groupPrefix}
isActive={!this.isCollapsed(groupPrefix, idx)} isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)} onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
/> />
listItems.push(grp) listItems.push(grp)
@@ -145,9 +135,10 @@ class LayerListContainer extends React.Component {
layers.forEach((layer, idxInGroup) => { layers.forEach((layer, idxInGroup) => {
const groupIdx = findClosestCommonPrefix(this.props.layers, idx) const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
const listItem = <LayerListItem const listItem = <LayerListItem
className={classnames({ className={classnames({
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx), 'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1 'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1
})} })}
index={idx} index={idx}
@@ -157,9 +148,9 @@ class LayerListContainer extends React.Component {
visibility={(layer.layout || {}).visibility} visibility={(layer.layout || {}).visibility}
isSelected={idx === this.props.selectedLayerIndex} isSelected={idx === this.props.selectedLayerIndex}
onLayerSelect={this.props.onLayerSelect} onLayerSelect={this.props.onLayerSelect}
onLayerDestroy={this.onLayerDestroy.bind(this)} onLayerDestroy={this.props.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)} onLayerCopy={this.props.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)} onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
/> />
listItems.push(listItem) listItems.push(listItem)
idx += 1 idx += 1
@@ -177,11 +168,26 @@ class LayerListContainer extends React.Component {
<header className="maputnik-layer-list-header"> <header className="maputnik-layer-list-header">
<span className="maputnik-layer-list-header-title">Layers</span> <span className="maputnik-layer-list-header-title">Layers</span>
<span className="maputnik-space" /> <span className="maputnik-space" />
<Button <div className="maputnik-default-property">
<div className="maputnik-multibutton">
<button
id="skip-menu"
onClick={this.toggleLayers}
className="maputnik-button">
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
</button>
</div>
</div>
<div className="maputnik-default-property">
<div className="maputnik-multibutton">
<button
onClick={this.toggleModal.bind(this, 'add')} onClick={this.toggleModal.bind(this, 'add')}
className="maputnik-add-layer"> data-wd-key="layer-list:add-layer"
className="maputnik-button maputnik-button-selected">
Add Layer Add Layer
</Button> </button>
</div>
</div>
</header> </header>
<ul className="maputnik-layer-list-container"> <ul className="maputnik-layer-list-container">
{listItems} {listItems}
@@ -190,21 +196,16 @@ class LayerListContainer extends React.Component {
} }
} }
const LayerListContainerSortable = SortableContainer((props) => <LayerListContainer {...props} />)
export default class LayerList extends React.Component { export default class LayerList extends React.Component {
static propTypes = {...layerListPropTypes} static propTypes = {...layerListPropTypes}
onSortEnd(move) {
const { oldIndex, newIndex } = move
if(oldIndex === newIndex) return
let layers = this.props.layers.slice(0)
layers = arrayMove(layers, oldIndex, newIndex)
this.props.onLayersChange(layers)
}
render() { render() {
return <LayerListContainer return <LayerListContainerSortable
{...this.props} {...this.props}
onSortEnd={this.onSortEnd.bind(this)} helperClass='sortableHelper'
onSortEnd={this.props.onMoveLayer.bind(this)}
useDragHandle={true} useDragHandle={true}
/> />
} }
+9 -7
View File
@@ -1,17 +1,19 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Collapser from './Collapser' import Collapser from './Collapser'
export default class LayerEditorGroup extends React.Component { export default class LayerListGroup extends React.Component {
static propTypes = { static propTypes = {
title: React.PropTypes.string.isRequired, title: PropTypes.string.isRequired,
children: React.PropTypes.element.isRequired, "data-wd-key": PropTypes.string,
isActive: React.PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
onActiveToggle: React.PropTypes.func.isRequired onActiveToggle: PropTypes.func.isRequired
} }
render() { render() {
return <div className="maputnik-layer-list-group"> return <li className="maputnik-layer-list-group">
<div className="maputnik-layer-list-group-header" <div className="maputnik-layer-list-group-header"
data-wd-key={"layer-list-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)} onClick={e => this.props.onActiveToggle(!this.props.isActive)}
> >
<span className="maputnik-layer-list-group-title">{this.props.title}</span> <span className="maputnik-layer-list-group-title">{this.props.title}</span>
@@ -21,6 +23,6 @@ export default class LayerEditorGroup extends React.Component {
isCollapsed={this.props.isActive} isCollapsed={this.props.isActive}
/> />
</div> </div>
</div> </li>
} }
} }
+62 -46
View File
@@ -1,72 +1,77 @@
import React from 'react' import React from 'react'
import Color from 'color' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import CopyIcon from 'react-icons/lib/md/content-copy' import {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from 'react-icons/md'
import VisibilityIcon from 'react-icons/lib/md/visibility'
import VisibilityOffIcon from 'react-icons/lib/md/visibility-off'
import DeleteIcon from 'react-icons/lib/md/delete'
import LayerIcon from '../icons/LayerIcon' import LayerIcon from '../icons/LayerIcon'
import LayerEditor from './LayerEditor'
import {SortableElement, SortableHandle} from 'react-sortable-hoc' import {SortableElement, SortableHandle} from 'react-sortable-hoc'
@SortableHandle
class LayerTypeDragHandle extends React.Component {
static propTypes = LayerIcon.propTypes
render() { const DraggableLabel = SortableHandle((props) => {
return <LayerIcon return <div className="maputnik-layer-list-item-handle">
{...this.props} <LayerIcon
style={{ className="layer-handle__icon"
cursor: 'move', type={props.layerType}
width: 14,
height: 14,
paddingRight: 3,
}}
/> />
} <span className="maputnik-layer-list-item-id">{props.layerId}</span>
} </div>
});
class IconAction extends React.Component { class IconAction extends React.Component {
static propTypes = { static propTypes = {
action: React.PropTypes.string.isRequired, action: PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
wdKey: PropTypes.string,
classBlockName: PropTypes.string,
classBlockModifier: PropTypes.string,
} }
renderIcon() { renderIcon() {
switch(this.props.action) { switch(this.props.action) {
case 'copy': return <CopyIcon /> case 'duplicate': return <MdContentCopy />
case 'show': return <VisibilityIcon /> case 'show': return <MdVisibility />
case 'hide': return <VisibilityOffIcon /> case 'hide': return <MdVisibilityOff />
case 'delete': return <DeleteIcon /> case 'delete': return <MdDelete />
default: return null
} }
} }
render() { render() {
return <a const {classBlockName, classBlockModifier} = this.props;
className="maputnik-layer-list-icon-action"
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 ${classAdditions}`}
data-wd-key={this.props.wdKey}
onClick={this.props.onClick} onClick={this.props.onClick}
> >
{this.renderIcon()} {this.renderIcon()}
</a> </button>
} }
} }
@SortableElement
class LayerListItem extends React.Component { class LayerListItem extends React.Component {
static propTypes = { static propTypes = {
layerId: React.PropTypes.string.isRequired, layerId: PropTypes.string.isRequired,
layerType: React.PropTypes.string.isRequired, layerType: PropTypes.string.isRequired,
isSelected: React.PropTypes.bool, isSelected: PropTypes.bool,
visibility: React.PropTypes.string, visibility: PropTypes.string,
className: React.PropTypes.string, className: PropTypes.string,
onLayerSelect: React.PropTypes.func.isRequired, onLayerSelect: PropTypes.func.isRequired,
onLayerCopy: React.PropTypes.func, onLayerCopy: PropTypes.func,
onLayerDestroy: React.PropTypes.func, onLayerDestroy: PropTypes.func,
onLayerVisibilityToggle: React.PropTypes.func, onLayerVisibilityToggle: PropTypes.func,
} }
static defaultProps = { static defaultProps = {
@@ -78,7 +83,7 @@ class LayerListItem extends React.Component {
} }
static childContextTypes = { static childContextTypes = {
reactIconBase: React.PropTypes.object reactIconBase: PropTypes.object
} }
getChildContext() { getChildContext() {
@@ -88,31 +93,42 @@ class LayerListItem extends React.Component {
} }
render() { render() {
const visibilityAction = this.props.visibility === 'visible' ? 'show' : 'hide';
return <li return <li
key={this.props.layerId} key={this.props.layerId}
onClick={e => this.props.onLayerSelect(this.props.layerId)} onClick={e => this.props.onLayerSelect(this.props.layerId)}
data-wd-key={"layer-list-item:"+this.props.layerId}
className={classnames({ className={classnames({
"maputnik-layer-list-item": true, "maputnik-layer-list-item": true,
"maputnik-layer-list-item-selected": this.props.isSelected, "maputnik-layer-list-item-selected": this.props.isSelected,
[this.props.className]: true, [this.props.className]: true,
})}> })}>
<LayerTypeDragHandle type={this.props.layerType} /> <DraggableLabel {...this.props} />
<span className="maputnik-layer-list-item-id">{this.props.layerId}</span>
<span style={{flexGrow: 1}} /> <span style={{flexGrow: 1}} />
<IconAction <IconAction
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
action={'delete'} action={'delete'}
classBlockName="delete"
onClick={e => this.props.onLayerDestroy(this.props.layerId)} onClick={e => this.props.onLayerDestroy(this.props.layerId)}
/> />
<IconAction <IconAction
action={'copy'} wdKey={"layer-list-item:"+this.props.layerId+":copy"}
action={'duplicate'}
classBlockName="duplicate"
onClick={e => this.props.onLayerCopy(this.props.layerId)} onClick={e => this.props.onLayerCopy(this.props.layerId)}
/> />
<IconAction <IconAction
action={this.props.visibility === 'visible' ? 'hide' : 'show'} wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
action={visibilityAction}
classBlockName="visibility"
classBlockModifier={visibilityAction}
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)} onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)}
/> />
</li> </li>
} }
} }
export default LayerListItem; const LayerListItemSortable = SortableElement((props) => <LayerListItem {...props} />);
export default LayerListItemSortable;
+9 -7
View File
@@ -1,16 +1,16 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import AutocompleteInput from '../inputs/AutocompleteInput' import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceBlock extends React.Component { class LayerSourceBlock extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string, value: PropTypes.string,
onChange: React.PropTypes.func, wdKey: PropTypes.string,
sourceIds: React.PropTypes.array, onChange: PropTypes.func,
sourceIds: PropTypes.array,
} }
static defaultProps = { static defaultProps = {
@@ -19,7 +19,9 @@ class LayerSourceBlock extends React.Component {
} }
render() { render() {
return <InputBlock label={"Source"} doc={GlSpec.layer.source.doc}> return <InputBlock label={"Source"} doc={latest.layer.source.doc}
data-wd-key={this.props.wdKey}
>
<AutocompleteInput <AutocompleteInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
@@ -1,26 +1,30 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import AutocompleteInput from '../inputs/AutocompleteInput' import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceLayer extends React.Component { class LayerSourceLayer extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string, value: PropTypes.string,
onChange: React.PropTypes.func, onChange: PropTypes.func,
sourceLayerIds: React.PropTypes.array, sourceLayerIds: PropTypes.array,
isFixed: PropTypes.bool,
} }
static defaultProps = { static defaultProps = {
onChange: () => {}, onChange: () => {},
sourceLayerIds: [], sourceLayerIds: [],
isFixed: false
} }
render() { render() {
return <InputBlock label={"Source Layer"} doc={GlSpec.layer['source-layer'].doc}> return <InputBlock label={"Source Layer"} doc={latest.layer['source-layer'].doc}
data-wd-key="layer-source-layer"
>
<AutocompleteInput <AutocompleteInput
keepMenuWithinWindowBounds={!!this.props.isFixed}
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
options={this.props.sourceLayerIds.map(l => [l, l])} options={this.props.sourceLayerIds.map(l => [l, l])}
+10 -4
View File
@@ -1,17 +1,21 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
class LayerTypeBlock extends React.Component { class LayerTypeBlock extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.string.isRequired, value: PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired, wdKey: PropTypes.string,
onChange: PropTypes.func.isRequired,
} }
render() { render() {
return <InputBlock label={"Type"} doc={GlSpec.layer.type.doc}> return <InputBlock label={"Type"} doc={latest.layer.type.doc}
data-wd-key={this.props.wdKey}
>
<SelectInput <SelectInput
options={[ options={[
['background', 'Background'], ['background', 'Background'],
@@ -21,6 +25,8 @@ class LayerTypeBlock extends React.Component {
['raster', 'Raster'], ['raster', 'Raster'],
['circle', 'Circle'], ['circle', 'Circle'],
['fill-extrusion', 'Fill Extrusion'], ['fill-extrusion', 'Fill Extrusion'],
['hillshade', 'Hillshade'],
['heatmap', 'Heatmap'],
]} ]}
onChange={this.props.onChange} onChange={this.props.onChange}
value={this.props.value} value={this.props.value}
+10 -7
View File
@@ -1,23 +1,26 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput' import NumberInput from '../inputs/NumberInput'
class MaxZoomBlock extends React.Component { class MaxZoomBlock extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.number.isRequired, value: PropTypes.number,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
return <InputBlock label={"Max Zoom"} doc={GlSpec.layer.maxzoom.doc}> return <InputBlock label={"Max Zoom"} doc={latest.layer.maxzoom.doc}
data-wd-key="max-zoom"
>
<NumberInput <NumberInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
min={GlSpec.layer.maxzoom.minimum} min={latest.layer.maxzoom.minimum}
max={GlSpec.layer.maxzoom.maximum} max={latest.layer.maxzoom.maximum}
default={GlSpec.layer.maxzoom.maximum} default={latest.layer.maxzoom.maximum}
/> />
</InputBlock> </InputBlock>
} }
+10 -7
View File
@@ -1,23 +1,26 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput' import NumberInput from '../inputs/NumberInput'
class MinZoomBlock extends React.Component { class MinZoomBlock extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.number.isRequired, value: PropTypes.number,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
return <InputBlock label={"Min Zoom"} doc={GlSpec.layer.minzoom.doc}> return <InputBlock label={"Min Zoom"} doc={latest.layer.minzoom.doc}
data-wd-key="min-zoom"
>
<NumberInput <NumberInput
value={this.props.value} value={this.props.value}
onChange={this.props.onChange} onChange={this.props.onChange}
min={GlSpec.layer.minzoom.minimum} min={latest.layer.minzoom.minimum}
max={GlSpec.layer.minzoom.maximum} max={latest.layer.minzoom.maximum}
default={GlSpec.layer.minzoom.minimum} default={latest.layer.minzoom.minimum}
/> />
</InputBlock> </InputBlock>
} }
+95 -4
View File
@@ -1,35 +1,126 @@
import React from 'react' import React from 'react'
import InputBlock from '../inputs/InputBlock' import PropTypes from 'prop-types'
import StringInput from '../inputs/StringInput'
import LayerIcon from '../icons/LayerIcon' import LayerIcon from '../icons/LayerIcon'
import {latest, expression, function as styleFunction} from '@mapbox/mapbox-gl-style-spec'
function groupFeaturesBySourceLayer(features) { function groupFeaturesBySourceLayer(features) {
const sources = {} const sources = {}
let returnedFeatures = {};
features.forEach(feature => { features.forEach(feature => {
if(returnedFeatures.hasOwnProperty(feature.layer.id)) {
returnedFeatures[feature.layer.id]++
const featureObject = sources[feature.layer['source-layer']].find(f => f.layer.id === feature.layer.id)
featureObject.counter = returnedFeatures[feature.layer.id]
} else {
sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || [] sources[feature.layer['source-layer']] = sources[feature.layer['source-layer']] || []
sources[feature.layer['source-layer']].push(feature) sources[feature.layer['source-layer']].push(feature)
returnedFeatures[feature.layer.id] = 1
}
}) })
return sources return sources
} }
class FeatureLayerPopup extends React.Component { class FeatureLayerPopup extends React.Component {
static propTypes = {
onLayerSelect: PropTypes.func.isRequired,
features: PropTypes.array,
zoom: PropTypes.number,
}
_getFeatureColor(feature, zoom) {
// Guard because openlayers won't have this
if (!feature.layer.paint) {
return;
}
try {
const paintProps = feature.layer.paint;
let propName;
if(paintProps.hasOwnProperty("text-color") && paintProps["text-color"]) {
propName = "text-color";
}
else if (paintProps.hasOwnProperty("fill-color") && paintProps["fill-color"]) {
propName = "fill-color";
}
else if (paintProps.hasOwnProperty("line-color") && paintProps["line-color"]) {
propName = "line-color";
}
else if (paintProps.hasOwnProperty("fill-extrusion-color") && paintProps["fill-extrusion-color"]) {
propName = "fill-extrusion-color";
}
if(propName) {
const propertySpec = latest["paint_"+feature.layer.type][propName];
let color = feature.layer.paint[propName];
if(typeof(color) === "object") {
if(color.stops) {
color = styleFunction.convertFunction(color, propertySpec);
}
const exprResult = expression.createExpression(color, propertySpec);
const val = exprResult.value.evaluate({
zoom: zoom
}, feature);
return val.toString();
}
else {
return color;
}
}
else {
// Default color
return "black";
}
}
// This is quite complex, just incase there's an edgecase we're missing
// always return black if we get an unexpected error.
catch (err) {
console.error("Unable to get feature color, error:", err);
return "black";
}
}
render() { render() {
const sources = groupFeaturesBySourceLayer(this.props.features) const sources = groupFeaturesBySourceLayer(this.props.features)
const items = Object.keys(sources).map(vectorLayerId => { const items = Object.keys(sources).map(vectorLayerId => {
const layers = sources[vectorLayerId].map((feature, idx) => { const layers = sources[vectorLayerId].map((feature, idx) => {
return <label const featureColor = this._getFeatureColor(feature, this.props.zoom);
return <div
key={idx} key={idx}
className="maputnik-popup-layer" 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={{ <LayerIcon type={feature.layer.type} style={{
width: 14, width: 14,
height: 14, height: 14,
paddingRight: 3 paddingRight: 3
}}/> }}/>
}
{feature.layer.id} {feature.layer.id}
{feature.counter && <span> × {feature.counter}</span>}
</label> </label>
</div>
}) })
return <div key={vectorLayerId}> return <div key={vectorLayerId}>
<div className="maputnik-popup-layer-id">{vectorLayerId}</div> <div className="maputnik-popup-layer-id">{vectorLayerId}</div>
+30 -3
View File
@@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
@@ -21,8 +22,8 @@ function renderProperties(feature) {
} }
function renderFeature(feature) { function renderFeature(feature) {
return <div key={feature.id}> return <div key={`${feature.sourceLayer}-${feature.id}`}>
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}</div> <div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
<InputBlock key={"property-type"} label={"$type"}> <InputBlock key={"property-type"} label={"$type"}>
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} /> <StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
</InputBlock> </InputBlock>
@@ -30,10 +31,36 @@ function renderFeature(feature) {
</div> </div>
} }
function removeDuplicatedFeatures(features) {
let uniqueFeatures = [];
features.forEach(feature => {
const featureIndex = uniqueFeatures.findIndex(feature2 => {
return feature.layer['source-layer'] === feature2.layer['source-layer']
&& JSON.stringify(feature.properties) === JSON.stringify(feature2.properties)
})
if(featureIndex === -1) {
uniqueFeatures.push(feature)
} else {
if(uniqueFeatures[featureIndex].hasOwnProperty('inspectModeCounter')) {
uniqueFeatures[featureIndex].inspectModeCounter++
} else {
uniqueFeatures[featureIndex].inspectModeCounter = 2
}
}
})
return uniqueFeatures
}
class FeaturePropertyPopup extends React.Component { class FeaturePropertyPopup extends React.Component {
static propTypes = {
features: PropTypes.array
}
render() { render() {
const features = this.props.features const features = removeDuplicatedFeatures(this.props.features)
return <div className="maputnik-feature-property-popup"> return <div className="maputnik-feature-property-popup">
{features.map(renderFeature)} {features.map(renderFeature)}
</div> </div>
+93 -31
View File
@@ -1,28 +1,26 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import MapboxGl from 'mapbox-gl/dist/mapbox-gl.js' import MapboxGl from 'mapbox-gl'
import MapboxInspect from 'mapbox-gl-inspect' import MapboxInspect from 'mapbox-gl-inspect'
import FeatureLayerPopup from './FeatureLayerPopup' import FeatureLayerPopup from './FeatureLayerPopup'
import FeaturePropertyPopup from './FeaturePropertyPopup' import FeaturePropertyPopup from './FeaturePropertyPopup'
import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color'
import style from '../../libs/style.js'
import tokens from '../../config/tokens.json' import tokens from '../../config/tokens.json'
import colors from 'mapbox-gl-inspect/lib/colors' import colors from 'mapbox-gl-inspect/lib/colors'
import Color from 'color' import Color from 'color'
import ZoomControl from '../../libs/zoomcontrol'
import { colorHighlightedLayer } from '../../libs/highlight' import { colorHighlightedLayer } from '../../libs/highlight'
import 'mapbox-gl/dist/mapbox-gl.css' import 'mapbox-gl/dist/mapbox-gl.css'
import '../../mapboxgl.css' import '../../mapboxgl.css'
import '../../libs/mapbox-rtl'
function renderLayerPopup(features) {
var mountNode = document.createElement('div');
ReactDOM.render(<FeatureLayerPopup features={features} />, mountNode)
return mountNode.innerHTML;
}
function renderPropertyPopup(features) { const IS_SUPPORTED = MapboxGl.supported();
var mountNode = document.createElement('div');
ReactDOM.render(<FeaturePropertyPopup features={features} />, mountNode) function renderPopup(popup, mountNode) {
return mountNode.innerHTML; ReactDOM.render(popup, mountNode);
var content = mountNode.innerHTML;
return content;
} }
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) { function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
@@ -42,7 +40,7 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
const sources = {} const sources = {}
Object.keys(originalMapStyle.sources).forEach(sourceId => { Object.keys(originalMapStyle.sources).forEach(sourceId => {
const source = originalMapStyle.sources[sourceId] const source = originalMapStyle.sources[sourceId]
if(source.type !== 'raster') { if(source.type !== 'raster' && source.type !== 'raster-dem') {
sources[sourceId] = source sources[sourceId] = source
} }
}) })
@@ -57,16 +55,20 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
export default class MapboxGlMap extends React.Component { export default class MapboxGlMap extends React.Component {
static propTypes = { static propTypes = {
onDataChange: React.PropTypes.func, onDataChange: PropTypes.func,
mapStyle: React.PropTypes.object.isRequired, onLayerSelect: PropTypes.func.isRequired,
inspectModeEnabled: React.PropTypes.bool.isRequired, mapStyle: PropTypes.object.isRequired,
highlightedLayer: React.PropTypes.object, inspectModeEnabled: PropTypes.bool.isRequired,
highlightedLayer: PropTypes.object,
options: PropTypes.object,
} }
static defaultProps = { static defaultProps = {
onMapLoaded: () => {}, onMapLoaded: () => {},
onDataChange: () => {}, onDataChange: () => {},
onLayerSelect: () => {},
mapboxAccessToken: tokens.mapbox, mapboxAccessToken: tokens.mapbox,
options: {},
} }
constructor(props) { constructor(props) {
@@ -75,43 +77,78 @@ export default class MapboxGlMap extends React.Component {
this.state = { this.state = {
map: null, map: null,
inspect: null, inspect: null,
isPopupOpen: false,
popupX: 0,
popupY: 0,
} }
} }
componentWillReceiveProps(nextProps) { updateMapFromProps(props) {
if(!IS_SUPPORTED) return;
if(!this.state.map) return if(!this.state.map) return
const metadata = nextProps.mapStyle.metadata || {} const metadata = props.mapStyle.metadata || {}
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
if(!nextProps.inspectModeEnabled) { if(!props.inspectModeEnabled) {
//Mapbox GL now does diffing natively so we don't need to calculate //Mapbox GL now does diffing natively so we don't need to calculate
//the necessary operations ourselves! //the necessary operations ourselves!
this.state.map.setStyle(nextProps.mapStyle, { diff: true}) this.state.map.setStyle(props.mapStyle, { diff: true})
} }
} }
shouldComponentUpdate(nextProps, nextState) {
let should = false;
try {
should = JSON.stringify(this.props) !== JSON.stringify(nextProps) || JSON.stringify(this.state) !== JSON.stringify(nextState);
} catch(e) {
// no biggie, carry on
}
return should;
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if(!IS_SUPPORTED) return;
const map = this.state.map;
this.updateMapFromProps(this.props);
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) { if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
this.state.inspect.toggleInspector() this.state.inspect.toggleInspector()
} }
if(this.props.inspectModeEnabled) { if(this.props.inspectModeEnabled) {
this.state.inspect.render() 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() { componentDidMount() {
const map = new MapboxGl.Map({ if(!IS_SUPPORTED) return;
const mapOpts = {
...this.props.options,
container: this.container, container: this.container,
style: this.props.mapStyle, style: this.props.mapStyle,
hash: true, hash: true,
}) }
const nav = new MapboxGl.NavigationControl(); 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({visualizePitch:true});
map.addControl(nav, 'top-right'); map.addControl(nav, 'top-right');
const tmpNode = document.createElement('div');
const inspect = new MapboxInspect({ const inspect = new MapboxInspect({
popup: new MapboxGl.Popup({ popup: new MapboxGl.Popup({
closeOnClick: false closeOnClick: false
@@ -120,22 +157,30 @@ export default class MapboxGlMap extends React.Component {
showMapPopupOnHover: false, showMapPopupOnHover: false,
showInspectMapPopupOnHover: true, showInspectMapPopupOnHover: true,
showInspectButton: false, showInspectButton: false,
blockHoverPopupOnClick: true,
assignLayerColor: (layerId, alpha) => { assignLayerColor: (layerId, alpha) => {
return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string() return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string()
}, },
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer), buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
renderPopup: features => { renderPopup: features => {
if(this.props.inspectModeEnabled) { if(this.props.inspectModeEnabled) {
return renderPropertyPopup(features) return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
} else { } else {
return renderLayerPopup(features) return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.props.onLayerSelect} zoom={this.state.zoom} />, tmpNode);
} }
} }
}) })
map.addControl(inspect) map.addControl(inspect)
map.on("style.load", () => { map.on("style.load", () => {
this.setState({ map, inspect }); this.setState({
map,
inspect,
zoom: map.getZoom()
});
if(this.props.inspectModeEnabled) {
inspect.toggleInspector();
}
}) })
map.on("data", e => { map.on("data", e => {
@@ -144,12 +189,29 @@ export default class MapboxGlMap extends React.Component {
map: this.state.map map: this.state.map
}) })
}) })
map.on("zoom", e => {
this.setState({
zoom: map.getZoom()
});
})
} }
render() { render() {
if(IS_SUPPORTED) {
return <div return <div
className="maputnik-map" className="maputnik-map__map"
ref={x => this.container = x} ref={x => this.container = x}
></div> ></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>
}
}
} }
-155
View File
@@ -1,155 +0,0 @@
import React from 'react'
import style from '../../libs/style.js'
import isEqual from 'lodash.isequal'
import { loadJSON } from '../../libs/urlopen'
function suitableVectorSource(mapStyle) {
const sources = Object.keys(mapStyle.sources)
.map(sourceId => {
return {
id: sourceId,
source: mapStyle.sources[sourceId]
}
})
.filter(({source}) => source.type === 'vector')
return sources[0]
}
function toVectorLayer(source, tilegrid, cb) {
function newMVTLayer(tileUrl) {
const ol = require('openlayers')
return new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: new ol.format.MVT(),
tileGrid: tilegrid,
tilePixelRatio: 8,
url: tileUrl
})
})
}
if(!source.tiles) {
sourceFromTileJSON(source.url, tileSource => {
cb(newMVTLayer(tileSource.tiles[0]))
})
} else {
cb(newMVTLayer(source.tiles[0]))
}
}
function sourceFromTileJSON(url, cb) {
loadJSON(url, null, tilejson => {
if(!tilejson) return
cb({
type: 'vector',
tiles: tilejson.tiles,
minzoom: tilejson.minzoom,
maxzoom: tilejson.maxzoom,
})
})
}
class OpenLayers3Map extends React.Component {
static propTypes = {
onDataChange: React.PropTypes.func,
mapStyle: React.PropTypes.object.isRequired,
accessToken: React.PropTypes.string,
}
static defaultProps = {
onMapLoaded: () => {},
onDataChange: () => {},
}
constructor(props) {
super(props)
this.tilegrid = null
this.resolutions = null
this.layer = null
this.map = null
}
updateStyle(newMapStyle) {
const oldSource = suitableVectorSource(this.props.mapStyle)
const newSource = suitableVectorSource(newMapStyle)
const resolutions = this.resolutions
function setStyleFunc(map, layer) {
const olms = require('ol-mapbox-style')
const styleFunc = olms.getStyleFunction(newMapStyle, newSource.id, resolutions)
layer.setStyle(styleFunc)
//NOTE: We need to mark the source as changed in order
//to trigger a rerender
layer.getSource().changed()
map.render()
}
if(newSource) {
if(this.layer && !isEqual(oldSource, newSource)) {
this.map.removeLayer(this.layer)
this.layer = null
}
if(!this.layer) {
toVectorLayer(newSource.source, this.tilegrid, vectorLayer => {
this.layer = vectorLayer
this.map.addLayer(this.layer)
setStyleFunc(this.map, this.layer)
})
} else {
setStyleFunc(this.map, this.layer)
}
}
}
componentWillReceiveProps(nextProps) {
require.ensure(["openlayers", "ol-mapbox-style"], () => {
if(!this.map || !this.resolutions) return
this.updateStyle(nextProps.mapStyle)
})
}
componentDidMount() {
//Load OpenLayers dynamically once we need it
//TODO: Make this more convenient
require.ensure(["openlayers", "ol-mapbox-style"], ()=> {
console.log('Loaded OpenLayers3 renderer')
const ol = require('openlayers')
const olms = require('ol-mapbox-style')
this.tilegrid = ol.tilegrid.createXYZ({tileSize: 512, maxZoom: 22})
this.resolutions = this.tilegrid.getResolutions()
const map = new ol.Map({
target: this.container,
layers: [],
view: new ol.View({
zoom: 2,
center: [52.5, -78.4]
})
})
map.addControl(new ol.control.Zoom())
this.map = map
this.updateStyle(this.props.mapStyle)
})
}
render() {
return <div
ref={x => this.container = x}
style={{
position: "fixed",
top: 0,
right: 0,
bottom: 0,
height: "100%",
width: "75%",
backgroundColor: '#fff',
...this.props.style,
}}>
</div>
}
}
export default OpenLayers3Map
+167
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>
}
}
+85 -20
View File
@@ -1,9 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button' import Button from '../Button'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import Modal from './Modal' import Modal from './Modal'
import LayerTypeBlock from '../layers/LayerTypeBlock' import LayerTypeBlock from '../layers/LayerTypeBlock'
@@ -13,16 +11,16 @@ import LayerSourceLayerBlock from '../layers/LayerSourceLayerBlock'
class AddModal extends React.Component { class AddModal extends React.Component {
static propTypes = { static propTypes = {
layers: React.PropTypes.array.isRequired, layers: PropTypes.array.isRequired,
onLayersChange: React.PropTypes.func.isRequired, onLayersChange: PropTypes.func.isRequired,
isOpen: React.PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, onOpenToggle: PropTypes.func.isRequired,
// A dict of source id's and the available source layers // A dict of source id's and the available source layers
sources: React.PropTypes.object.isRequired, sources: PropTypes.object.isRequired,
} }
addLayer() { addLayer = () => {
const changedLayers = this.props.layers.slice(0) const changedLayers = this.props.layers.slice(0)
const layer = { const layer = {
id: this.state.id, id: this.state.id,
@@ -55,47 +53,114 @@ class AddModal extends React.Component {
} }
} }
componentWillReceiveProps(nextProps) { componentDidUpdate(prevProps, prevState) {
const sourceIds = Object.keys(nextProps.sources) // Check if source is valid for new type
if(!this.state.source && sourceIds.length > 0) { const oldType = prevState.type;
const newType = this.state.type;
const availableSourcesOld = this.getSources(oldType);
const availableSourcesNew = this.getSources(newType);
if(
// Type has changed
oldType !== newType
&& prevState.source !== ""
// Was a valid source previously
&& availableSourcesOld.indexOf(prevState.source) > -1
// And is not a valid source now
&& availableSourcesNew.indexOf(this.state.source) < 0
) {
// Clear the source
this.setState({ this.setState({
source: sourceIds[0], source: ""
'source-layer': this.state['source-layer'] || (nextProps.sources[sourceIds[0]] || [])[0] });
})
} }
} }
getLayersForSource(source) {
const sourceObj = this.props.sources[source] || {};
return sourceObj.layers || [];
}
getSources(type) {
const sources = [];
const types = {
vector: [
"fill",
"line",
"symbol",
"circle",
"fill-extrusion",
"heatmap"
],
raster: [
"raster"
],
geojson: [
"fill",
"line",
"symbol",
"circle",
"fill-extrusion",
"heatmap"
]
}
for(let [key, val] of Object.entries(this.props.sources)) {
if(types[val.type] && types[val.type].indexOf(type) > -1) {
sources.push(key);
}
}
return sources;
}
render() { render() {
const sources = this.getSources(this.state.type);
const layers = this.getLayersForSource(this.state.source);
return <Modal return <Modal
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Add Layer'} title={'Add Layer'}
data-wd-key="modal:add-layer"
> >
<div className="maputnik-add-layer"> <div className="maputnik-add-layer">
<LayerIdBlock <LayerIdBlock
value={this.state.id} value={this.state.id}
onChange={v => this.setState({ id: v })} wdKey="add-layer.layer-id"
onChange={v => {
this.setState({ id: v })
}}
/> />
<LayerTypeBlock <LayerTypeBlock
value={this.state.type} value={this.state.type}
wdKey="add-layer.layer-type"
onChange={v => this.setState({ type: v })} onChange={v => this.setState({ type: v })}
/> />
{this.state.type !== 'background' && {this.state.type !== 'background' &&
<LayerSourceBlock <LayerSourceBlock
sourceIds={Object.keys(this.props.sources)} sourceIds={sources}
wdKey="add-layer.layer-source-block"
value={this.state.source} value={this.state.source}
onChange={v => this.setState({ source: v })} onChange={v => this.setState({ source: v })}
/> />
} }
{this.state.type !== 'background' && this.state.type !== 'raster' && {['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
<LayerSourceLayerBlock <LayerSourceLayerBlock
sourceLayerIds={this.props.sources[this.state.source] || []} isFixed={true}
sourceLayerIds={layers}
value={this.state['source-layer']} value={this.state['source-layer']}
onChange={v => this.setState({ 'source-layer': v })} onChange={v => this.setState({ 'source-layer': v })}
/> />
} }
<Button className="maputnik-add-layer-button" onClick={this.addLayer.bind(this)}> <Button
className="maputnik-add-layer-button"
onClick={this.addLayer}
data-wd-key="add-layer"
>
Add Layer Add Layer
</Button> </Button>
</div> </div>
+53
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;
+57 -175
View File
@@ -1,178 +1,18 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import Slugify from 'slugify'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {format} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import CheckboxInput from '../inputs/CheckboxInput' import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button' import Button from '../Button'
import Modal from './Modal' import Modal from './Modal'
import MdFileDownload from 'react-icons/lib/md/file-download' import {MdFileDownload} from 'react-icons/md'
import style from '../../libs/style.js' import style from '../../libs/style'
import formatStyle from 'mapbox-gl-style-spec/lib/format'
import GitHub from 'github-api'
class Gist extends React.Component {
static propTypes = {
mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
preview: false,
saving: false,
latestGist: null,
}
}
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 && (this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token'];
const mapStyleStr = preview ?
formatStyle(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
formatStyle(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.28.0/mapbox-gl.css" />
<script src="https://api.mapbox.com/mapbox-gl-js/v0.28.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>
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: true,
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
})
}
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" 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';
return <p>
Latest saved gist:{' '}
{this.renderPreviewLink(this)}
<a target="_blank" href={"https://gist.github.com/"+user+"/"+gist.id}>Source</a>
</p>
}
}
render() {
return <div className="maputnik-export-gist">
<Button onClick={this.onSave.bind(this)}>
<MdFileDownload />
Save to Gist (anonymous)
</Button>
{' '}
<CheckboxInput
value={this.state.preview}
name='gist-style-preview'
onChange={this.onPreviewChange.bind(this)}
/>
<span> Include preview</span>
{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>
<a target="_blank" href="https://openmaptiles.com/hosting/">Get your free access token</a>
</div>
: null}
{this.renderLatestGist()}
</div>
}
}
function stripAccessTokens(mapStyle) { function stripAccessTokens(mapStyle) {
const changedMetadata = { ...mapStyle.metadata } const changedMetadata = { ...mapStyle.metadata }
@@ -186,10 +26,10 @@ function stripAccessTokens(mapStyle) {
class ExportModal extends React.Component { class ExportModal extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired, onStyleChanged: PropTypes.func.isRequired,
isOpen: React.PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, onOpenToggle: PropTypes.func.isRequired,
} }
constructor(props) { constructor(props) {
@@ -197,12 +37,36 @@ class ExportModal extends React.Component {
} }
downloadStyle() { downloadStyle() {
const blob = new Blob([formatStyle(stripAccessTokens(this.props.mapStyle))], {type: "application/json;charset=utf-8"}); const tokenStyle = format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle)));
saveAs(blob, this.props.mapStyle.id + ".json");
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
let exportName;
if(this.props.mapStyle.name) {
exportName = Slugify(this.props.mapStyle.name, {
replacement: '_',
lower: true
})
} else {
exportName = this.props.mapStyle.id
} }
saveAs(blob, exportName + ".json");
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
render() { render() {
return <Modal return <Modal
data-wd-key="export-modal"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Export Style'} title={'Export Style'}
@@ -213,16 +77,34 @@ class ExportModal extends React.Component {
<p> <p>
Download a JSON style to your computer. Download a JSON style to your computer.
</p> </p>
<p>
<InputBlock label={"MapTiler Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
</InputBlock>
<InputBlock label={"Mapbox Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/>
</InputBlock>
<InputBlock label={"Thunderforest Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
/>
</InputBlock>
</p>
<Button onClick={this.downloadStyle.bind(this)}> <Button onClick={this.downloadStyle.bind(this)}>
<MdFileDownload /> <MdFileDownload />
Download Download
</Button> </Button>
</div> </div>
<div className="maputnik-modal-section">
<h4>Save style</h4>
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
</div>
</Modal> </Modal>
} }
} }
+45
View File
@@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
class LoadingModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
message: PropTypes.node.isRequired,
}
underlayOnClick(e) {
// This stops click events falling through to underlying modals.
e.stopPropagation();
}
render() {
return <Modal
data-wd-key="loading-modal"
isOpen={this.props.isOpen}
underlayClickExits={false}
underlayProps={{
onClick: (e) => underlayProps(e)
}}
closeable={false}
title={this.props.title}
onOpenToggle={() => this.props.onCancel()}
>
<p>
{this.props.message}
</p>
<p className="maputnik-dialog__buttons">
<Button onClick={(e) => this.props.onCancel(e)}>
Cancel
</Button>
</p>
</Modal>
}
}
export default LoadingModal
+55 -12
View File
@@ -1,29 +1,72 @@
import React from 'react' import React from 'react'
import CloseIcon from 'react-icons/lib/md/close' import PropTypes from 'prop-types'
import Overlay from './Overlay' import {MdClose} from 'react-icons/md'
import AriaModal from 'react-aria-modal'
class Modal extends React.Component { class Modal extends React.Component {
static propTypes = { static propTypes = {
isOpen: React.PropTypes.bool.isRequired, "data-wd-key": PropTypes.string,
title: React.PropTypes.string.isRequired, isOpen: PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, title: PropTypes.string.isRequired,
onOpenToggle: PropTypes.func.isRequired,
children: PropTypes.node,
underlayClickExits: PropTypes.bool,
underlayProps: PropTypes.object,
}
static defaultProps = {
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');
} }
render() { render() {
return <Overlay isOpen={this.props.isOpen}> if(this.props.isOpen) {
<div className="maputnik-modal"> return <AriaModal
titleText={this.props.title}
underlayClickExits={this.props.underlayClickExits}
underlayProps={this.props.underlayProps}
getApplicationNode={this.getApplicationNode}
data-wd-key={this.props["data-wd-key"]}
verticallyCenter={true}
onExit={this.onClose}
>
<div className="maputnik-modal"
data-wd-key={this.props["data-wd-key"]}
>
<header className="maputnik-modal-header"> <header className="maputnik-modal-header">
<h1 className="maputnik-modal-header-title">{this.props.title}</h1> <h1 className="maputnik-modal-header-title">{this.props.title}</h1>
<span className="maputnik-modal-header-space"></span> <span className="maputnik-modal-header-space"></span>
<a className="maputnik-modal-header-toggle" <button className="maputnik-modal-header-toggle"
onClick={() => this.props.onOpenToggle(false)} onClick={this.onClose}
data-wd-key={this.props["data-wd-key"]+".close-modal"}
> >
<CloseIcon /> <MdClose />
</a> </button>
</header> </header>
<div className="maputnik-modal-scroller">
<div className="maputnik-modal-content">{this.props.children}</div> <div className="maputnik-modal-content">{this.props.children}</div>
</div> </div>
</Overlay> </div>
</AriaModal>
}
else {
return false;
}
} }
} }
+174 -33
View File
@@ -1,39 +1,42 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import LoadingModal from './LoadingModal'
import Modal from './Modal' import Modal from './Modal'
import Button from '../Button' import Button from '../Button'
import FileReaderInput from 'react-file-reader-input' import FileReaderInput from 'react-file-reader-input'
import request from 'request'
import FileUploadIcon from 'react-icons/lib/md/file-upload' import {MdFileUpload} from 'react-icons/md'
import AddIcon from 'react-icons/lib/md/add-circle-outline' import {MdAddCircleOutline} from 'react-icons/md'
import style from '../../libs/style.js' import style from '../../libs/style.js'
import publicStyles from '../../config/styles.json' import publicStyles from '../../config/styles.json'
class PublicStyle extends React.Component { class PublicStyle extends React.Component {
static propTypes = { static propTypes = {
url: React.PropTypes.string.isRequired, url: PropTypes.string.isRequired,
thumbnailUrl: React.PropTypes.string.isRequired, thumbnailUrl: PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired, title: PropTypes.string.isRequired,
onSelect: React.PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
} }
render() { render() {
return <div className="maputnik-public-style"> return <div className="maputnik-public-style">
<Button <Button
className="maputnik-public-style-button" className="maputnik-public-style-button"
aria-label={this.props.title}
onClick={() => this.props.onSelect(this.props.url)} onClick={() => this.props.onSelect(this.props.url)}
> >
<header className="maputnik-public-style-header"> <header className="maputnik-public-style-header">
<h4>{this.props.title}</h4> <h4>{this.props.title}</h4>
<span className="maputnik-space" /> <span className="maputnik-space" />
<AddIcon /> <MdAddCircleOutline />
</header> </header>
<img <div
className="maputnik-public-style-thumbnail" className="maputnik-public-style-thumbnail"
src={this.props.thumbnailUrl} style={{
alt={this.props.title} backgroundImage: `url(${this.props.thumbnailUrl})`
/> }}
></div>
</Button> </Button>
</div> </div>
} }
@@ -41,38 +44,128 @@ class PublicStyle extends React.Component {
class OpenModal extends React.Component { class OpenModal extends React.Component {
static propTypes = { static propTypes = {
isOpen: React.PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, onOpenToggle: PropTypes.func.isRequired,
onStyleOpen: React.PropTypes.func.isRequired, onStyleOpen: PropTypes.func.isRequired,
} }
onStyleSelect(styleUrl) { constructor(props) {
request({ super(props);
url: styleUrl, this.state = {
withCredentials: false, styleUrl: ""
}, (error, response, body) => { };
if (!error && response.statusCode == 200) {
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
console.log('Loaded style ', mapStyle.id)
this.props.onStyleOpen(mapStyle)
} else {
console.warn('Could not open the style URL', styleUrl)
} }
clearError() {
this.setState({
error: null
}) })
} }
onUpload(_, files) { onCancelActiveRequest(e) {
// Else the click propagates to the underlying modal
if(e) e.stopPropagation();
if(this.state.activeRequest) {
this.state.activeRequest.abort();
this.setState({
activeRequest: null,
activeRequestUrl: null
});
}
}
onStyleSelect = (styleUrl) => {
this.clearError();
let canceled;
const activeRequest = fetch(styleUrl, {
mode: 'cors',
credentials: "same-origin"
})
.then(function(response) {
return response.json();
})
.then((body) => {
if(canceled) {
return;
}
this.setState({
activeRequest: null,
activeRequestUrl: null
});
const mapStyle = style.ensureStyleValidity(body)
console.log('Loaded style ', mapStyle.id)
this.props.onStyleOpen(mapStyle)
this.onOpenToggle()
})
.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: {
abort: function() {
canceled = true;
}
},
activeRequestUrl: styleUrl
})
}
onOpenUrl = () => {
const url = this.styleUrlElement.value;
this.onStyleSelect(url);
}
onUpload = (_, files) => {
const [e, file] = files[0]; const [e, file] = files[0];
const reader = new FileReader(); const reader = new FileReader();
this.clearError();
reader.readAsText(file, "UTF-8"); reader.readAsText(file, "UTF-8");
reader.onload = e => { reader.onload = e => {
let mapStyle = JSON.parse(e.target.result) let mapStyle;
try {
mapStyle = JSON.parse(e.target.result)
}
catch(err) {
this.setState({
error: err.toString()
});
return;
}
mapStyle = style.ensureStyleValidity(mapStyle) mapStyle = style.ensureStyleValidity(mapStyle)
this.props.onStyleOpen(mapStyle); this.props.onStyleOpen(mapStyle);
this.onOpenToggle();
} }
reader.onerror = e => console.log(e.target); reader.onerror = e => console.log(e.target);
} }
onOpenToggle() {
this.setState({
styleUrl: ""
});
this.clearError();
this.props.onOpenToggle();
}
onChangeUrl = () => {
this.setState({
styleUrl: this.styleUrlElement.value
});
}
render() { render() {
const styleOptions = publicStyles.map(style => { const styleOptions = publicStyles.map(style => {
return <PublicStyle return <PublicStyle
@@ -80,23 +173,62 @@ class OpenModal extends React.Component {
url={style.url} url={style.url}
title={style.title} title={style.title}
thumbnailUrl={style.thumbnail} thumbnailUrl={style.thumbnail}
onSelect={this.onStyleSelect.bind(this)} onSelect={this.onStyleSelect}
/> />
}) })
return <Modal let errorElement;
if(this.state.error) {
errorElement = (
<div className="maputnik-modal-error">
{this.state.error}
<a href="#" onClick={() => this.clearError()} className="maputnik-modal-error-close">×</a>
</div>
);
}
return (
<div>
<Modal
data-wd-key="open-modal"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={() => this.onOpenToggle()}
title={'Open Style'} title={'Open Style'}
> >
{errorElement}
<section className="maputnik-modal-section"> <section className="maputnik-modal-section">
<h2>Upload Style</h2> <h2>Upload Style</h2>
<p>Upload a JSON style from your computer.</p> <p>Upload a JSON style from your computer.</p>
<FileReaderInput onChange={this.onUpload.bind(this)}> <FileReaderInput onChange={this.onUpload} tabIndex="-1">
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button> <Button className="maputnik-upload-button"><MdFileUpload /> Upload</Button>
</FileReaderInput> </FileReaderInput>
</section> </section>
<section className="maputnik-modal-section"> <section className="maputnik-modal-section">
<h2>Load from URL</h2>
<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..."
value={this.state.styleUrl}
onChange={this.onChangeUrl}
/>
<div>
<Button
data-wd-key="open-modal.url.button"
className="maputnik-big-button"
onClick={this.onOpenUrl}
disabled={this.state.styleUrl.length < 1}
>Open URL</Button>
</div>
</section>
<section className="maputnik-modal-section maputnik-modal-section--shrink">
<h2>Gallery Styles</h2> <h2>Gallery Styles</h2>
<p> <p>
Open one of the publicly available styles to start from. Open one of the publicly available styles to start from.
@@ -106,6 +238,15 @@ class OpenModal extends React.Component {
</div> </div>
</section> </section>
</Modal> </Modal>
<LoadingModal
isOpen={!!this.state.activeRequest}
title={'Loading style'}
onCancel={(e) => this.onCancelActiveRequest(e)}
message={"Loading: "+this.state.activeRequestUrl}
/>
</div>
)
} }
} }
-23
View File
@@ -1,23 +0,0 @@
import React from 'react'
class Overlay extends React.Component {
static propTypes = {
isOpen: React.PropTypes.bool.isRequired,
children: React.PropTypes.element.isRequired
}
render() {
let overlayStyle = {}
if(!this.props.isOpen) {
overlayStyle['display'] = 'none';
}
return <div className={"maputnik-overlay"} style={overlayStyle}>
<div className={"maputnik-overlay-viewport"} />
{this.props.children}
</div>
}
}
export default Overlay
+33 -28
View File
@@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
@@ -8,14 +9,11 @@ import Modal from './Modal'
class SettingsModal extends React.Component { class SettingsModal extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired, onStyleChanged: PropTypes.func.isRequired,
isOpen: React.PropTypes.bool.isRequired, onChangeMetadataProperty: PropTypes.func.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, isOpen: PropTypes.bool.isRequired,
} onOpenToggle: PropTypes.func.isRequired,
constructor(props) {
super(props);
} }
changeStyleProperty(property, value) { changeStyleProperty(property, value) {
@@ -26,47 +24,42 @@ class SettingsModal extends React.Component {
this.props.onStyleChanged(changedStyle) this.props.onStyleChanged(changedStyle)
} }
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
render() { render() {
const metadata = this.props.mapStyle.metadata || {} const metadata = this.props.mapStyle.metadata || {}
const {onChangeMetadataProperty} = this.props;
const inputProps = { } const inputProps = { }
return <Modal return <Modal
data-wd-key="modal-settings"
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle} onOpenToggle={this.props.onOpenToggle}
title={'Style Settings'} title={'Style Settings'}
> >
<div style={{minWidth: 350}}> <div style={{minWidth: 350}}>
<InputBlock label={"Name"} doc={GlSpec.$root.name.doc}> <InputBlock label={"Name"} doc={latest.$root.name.doc}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.name"
value={this.props.mapStyle.name} value={this.props.mapStyle.name}
onChange={this.changeStyleProperty.bind(this, "name")} onChange={this.changeStyleProperty.bind(this, "name")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Owner"} doc={"Owner ID of the style. Used by Mapbox or future style APIs."}> <InputBlock label={"Owner"} doc={"Owner ID of the style. Used by Mapbox or future style APIs."}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.owner"
value={this.props.mapStyle.owner} value={this.props.mapStyle.owner}
onChange={this.changeStyleProperty.bind(this, "owner")} onChange={this.changeStyleProperty.bind(this, "owner")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Sprite URL"} doc={GlSpec.$root.sprite.doc}> <InputBlock label={"Sprite URL"} doc={latest.$root.sprite.doc}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.sprite"
value={this.props.mapStyle.sprite} value={this.props.mapStyle.sprite}
onChange={this.changeStyleProperty.bind(this, "sprite")} onChange={this.changeStyleProperty.bind(this, "sprite")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Glyphs URL"} doc={GlSpec.$root.glyphs.doc}> <InputBlock label={"Glyphs URL"} doc={latest.$root.glyphs.doc}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.glyphs"
value={this.props.mapStyle.glyphs} value={this.props.mapStyle.glyphs}
onChange={this.changeStyleProperty.bind(this, "glyphs")} onChange={this.changeStyleProperty.bind(this, "glyphs")}
/> />
@@ -74,28 +67,40 @@ class SettingsModal extends React.Component {
<InputBlock label={"Mapbox Access Token"} doc={"Public access token for Mapbox services."}> <InputBlock label={"Mapbox Access Token"} doc={"Public access token for Mapbox services."}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:mapbox_access_token"
value={metadata['maputnik:mapbox_access_token']} value={metadata['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")} onChange={onChangeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"OpenMapTiles Access Token"} doc={"Public access token for the OpenMapTiles CDN."}> <InputBlock label={"MapTiler Access Token"} doc={"Public access token for MapTiler Cloud."}>
<StringInput {...inputProps} <StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
value={metadata['maputnik:openmaptiles_access_token']} value={metadata['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")} onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
</InputBlock>
<InputBlock label={"Thunderforest Access Token"} doc={"Public access token for Thunderforest services."}>
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:thunderforest_access_token"
value={metadata['maputnik:thunderforest_access_token']}
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}> <InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}>
<SelectInput {...inputProps} <SelectInput {...inputProps}
data-wd-key="modal-settings.maputnik:renderer"
options={[ options={[
['mbgljs', 'MapboxGL JS'], ['mbgljs', 'MapboxGL JS'],
['ol3', 'Open Layers 3'], ['ol', 'Open Layers (experimental)'],
]} ]}
value={metadata['maputnik:renderer'] || 'mbgljs'} value={metadata['maputnik:renderer'] || 'mbgljs'}
onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')} onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
/> />
</InputBlock> </InputBlock>
</div> </div>
</Modal> </Modal>
} }
+72
View File
@@ -0,0 +1,72 @@
import React from 'react'
import PropTypes from 'prop-types'
import Modal from './Modal'
class ShortcutsModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
render() {
const help = [
{
key: "?",
text: "Shortcuts menu"
},
{
key: "o",
text: "Open modal"
},
{
key: "e",
text: "Export modal"
},
{
key: "d",
text: "Data Sources modal"
},
{
key: "s",
text: "Style Settings modal"
},
{
key: "i",
text: "Toggle inspect"
},
{
key: "m",
text: "Focus map"
},
{
key: "!",
text: "Debug modal"
},
]
return <Modal
data-wd-key="shortcuts-modal"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Shortcuts'}
>
<div className="maputnik-modal-section maputnik-modal-shortcuts">
<p>
Press <code>ESC</code> to lose focus of any active elements, then press one of:
</p>
<ul>
{help.map((item) => {
return <li key={item.key}>
<code>{item.key}</code> {item.text}
</li>
})}
</ul>
</div>
</Modal>
}
}
export default ShortcutsModal
+40 -21
View File
@@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Modal from './Modal' import Modal from './Modal'
import Button from '../Button' import Button from '../Button'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
@@ -11,15 +12,14 @@ import style from '../../libs/style'
import { deleteSource, addSource, changeSource } from '../../libs/source' import { deleteSource, addSource, changeSource } from '../../libs/source'
import publicSources from '../../config/tilesets.json' import publicSources from '../../config/tilesets.json'
import AddIcon from 'react-icons/lib/md/add-circle-outline' import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
import DeleteIcon from 'react-icons/lib/md/delete'
class PublicSource extends React.Component { class PublicSource extends React.Component {
static propTypes = { static propTypes = {
id: React.PropTypes.string.isRequired, id: PropTypes.string.isRequired,
type: React.PropTypes.string.isRequired, type: PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired, title: PropTypes.string.isRequired,
onSelect: React.PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
} }
render() { render() {
@@ -33,7 +33,7 @@ class PublicSource extends React.Component {
<p className="maputnik-public-source-id">#{this.props.id}</p> <p className="maputnik-public-source-id">#{this.props.id}</p>
</div> </div>
<span className="maputnik-space" /> <span className="maputnik-space" />
<AddIcon /> <MdAddCircleOutline />
</Button> </Button>
</div> </div>
} }
@@ -44,6 +44,10 @@ function editorMode(source) {
if(source.tiles) return 'tilexyz_raster' if(source.tiles) return 'tilexyz_raster'
return 'tilejson_raster' return 'tilejson_raster'
} }
if(source.type === 'raster-dem') {
if(source.tiles) return 'tilexyz_raster-dem'
return 'tilejson_raster-dem'
}
if(source.type === 'vector') { if(source.type === 'vector') {
if(source.tiles) return 'tilexyz_vector' if(source.tiles) return 'tilexyz_vector'
return 'tilejson_vector' return 'tilejson_vector'
@@ -54,10 +58,10 @@ function editorMode(source) {
class ActiveSourceTypeEditor extends React.Component { class ActiveSourceTypeEditor extends React.Component {
static propTypes = { static propTypes = {
sourceId: React.PropTypes.string.isRequired, sourceId: PropTypes.string.isRequired,
source: React.PropTypes.object.isRequired, source: PropTypes.object.isRequired,
onDelete: React.PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
@@ -71,7 +75,7 @@ class ActiveSourceTypeEditor extends React.Component {
onClick={()=> this.props.onDelete(this.props.sourceId)} onClick={()=> this.props.onDelete(this.props.sourceId)}
style={{backgroundColor: 'transparent'}} style={{backgroundColor: 'transparent'}}
> >
<DeleteIcon /> <MdDelete />
</Button> </Button>
</div> </div>
<div className="maputnik-active-source-type-editor-content"> <div className="maputnik-active-source-type-editor-content">
@@ -87,7 +91,7 @@ class ActiveSourceTypeEditor extends React.Component {
class AddSource extends React.Component { class AddSource extends React.Component {
static propTypes = { static propTypes = {
onAdd: React.PropTypes.func.isRequired, onAdd: PropTypes.func.isRequired,
} }
constructor(props) { constructor(props) {
@@ -126,6 +130,16 @@ class AddSource extends React.Component {
minzoom: source.minzoom || 0, minzoom: source.minzoom || 0,
maxzoom: source.maxzoom || 14 maxzoom: source.maxzoom || 14
} }
case 'tilejson_raster-dem': return {
type: 'raster-dem',
url: source.url || 'http://localhost:3000/tilejson.json'
}
case 'tilexyz_raster-dem': return {
type: 'raster-dem',
tiles: source.tiles || ['http://localhost:3000/{x}/{y}/{z}.pbf'],
minzoom: source.minzoom || 0,
maxzoom: source.maxzoom || 14
}
default: return {} default: return {}
} }
} }
@@ -138,7 +152,7 @@ class AddSource extends React.Component {
onChange={v => this.setState({ sourceId: v})} onChange={v => this.setState({ sourceId: v})}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Source Type"} doc={GlSpec.source_tile.type.doc}> <InputBlock label={"Source Type"} doc={latest.source_vector.type.doc}>
<SelectInput <SelectInput
options={[ options={[
['geojson', 'GeoJSON'], ['geojson', 'GeoJSON'],
@@ -146,6 +160,8 @@ class AddSource extends React.Component {
['tilexyz_vector', 'Vector (XYZ URLs)'], ['tilexyz_vector', 'Vector (XYZ URLs)'],
['tilejson_raster', 'Raster (TileJSON URL)'], ['tilejson_raster', 'Raster (TileJSON URL)'],
['tilexyz_raster', 'Raster (XYZ URL)'], ['tilexyz_raster', 'Raster (XYZ URL)'],
['tilejson_raster-dem', 'Raster DEM (TileJSON URL)'],
['tilexyz_raster-dem', 'Raster DEM (XYZ URLs)'],
]} ]}
onChange={mode => this.setState({mode: mode, source: this.defaultSource(mode)})} onChange={mode => this.setState({mode: mode, source: this.defaultSource(mode)})}
value={this.state.mode} value={this.state.mode}
@@ -167,10 +183,10 @@ class AddSource extends React.Component {
class SourcesModal extends React.Component { class SourcesModal extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: PropTypes.object.isRequired,
isOpen: React.PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, onOpenToggle: PropTypes.func.isRequired,
onStyleChanged: React.PropTypes.func.isRequired, onStyleChanged: PropTypes.func.isRequired,
} }
stripTitle(source) { stripTitle(source) {
@@ -217,11 +233,14 @@ class SourcesModal extends React.Component {
<div className="maputnik-modal-section"> <div className="maputnik-modal-section">
<h4>Choose Public Source</h4> <h4>Choose Public Source</h4>
<p> <p>
Add one of the publicly availble sources to your style. Add one of the publicly available sources to your style.
</p> </p>
<div style={{maxwidth: 500}}> <div className="maputnik-public-sources" style={{maxwidth: 500}}>
{tilesetOptions} {tilesetOptions}
</div> </div>
<p>
<strong>Note:</strong> Some of the tilesets are not optimised for online use, and as a result the file sizes of the tiles can be quite large (heavy) for online vector rendering. Please review any tilesets before use.
</p>
</div> </div>
<div className="maputnik-modal-section"> <div className="maputnik-modal-section">
+39
View File
@@ -0,0 +1,39 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
import logoImage from 'maputnik-design/logos/logo-color.svg'
class SurveyModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
onClick = () => {
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
this.props.onOpenToggle();
}
render() {
return <Modal
data-wd-key="modal-survey"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title="Maputnik Survey"
>
<div className="maputnik-modal-survey">
<img className="maputnik-modal-survey__logo" src={logoImage} alt="" width="128" />
<h1>You + Maputnik = Maputnik better for you</h1>
<p className="maputnik-modal-survey__description">We dont track you, so we dont know how you use Maputnik. Help us make Maputnik better for you by completing a 7minute survey carried out by our contributing designer.</p>
<Button onClick={this.onClick} className="maputnik-big-button maputnik-white-button maputnik-wide-button">Take the Maputnik Survey</Button>
<p className="maputnik-modal-survey__footnote">It takes 7 minutes, tops! Every question is optional.</p>
</div>
</Modal>
}
}
export default SurveyModal
+37 -15
View File
@@ -1,17 +1,22 @@
import React from 'react' import React from 'react'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import NumberInput from '../inputs/NumberInput' import NumberInput from '../inputs/NumberInput'
import SelectInput from '../inputs/SelectInput'
class TileJSONSourceEditor extends React.Component { class TileJSONSourceEditor extends React.Component {
static propTypes = { static propTypes = {
source: React.PropTypes.object.isRequired, source: PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
children: PropTypes.node,
} }
render() { render() {
return <InputBlock label={"TileJSON URL"} doc={GlSpec.source_tile.url.doc}> return <div>
<InputBlock label={"TileJSON URL"} doc={latest.source_vector.url.doc}>
<StringInput <StringInput
value={this.props.source.url} value={this.props.source.url}
onChange={url => this.props.onChange({ onChange={url => this.props.onChange({
@@ -20,13 +25,16 @@ class TileJSONSourceEditor extends React.Component {
})} })}
/> />
</InputBlock> </InputBlock>
{this.props.children}
</div>
} }
} }
class TileURLSourceEditor extends React.Component { class TileURLSourceEditor extends React.Component {
static propTypes = { static propTypes = {
source: React.PropTypes.object.isRequired, source: PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
children: PropTypes.node,
} }
changeTileUrl(idx, value) { changeTileUrl(idx, value) {
@@ -42,7 +50,7 @@ class TileURLSourceEditor extends React.Component {
const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th'] const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th']
const tiles = this.props.source.tiles || [] const tiles = this.props.source.tiles || []
return tiles.map((tileUrl, tileIndex) => { return tiles.map((tileUrl, tileIndex) => {
return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={GlSpec.source_tile.tiles.doc}> return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={latest.source_vector.tiles.doc}>
<StringInput <StringInput
value={tileUrl} value={tileUrl}
onChange={this.changeTileUrl.bind(this, tileIndex)} onChange={this.changeTileUrl.bind(this, tileIndex)}
@@ -54,7 +62,7 @@ class TileURLSourceEditor extends React.Component {
render() { render() {
return <div> return <div>
{this.renderTileUrls()} {this.renderTileUrls()}
<InputBlock label={"Min Zoom"} doc={GlSpec.source_tile.minzoom.doc}> <InputBlock label={"Min Zoom"} doc={latest.source_vector.minzoom.doc}>
<NumberInput <NumberInput
value={this.props.source.minzoom || 0} value={this.props.source.minzoom || 0}
onChange={minzoom => this.props.onChange({ onChange={minzoom => this.props.onChange({
@@ -63,7 +71,7 @@ class TileURLSourceEditor extends React.Component {
})} })}
/> />
</InputBlock> </InputBlock>
<InputBlock label={"Max Zoom"} doc={GlSpec.source_tile.maxzoom.doc}> <InputBlock label={"Max Zoom"} doc={latest.source_vector.maxzoom.doc}>
<NumberInput <NumberInput
value={this.props.source.maxzoom || 22} value={this.props.source.maxzoom || 22}
onChange={maxzoom => this.props.onChange({ onChange={maxzoom => this.props.onChange({
@@ -72,6 +80,7 @@ class TileURLSourceEditor extends React.Component {
})} })}
/> />
</InputBlock> </InputBlock>
{this.props.children}
</div> </div>
} }
@@ -79,12 +88,12 @@ class TileURLSourceEditor extends React.Component {
class GeoJSONSourceEditor extends React.Component { class GeoJSONSourceEditor extends React.Component {
static propTypes = { static propTypes = {
source: React.PropTypes.object.isRequired, source: PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
return <InputBlock label={"GeoJSON Data"} doc={GlSpec.source_geojson.data.doc}> return <InputBlock label={"GeoJSON Data"} doc={latest.source_geojson.data.doc}>
<StringInput <StringInput
value={this.props.source.data} value={this.props.source.data}
onChange={data => this.props.onChange({ onChange={data => this.props.onChange({
@@ -98,9 +107,9 @@ class GeoJSONSourceEditor extends React.Component {
class SourceTypeEditor extends React.Component { class SourceTypeEditor extends React.Component {
static propTypes = { static propTypes = {
mode: React.PropTypes.string.isRequired, mode: PropTypes.string.isRequired,
source: React.PropTypes.object.isRequired, source: PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
render() { render() {
@@ -114,6 +123,19 @@ class SourceTypeEditor extends React.Component {
case 'tilexyz_vector': return <TileURLSourceEditor {...commonProps} /> case 'tilexyz_vector': return <TileURLSourceEditor {...commonProps} />
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} /> case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} /> case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
<InputBlock label={"Encoding"} doc={latest.source_raster_dem.encoding.doc}>
<SelectInput
options={Object.keys(latest.source_raster_dem.encoding.values)}
onChange={encoding => this.props.onChange({
...this.props.source,
encoding: encoding
})}
value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
/>
</InputBlock>
</TileURLSourceEditor>
default: return null default: return null
} }
} }
+4 -9
View File
@@ -1,14 +1,9 @@
{ {
"version": 8, "version": 8,
"name": "Empty Style", "name": "Empty Style",
"metadata": { "metadata": {},
"mapbox:autocomposite": false, "sources": {},
"mapbox:type": "template", "sprite": "",
"maputnik:renderer": "mbgljs", "glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
"openmaptiles:version": "3.x"
},
"sources": { },
"glyphs": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"sprites": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"layers": [] "layers": []
} }
+42 -6
View File
@@ -71,7 +71,8 @@
"fill-extrusion-translate-anchor", "fill-extrusion-translate-anchor",
"fill-extrusion-pattern", "fill-extrusion-pattern",
"fill-extrusion-height", "fill-extrusion-height",
"fill-extrusion-base" "fill-extrusion-base",
"fill-extrusion-vertical-gradient"
] ]
} }
] ]
@@ -91,7 +92,8 @@
"circle-stroke-width", "circle-stroke-width",
"circle-pitch-scale", "circle-pitch-scale",
"circle-translate", "circle-translate",
"circle-translate-anchor" "circle-translate-anchor",
"circle-pitch-alignment"
] ]
} }
] ]
@@ -104,7 +106,8 @@
"fields": [ "fields": [
"symbol-placement", "symbol-placement",
"symbol-spacing", "symbol-spacing",
"symbol-avoid-edges" "symbol-avoid-edges",
"symbol-z-order"
] ]
}, },
{ {
@@ -136,6 +139,7 @@
"title": "Icon layout properties", "title": "Icon layout properties",
"type": "properties", "type": "properties",
"fields": [ "fields": [
"icon-image",
"icon-allow-overlap", "icon-allow-overlap",
"icon-ignore-placement", "icon-ignore-placement",
"icon-optional", "icon-optional",
@@ -143,11 +147,12 @@
"icon-size", "icon-size",
"icon-text-fit", "icon-text-fit",
"icon-text-fit-padding", "icon-text-fit-padding",
"icon-image",
"icon-rotate", "icon-rotate",
"icon-padding", "icon-padding",
"icon-keep-upright", "icon-keep-upright",
"icon-offset" "icon-offset",
"icon-anchor",
"icon-pitch-alignment"
] ]
}, },
{ {
@@ -190,7 +195,38 @@
"raster-brightness-max", "raster-brightness-max",
"raster-saturation", "raster-saturation",
"raster-contrast", "raster-contrast",
"raster-fade-duration" "raster-fade-duration",
"raster-resampling"
]
}
]
},
"hillshade": {
"groups": [
{
"title": "Paint properties",
"type": "properties",
"fields": [
"hillshade-illumination-direction",
"hillshade-illumination-anchor",
"hillshade-exaggeration",
"hillshade-shadow-color",
"hillshade-highlight-color",
"hillshade-accent-color"
]
}
]
},
"heatmap": {
"groups": [
{
"title": "Paint properties",
"type": "properties",
"fields": [
"heatmap-radius",
"heatmap-weight",
"heatmap-intensity",
"heatmap-opacity"
] ]
} }
] ]
+41 -35
View File
@@ -2,61 +2,67 @@
{ {
"id": "klokantech-basic", "id": "klokantech-basic",
"title": "Klokantech Basic", "title": "Klokantech Basic",
"url": "https://rawgit.com/openmaptiles/klokantech-basic-gl-style/master/style.json", "url": "https://cdn.jsdelivr.net/gh/openmaptiles/klokantech-basic-gl-style@e142f83/style.json",
"thumbnail": "http://maputnik.com/thumbnails/klokantech-basic.png" "thumbnail": "https://maputnik.github.io/thumbnails/klokantech-basic.png"
}, },
{ {
"id": "dark-matter", "id": "dark-matter",
"title": "Dark Matter", "title": "Dark Matter",
"url": "https://rawgit.com/openmaptiles/dark-matter-gl-style/master/style.json", "url": "https://cdn.jsdelivr.net/gh/openmaptiles/dark-matter-gl-style@1dcc1d3/style.json",
"thumbnail": "http://maputnik.com/thumbnails/dark-matter.png" "thumbnail": "https://maputnik.github.io/thumbnails/dark-matter.png"
}, },
{ {
"id": "positron", "id": "positron",
"title": "Positron", "title": "Positron",
"url": "https://rawgit.com/openmaptiles/positron-gl-style/master/style.json", "url": "https://cdn.jsdelivr.net/gh/openmaptiles/positron-gl-style@2877814/style.json",
"thumbnail": "http://maputnik.com/thumbnails/positron.png" "thumbnail": "https://maputnik.github.io/thumbnails/positron.png"
}, },
{ {
"id": "osm-bright", "id": "osm-bright",
"title": "OSM Bright", "title": "OSM Bright",
"url": "https://rawgit.com/openmaptiles/osm-bright-gl-style/master/style.json", "url": "https://cdn.jsdelivr.net/gh/openmaptiles/osm-bright-gl-style@500e26e/style.json",
"thumbnail": "http://maputnik.com/thumbnails/osm-bright.png" "thumbnail": "https://maputnik.github.io/thumbnails/osm-bright.png"
},
{
"id": "toner-gl-style",
"title": "Toner",
"url": "https://cdn.jsdelivr.net/gh/openmaptiles/toner-gl-style@bb49571/style.json",
"thumbnail": "https://maputnik.github.io/thumbnails/toner.png"
}, },
{ {
"id": "osm-liberty", "id": "osm-liberty",
"title": "OSM Liberty", "title": "OSM Liberty",
"url": "https://rawgit.com/lukasmartinelli/osm-liberty/gh-pages/style.json", "url": "https://maputnik.github.io/osm-liberty/style.json",
"thumbnail": "https://cdn.rawgit.com/lukasmartinelli/osm-liberty/gh-pages/thumbnail.png" "thumbnail": "https://maputnik.github.io/osm-liberty/thumbnail.png"
},
{
"id": "os-zoomstack-outdoor",
"title": "Zoomstack Outdoor",
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-outdoor/style.json",
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-outdoor.png"
},
{
"id": "os-zoomstack-road",
"title": "Zoomstack Road",
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-road/style.json",
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-road.png"
},
{
"id": "os-zoomstack-light",
"title": "Zoomstack Light",
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-light/style.json",
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-light.png"
},
{
"id": "os-zoomstack-night",
"title": "Zoomstack Night",
"url": "https://s3-eu-west-1.amazonaws.com/tiles.os.uk/styles/open-zoomstack-night/style.json",
"thumbnail": "https://maputnik.github.io/thumbnails/os-zoomstack-night.png"
}, },
{ {
"id": "empty-style", "id": "empty-style",
"title": "Empty Style", "title": "Empty Style",
"url": "https://rawgit.com/maputnik/editor/master/src/config/empty-style.json", "url": "https://cdn.jsdelivr.net/gh/maputnik/editor@9cf74ca405d2be0608b57db8109cf3a6af5b9f49/src/config/empty-style.json",
"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII=" "thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII="
},
{
"id": "mapbox-satellite",
"title": "Mapbox Satellite",
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/satellite-v9.json",
"thumbnail": "http://maputnik.com/thumbnails/mapbox-satellite.png"
},
{
"id": "mapbox-bright",
"title": "Mapbox Bright",
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/bright-v9.json",
"thumbnail": "http://maputnik.com/thumbnails/mapbox-bright.png"
},
{
"id": "mapbox-basic",
"title": "Mapbox Basic",
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/basic-v9.json",
"thumbnail": "http://maputnik.com/thumbnails/mapbox-basic.png"
},
{
"id": "tilezen",
"title": "Tilezen",
"url": "https://rawgit.com/lukasmartinelli/tilezen-gl-style/master/style.json",
"thumbnail": "http://maputnik.com/thumbnails/tilezen.png"
} }
] ]
+14 -13
View File
@@ -1,21 +1,22 @@
{ {
"mapbox-streets": {
"type": "vector",
"url": "mapbox://mapbox.mapbox-streets-v7",
"title": "Mapbox Streets"
},
"openmaptiles": { "openmaptiles": {
"type": "vector", "type": "vector",
"url": "https://free.tilehosting.com/data/v3.json?key={key}", "url": "https://api.maptiler.com/tiles/v3/tiles.json?key={key}",
"title": "OpenMapTiles" "title": "OpenMapTiles"
}, },
"tilezen": { "thunderforest_transport": {
"type": "vector", "type": "vector",
"tiles": [ "url": "https://tile.thunderforest.com/thunderforest.transport-v1.json?apikey={key}",
"http://tile.mapzen.com/mapzen/vector/v1/{layers}/{z}/{x}/{y}.pbf?api_key=mapzen-RVcyVL7" "title": "Thunderforest Transport (heavy)"
], },
"minZoom": 0, "thunderforest_outdoors": {
"maxZoom": 15, "type": "vector",
"title": "Mapzen Vector Tile Service" "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"
} }
} }
+2 -1
View File
@@ -1,4 +1,5 @@
{ {
"mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w", "mapbox": "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6ImNpeHJmNXNmZTAwNHIycXBid2NqdTJibjMifQ.Dv1-GDpTWi0NP6xW9Fct1w",
"openmaptiles": "Og58UhhtiiTaLVlPtPgs" "openmaptiles": "KDhMfHvorAFkFe64wlZb",
"thunderforest": "b71f7f0ba4064f5eb9e903859a9cf5c6"
} }
+7 -1
View File
@@ -1,3 +1,4 @@
import { IconContext } from "react-icons";
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
@@ -5,4 +6,9 @@ import './favicon.ico'
import './styles/index.scss' import './styles/index.scss'
import App from './components/App'; import App from './components/App';
ReactDOM.render(<App/>, document.querySelector("#app")); ReactDOM.render(
<IconContext.Provider value={{className: 'react-icons'}}>
<App/>
</IconContext.Provider>,
document.querySelector("#app")
);
+12
View File
@@ -0,0 +1,12 @@
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 = throttle(() => {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
}, 3000);
export default {
reducedMotionEnabled
}
+34 -20
View File
@@ -1,33 +1,37 @@
import request from 'request'
import style from './style.js' import style from './style.js'
import ReconnectingWebSocket from 'reconnecting-websocket' import ReconnectingWebSocket from 'reconnecting-websocket'
const host = 'localhost'
const port = '8000'
const localUrl = `http://${host}:${port}`
const websocketUrl = `ws://${host}:${port}/ws`
export class ApiStyleStore { export class ApiStyleStore {
constructor(opts) { constructor(opts) {
this.onLocalStyleChange = opts.onLocalStyleChange || (() => {}) this.onLocalStyleChange = opts.onLocalStyleChange || (() => {})
const port = opts.port || '8000'
const host = opts.host || 'localhost'
this.localUrl = `http://${host}:${port}`
this.websocketUrl = `ws://${host}:${port}/ws`
this.init = this.init.bind(this)
} }
init(cb) { init(cb) {
request(localUrl + '/styles', (error, response, body) => { fetch(this.localUrl + '/styles', {
if (!error && body && response.statusCode == 200) { mode: 'cors',
const styleIds = JSON.parse(body) })
.then((response) => {
return response.json();
})
.then((body) => {
const styleIds = body;
this.latestStyleId = styleIds[0] this.latestStyleId = styleIds[0]
this.notifyLocalChanges() this.notifyLocalChanges()
cb(null) cb(null)
} else { })
.catch(function(e) {
cb(new Error('Can not connect to style API')) cb(new Error('Can not connect to style API'))
}
}) })
} }
notifyLocalChanges() { notifyLocalChanges() {
const connection = new ReconnectingWebSocket(websocketUrl) const connection = new ReconnectingWebSocket(this.websocketUrl)
connection.onmessage = e => { connection.onmessage = e => {
if(!e.data) return if(!e.data) return
console.log('Received style update from API') console.log('Received style update from API')
@@ -44,8 +48,14 @@ export class ApiStyleStore {
latestStyle(cb) { latestStyle(cb) {
if(this.latestStyleId) { if(this.latestStyleId) {
request(localUrl + '/styles/' + this.latestStyleId, (error, response, body) => { fetch(this.localUrl + '/styles/' + this.latestStyleId, {
cb(style.ensureStyleValidity(JSON.parse(body))) mode: 'cors',
})
.then(function(response) {
return response.json();
})
.then(function(body) {
cb(style.ensureStyleValidity(body))
}) })
} else { } else {
throw new Error('No latest style available. You need to init the api backend first.') throw new Error('No latest style available. You need to init the api backend first.')
@@ -55,11 +65,15 @@ export class ApiStyleStore {
// Save current style replacing previous version // Save current style replacing previous version
save(mapStyle) { save(mapStyle) {
const id = mapStyle.id const id = mapStyle.id
request.put({ fetch(this.localUrl + '/styles/' + id, {
url: localUrl + '/styles/' + id, method: "PUT",
json: true, mode: 'cors',
body: mapStyle headers: {
}, (error, response, body) => { "Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify(mapStyle)
})
.catch(function(error) {
if(error) console.error(error) if(error) console.error(error)
}) })
return mapStyle return mapStyle
+44
View File
@@ -0,0 +1,44 @@
import querystring from 'querystring'
const debugStore = {};
function enabled() {
const qs = querystring.parse(window.location.search.slice(1));
if(qs.hasOwnProperty("debug")) {
return !!qs.debug.match(/^(|1|true)$/);
}
else {
return false;
}
}
function genErr() {
return new Error("Debug not enabled, enable by appending '?debug' to your query string");
}
function set(namespace, key, value) {
if(!enabled()) {
throw genErr();
}
debugStore[namespace] = debugStore[namespace] || {};
debugStore[namespace][key] = value;
}
function get(namespace, key) {
if(!enabled()) {
throw genErr();
}
if(debugStore.hasOwnProperty(namespace)) {
return debugStore[namespace][key];
}
}
const mod = {
enabled,
get,
set
}
window.debug = mod;
export default mod;
+2 -2
View File
@@ -1,7 +1,7 @@
import diffStyles from 'mapbox-gl-style-spec/lib/diff' import {diff} from '@mapbox/mapbox-gl-style-spec'
export function diffMessages(beforeStyle, afterStyle) { export function diffMessages(beforeStyle, afterStyle) {
const changes = diffStyles(beforeStyle, afterStyle) const changes = diff(beforeStyle, afterStyle)
return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' ')) return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' '))
} }
+9
View File
@@ -0,0 +1,9 @@
/**
* A unique id for the current document.
*/
let REF = 0;
export default function(prefix="") {
REF++;
return prefix+REF;
}
+2 -2
View File
@@ -1,6 +1,6 @@
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
export const combiningFilterOps = ['all', 'any', 'none'] export const combiningFilterOps = ['all', 'any', 'none']
export const setFilterOps = ['in', '!in'] export const setFilterOps = ['in', '!in']
export const otherFilterOps = Object export const otherFilterOps = Object
.keys(GlSpec.filter_operator.values) .keys(latest.filter_operator.values)
.filter(op => combiningFilterOps.indexOf(op) < 0) .filter(op => combiningFilterOps.indexOf(op) < 0)
+30 -3
View File
@@ -1,16 +1,16 @@
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js' import {latest} from '@mapbox/mapbox-gl-style-spec'
export function changeType(layer, newType) { export function changeType(layer, newType) {
const changedPaintProps = { ...layer.paint } const changedPaintProps = { ...layer.paint }
Object.keys(changedPaintProps).forEach(propertyName => { Object.keys(changedPaintProps).forEach(propertyName => {
if(!(propertyName in GlSpec['paint_' + newType])) { if(!(propertyName in latest['paint_' + newType])) {
delete changedPaintProps[propertyName] delete changedPaintProps[propertyName]
} }
}) })
const changedLayoutProps = { ...layer.layout } const changedLayoutProps = { ...layer.layout }
Object.keys(changedLayoutProps).forEach(propertyName => { Object.keys(changedLayoutProps).forEach(propertyName => {
if(!(propertyName in GlSpec['layout_' + newType])) { if(!(propertyName in latest['layout_' + newType])) {
delete changedLayoutProps[propertyName] delete changedLayoutProps[propertyName]
} }
}) })
@@ -27,6 +27,32 @@ export function changeType(layer, newType) {
* to a {@newValue}. * to a {@newValue}.
*/ */
export function changeProperty(layer, group, property, newValue) { export function changeProperty(layer, group, property, newValue) {
// Remove the property if undefined
if(newValue === undefined) {
if(group) {
const newLayer = {
...layer,
// Change object so the diff works in ./src/components/map/MapboxGlMap.jsx
[group]: {
...layer[group]
}
};
delete newLayer[group][property];
// Remove the group if it is now empty
if(Object.keys(newLayer[group]).length < 1) {
delete newLayer[group];
}
return newLayer;
} else {
const newLayer = {
...layer
};
delete newLayer[property];
return newLayer;
}
}
else {
if(group) { if(group) {
return { return {
...layer, ...layer,
@@ -41,4 +67,5 @@ export function changeProperty(layer, group, property, newValue) {
[property]: newValue [property]: newValue
} }
} }
}
} }
+11
View File
@@ -0,0 +1,11 @@
import MapboxGl from 'mapbox-gl'
import {readFileSync} from 'fs'
const data = readFileSync(__dirname+"/../../node_modules/@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js", "utf8");
const blob = new window.Blob([data], {
type: "text/javascript"
});
const objectUrl = window.URL.createObjectURL(blob);
MapboxGl.setRTLTextPlugin(objectUrl);
+21 -17
View File
@@ -1,21 +1,19 @@
import request from 'request' import npmurl from 'url'
function loadJSON(url, defaultValue, cb) { function loadJSON(url, defaultValue, cb) {
request({ fetch(url, {
url: url, mode: 'cors',
withCredentials: false, credentials: "same-origin"
}, (error, response, body) => { })
if (!error && body && response.statusCode == 200) { .then(function(response) {
try { return response.json();
cb(JSON.parse(body)) })
} catch(err) { .then(function(body) {
console.error(err) cb(body)
cb(defaultValue) })
} .catch(function() {
} else {
console.warn('Can not metadata for ' + url) console.warn('Can not metadata for ' + url)
cb(defaultValue) cb(defaultValue)
}
}) })
} }
@@ -23,9 +21,15 @@ export function downloadGlyphsMetadata(urlTemplate, cb) {
if(!urlTemplate) return cb([]) if(!urlTemplate) return cb([])
// Special handling because Tileserver GL serves the fontstacks metadata differently // Special handling because Tileserver GL serves the fontstacks metadata differently
// https://github.com/klokantech/tileserver-gl/pull/104 // https://github.com/klokantech/tileserver-gl/pull/104#issuecomment-274444087
let url = urlTemplate.replace('/fonts/{fontstack}/{range}.pbf', '/fontstacks.json') let urlObj = npmurl.parse(urlTemplate);
url = url.replace('{fontstack}/{range}.pbf', 'fontstacks.json') const normPathPart = '/%7Bfontstack%7D/%7Brange%7D.pbf';
if(urlObj.pathname === normPathPart) {
urlObj.pathname = '/fontstacks.json';
} else {
urlObj.pathname = urlObj.pathname.replace(normPathPart, '.json');
}
let url = npmurl.format(urlObj);
loadJSON(url, [], cb) loadJSON(url, [], cb)
} }
+17
View File
@@ -0,0 +1,17 @@
function asBool(queryObj, key) {
if(queryObj.hasOwnProperty(key)) {
if(queryObj[key].match(/^false|0$/)) {
return false;
}
else {
return true;
}
}
else {
return false;
}
}
module.exports = {
asBool
}
+14
View File
@@ -0,0 +1,14 @@
export default function(a, b) {
a = parseFloat(a, 10);
b = parseFloat(b, 10);
if(a < b) {
return -1
}
else if(a > b) {
return 1
}
else {
return 0;
}
}

Some files were not shown because too many files have changed in this diff Show More