Compare commits

...

1307 Commits

Author SHA1 Message Date
orangemug
6da83c4670 1.7.0-beta2 2020-04-06 16:57:02 +01:00
Orange Mug
d26af16003 Merge pull request #639 from orangemug/fix/only-scroll-layer-list-if-item-not-in-view
Only scroll to selected item in <LayerList/> if not already in view.
2020-04-06 16:55:26 +01:00
Orange Mug
d75b86c927 Merge pull request #638 from orangemug/maintenance/update-deps-20200406
Update all deps
2020-04-06 16:54:06 +01:00
orangemug
a0cd087ccc Revert webdriverio version updates. 2020-04-06 15:47:12 +01:00
orangemug
313b639a5f Only scroll to selected item in <LayerList/> if not already in view. 2020-04-06 15:30:16 +01:00
orangemug
93c45d5340 Update all deps. 2020-04-06 15:14:21 +01:00
Orange Mug
3be6cb5926 Merge pull request #637 from orangemug/fix/console-errors-2020-04-06
Fix a bunch of errors/warnings from the console
2020-04-06 15:06:39 +01:00
Orange Mug
9d151fdc1f Merge pull request #636 from pathmapper/promote_beta
Promote v1.7.0-beta in readme
2020-04-06 14:32:29 +01:00
orangemug
44d1a7a6b0 {arrayMove} will no longer be included in 'react-sortable-hoc', move to array-move. 2020-04-06 14:18:41 +01:00
pathmapper
0e5676eae0 Promote v1.7.0-beta in readme 2020-04-06 15:14:42 +02:00
orangemug
b8739915b2 Lots of smaller fixes found in the console logs during testing. 2020-04-06 13:59:08 +01:00
Orange Mug
a1dedd1aa6 Merge pull request #634 from orangemug/fix/issue-630
Scroll selected <LayerListItem/> into view
2020-04-06 13:11:53 +01:00
Orange Mug
33b4a40c35 Merge pull request #635 from orangemug/fix/issue-633
Fixes for <NumberInput/>
2020-04-06 13:10:48 +01:00
orangemug
a624909819 Reset dirtyValue on resetValue 2020-04-06 10:40:30 +01:00
orangemug
d5d387f349 Removed placeholder on range (doesn't work) in favour 'default' value merged into 'value'. 2020-04-06 10:24:31 +01:00
orangemug
c58ae0f895 Fix <NumberInput/> to allow for decimal numbers. 2020-04-06 09:55:07 +01:00
orangemug
c9e360d675 Fix layer selection via <FeatureLayerPopup/> 2020-04-06 08:47:13 +01:00
orangemug
75ece350bd Merge remote-tracking branch 'upstream/master' into fix/issue-630 2020-04-04 15:48:52 +01:00
Orange Mug
45680151ef Merge pull request #632 from orangemug/fix/color-popup-swatch
Fix color in <FeatureLayerPopup/>
2020-04-04 15:48:35 +01:00
orangemug
87bae82b17 Merge remote-tracking branch 'upstream/master' into fix/issue-630 2020-04-04 15:44:29 +01:00
orangemug
fcad636f85 Scroll selected <LayerListItem/> into view 2020-04-04 15:41:35 +01:00
orangemug
bac8495b3c Made <FeatureLayerPopup/> swatch simplier because colors from features are already evaluated 2020-04-04 10:45:11 +01:00
orangemug
df98cb9c7b Fix layer color swatch in <FeatureLayerPopup/> 2020-04-04 10:21:58 +01:00
Orange Mug
34c3015b42 Merge pull request #625 from orangemug/fix/invalid-style-with-duplicate-layer-ids
Fix UI when loading invalid style with duplicate layer ids
2020-03-30 20:38:25 +01:00
orangemug
7d51ea9b25 1.7.0-beta 2020-03-30 17:44:26 +01:00
orangemug
ca7bf9f4a7 Fixed lint errors. 2020-03-30 09:57:14 +01:00
orangemug
61ba399e1c Duplicate layer ids now show errors inline. 2020-03-30 09:47:46 +01:00
orangemug
b5c09a4f17 Merge remote-tracking branch 'upstream/master' into fix/invalid-style-with-duplicate-layer-ids 2020-03-30 08:52:37 +01:00
Orange Mug
fcfc7ab874 Merge pull request #626 from orangemug/fix/issue-321-accessible-layer-groups
Moved to using react-accessible-accordion for <LayerEditorGroup/>
2020-03-29 16:12:33 +01:00
pathmapper
a0bc4744a2 Merge pull request #627 from orangemug/fix/make-scrollbar-wider
Wider scrollbar
2020-03-29 13:55:55 +02:00
orangemug
e6e4c928f3 Make the scrollbar wider so it's easier to grab with the mouse pointer. 2020-03-29 07:40:43 +01:00
orangemug
00388e03b8 Moved to using react-accessible-accordion for <LayerEditorGroup/> 2020-03-28 12:53:46 +00:00
orangemug
6f83839a4c Added missing file. 2020-03-28 11:06:45 +00:00
orangemug
74b47e7e74 Fix UI when loading invalid style with duplicate layer ids.
Also fix for throwing error when 2 layers exist with empty strings as ids.
2020-03-28 10:58:47 +00:00
Orange Mug
f70d078ec6 Merge pull request #624 from orangemug/maintenance/update-deps-20200328
Update dependencies
2020-03-28 09:49:59 +00:00
orangemug
1d8131fb85 Update dependencies. 2020-03-28 08:33:57 +00:00
Orange Mug
8c82db9162 Merge pull request #620 from orangemug/feature/ui-errors-and-expressions
Added support for expressions and UI errors
2020-03-27 17:51:02 +00:00
orangemug
f23f60807a Tidy 2020-03-16 10:17:04 +00:00
orangemug
8f581956e8 Revert jsonlint 2020-03-08 19:00:36 +00:00
orangemug
87fb0f6a5c Updated lockfile. 2020-03-08 18:44:04 +00:00
orangemug
1c953bc296 Remove old hack 'src/vendor/codemirror/addon/lint/json-lint' 2020-03-08 18:43:46 +00:00
orangemug
ce976991d4 Added inline errors to the code-mirror editors based on field spec. 2020-03-08 18:38:32 +00:00
orangemug
be7642976b Can't use object as react-dom key. 2020-02-25 09:27:29 +00:00
orangemug
a5b226d9f3 Fixed lint error. 2020-02-25 09:25:52 +00:00
orangemug
1b3d8b5b79 Added JSON error to expression editor 2020-02-25 09:20:14 +00:00
orangemug
97a61afc24 Merge remote-tracking branch 'upstream/master' into feature/ui-errors-and-expressions 2020-02-23 13:34:05 +00:00
Orange Mug
d1f6bc95db Merge pull request #621 from orangemug/fix/CheckboxInput-props-error
Added default to <CheckboxInput/> value to fix warning in console.
2020-02-23 13:24:16 +00:00
orangemug
10b03c4e00 Also fixed invalid DOM nesting warning. 2020-02-22 18:47:46 +00:00
Orange Mug
449d8e7665 Merge pull request #617 from orangemug/fix/map-controls-and-improve-zoom-styling
Improved map control zoom styling
2020-02-22 18:41:05 +00:00
orangemug
4b8800e8ac Added default to <CheckboxInput/> value to fix warning in console. 2020-02-22 18:39:24 +00:00
orangemug
874c6460f6 Merge remote-tracking branch 'upstream/master' into fix/map-controls-and-improve-zoom-styling 2020-02-22 17:59:24 +00:00
orangemug
55cb86f721 Disabled survey modal on first visit to website. 2020-02-22 17:52:20 +00:00
orangemug
a30017fd2c Inline survey modal image 2020-02-22 17:50:14 +00:00
orangemug
3b5ba6c59e Fixed lint errors. 2020-02-22 13:15:55 +00:00
orangemug
a693f6db4e Added infobox for old style filters to allow you to switch to the filter editor. 2020-02-22 11:54:59 +00:00
Orange Mug
5be7e0c7ec Merge pull request #619 from orangemug/maintenance/update-deps-20200217-v2
Update all the deps
2020-02-22 08:57:07 +00:00
orangemug
7c6b3c0d80 Bumped deps. 2020-02-21 09:07:25 +00:00
orangemug
e5e03be382 Fixed missing command update to appveyor.yml 2020-02-21 08:46:46 +00:00
orangemug
0d35106cc8 Tidy test scripts. 2020-02-21 08:06:01 +00:00
orangemug
5710edcff7 Fixed invalid lockfile. 2020-02-18 21:42:25 +00:00
orangemug
2cc7c63bb1 Updated to new webdriverio config api, and fixed failing tests. 2020-02-18 21:39:11 +00:00
orangemug
ba9d21c045 Merge remote-tracking branch 'upstream/master' into maintenance/update-deps-20200217-v2 2020-02-18 19:10:20 +00:00
orangemug
4ef6ecb7eb Moved from magnify icon to 'zoom:' label. Also increased contrast on compass arrow. 2020-02-18 18:50:30 +00:00
orangemug
52e8b21b3d Tidy function. 2020-02-18 08:06:36 +00:00
Orange Mug
c6ba4f66e2 Merge pull request #618 from orangemug/feature/improve-loader
Added improved loader with maputnik logo
2020-02-18 07:50:56 +00:00
orangemug
5a47a96f09 Update all the deps 2020-02-17 19:38:59 +00:00
orangemug
ae878f6000 Added missing plugin from production. 2020-02-17 19:03:19 +00:00
orangemug
aebfe62a8e Fixed image path. 2020-02-17 18:55:28 +00:00
orangemug
6be3543616 Added improved loader. 2020-02-17 18:52:16 +00:00
orangemug
0f6708d9d4 Improved zoom styling and fixed zoom in/out button styling. 2020-02-17 15:39:54 +00:00
orangemug
0705522a24 Fixed width issue with expression error UI. 2020-02-17 13:44:05 +00:00
orangemug
35098111ac Include missing errors from expressions UI. 2020-02-17 13:41:11 +00:00
orangemug
39333953d7 Only show 'switch to layer' button if layer not already selected. 2020-02-17 13:24:14 +00:00
orangemug
adea3d0f13 Added ability to switch to layer from global error panel. 2020-02-17 13:19:26 +00:00
orangemug
d1cb2690fc Fixed lint errors. 2020-02-17 11:55:16 +00:00
orangemug
3ffdcc9639 Added the ability to convert from data/zoom function to expression 2020-02-17 11:52:40 +00:00
orangemug
793b5d15ad Fixed lint errors 2020-02-17 11:30:48 +00:00
orangemug
cff32696cc Better handling of undo history for expressions. 2020-02-17 11:29:05 +00:00
orangemug
029eff9317 Added margin-left to function errors to line up with values. 2020-02-16 22:02:05 +00:00
orangemug
b7d08dfaa6 Fixed data function display issues. 2020-02-16 21:56:03 +00:00
orangemug
94089836bf Fixed lint errors. 2020-02-16 21:30:27 +00:00
orangemug
ff8a8fb749 Added errors to filter functions 2020-02-16 21:25:41 +00:00
orangemug
1300951a29 Removed height from '.maputnik-layout-bottom' to allow it to expand. 2020-02-16 20:31:51 +00:00
orangemug
3cb1ed9403 Made function to check if expression/function more robust. 2020-02-16 20:25:43 +00:00
orangemug
a5ac1cc93d Guard against errors producing dirty map state. 2020-02-16 18:22:21 +00:00
orangemug
29a0ef0d1c Change to always attempt rendering of map state, even if broken, allowing the user to fix style issues step-by-step. 2020-02-16 18:11:47 +00:00
orangemug
26907f7014 Fix button style in disabled state as reported by @pathmapper 2020-02-16 17:34:23 +00:00
orangemug
3ac06c7cb1 Merge remote-tracking branch 'upstream/master' into feature/ui-errors-and-expressions 2020-02-10 15:57:25 +00:00
Orange Mug
f268f09ca2 Merge pull request #616 from orangemug/fix/error-bar-width
Fixed overflowing error bar
2020-02-10 15:50:53 +00:00
orangemug
f4c18fd91b Fixed error bar from overflowing. 2020-02-09 17:23:50 +00:00
orangemug
0567b098ec Fixed undo button disabled state for expressions. 2020-02-09 16:48:52 +00:00
orangemug
dc6006fd6d Fixed lint errors. 2020-02-09 16:36:17 +00:00
orangemug
109261ba00 Fixed filter defaults and removed auto-migrate. 2020-02-09 16:33:47 +00:00
orangemug
b539644b2b Added expression support for filters. 2020-02-09 15:08:24 +00:00
orangemug
be36eec93d Added 'undo' alongside 'trash' button 2020-02-08 10:38:31 +00:00
pathmapper
fe5066a2a4 Merge pull request #614 from orangemug/fix/map-container-background-color
Make background map container white to match html page default
2020-02-02 17:59:35 +01:00
Orange Mug
642e5c0b29 Merge pull request #613 from orangemug/fix/brackets-color-clash-with-cursor
Changed bracket matching color
2020-02-02 15:26:39 +00:00
orangemug
97bdc93a39 Fixed lint error. 2020-02-02 11:56:51 +00:00
orangemug
c770b440c2 Make background map container white to match html page default. 2020-02-02 11:47:37 +00:00
orangemug
7559985a2e Changed bracket matching color so it doesn't clash with cursor. 2020-02-02 11:32:58 +00:00
orangemug
532bbecb47 Only disable <LayerTypeBlock/> in <LayerEditor/> 2020-02-02 08:05:01 +00:00
orangemug
8ed67e98ce Fixed lint errors 2020-02-02 07:44:20 +00:00
orangemug
5792c632f9 hopefulMapStyle -> dirtyMapStyle 2020-02-01 17:55:26 +00:00
orangemug
3e2927e6a4 Fixed undo/redo 2020-02-01 17:53:01 +00:00
orangemug
f09cc25a3b Merge remote-tracking branch 'upstream/master' into feature/ui-errors-and-expressions 2020-02-01 17:36:59 +00:00
orangemug
c5c3e93aff Better support for expressions
- Expression editing state
 - CodeMirror JSON editor
 - Improved styling
2020-02-01 17:07:52 +00:00
Orange Mug
cc371d6a70 Merge pull request #612 from orangemug/fix/color-field-name-is-not-required
'name' is not a required property of <ColorField />
2020-01-30 09:47:06 +00:00
Orange Mug
1b17e8fa0a Merge pull request #610 from orangemug/fix/issue-609
Fix for stuck/unchanging pop-up in Inspect view
2020-01-30 09:22:43 +00:00
Orange Mug
bc4706de83 Merge pull request #607 from orangemug/fix/issue-567-better-solution-for-tooltips
Inline property documentation and SDK docs
2020-01-30 09:21:10 +00:00
orangemug
0f22eb83d3 'name' is not a required property of <ColorField /> 2020-01-30 09:18:23 +00:00
orangemug
a8cbe19f09 Added style_renderer to field-spec-additional 2020-01-30 08:54:08 +00:00
orangemug
c714e23d79 'id' is not guaranteed to be unique, see <https://github.com/maputnik/editor/pull/610/files#r372521304> 2020-01-30 08:46:26 +00:00
orangemug
5b3d579f87 Fix to fallback to index for unique feature key in <FeaturePropertyPopup /> 2020-01-29 08:44:27 +00:00
orangemug
725b752e35 Added initial expression work and UI errors. 2020-01-29 08:22:03 +00:00
orangemug
223721a65d Code review fixes
- Added docs to export modal
 - Fixed 'source type' doc in data sources modal
2020-01-27 08:49:44 +00:00
orangemug
9b4d924dff Fix to ignore modified styleSpec. 2020-01-24 17:46:43 +00:00
orangemug
b31537e063 Added property values to docs 2020-01-24 09:04:32 +00:00
Orange Mug
63ed8c1de3 Merge pull request #605 from orangemug/maintenance/update-deps-2020-01-19
Updated deps
2020-01-23 08:50:01 +00:00
Orange Mug
7aa0298f7c Merge pull request #603 from orangemug/feature/add-image-and-video-sources
Added ability to add image/video sources
2020-01-23 08:49:37 +00:00
orangemug
62f3cbe8fb Lint fixes. 2020-01-23 08:42:20 +00:00
orangemug
30facc885f Tidy inline docs and added sdk support table. 2020-01-23 08:33:12 +00:00
orangemug
17aa88e3b6 Merge remote-tracking branch 'upstream/master' into fix/issue-567-better-solution-for-tooltips 2020-01-22 08:57:45 +00:00
Orange Mug
5b9af07ebc Merge pull request #604 from orangemug/feature/issue-82
Link to OSM from debug panel
2020-01-19 20:11:30 +00:00
orangemug
0009c74948 Fixes lat/lon precision and center change via zoom without pan. 2020-01-19 19:09:04 +00:00
Orange Mug
8911f83ef3 Merge pull request #522 from orangemug/feature/add-range-slider
Add range input for minZoom/maxZoom
2020-01-19 18:54:07 +00:00
orangemug
2fafafe0dc Updated deps 2020-01-19 16:39:41 +00:00
orangemug
27e6675d26 Fix replacing of access tokens in MapboxGl. 2020-01-19 16:30:27 +00:00
orangemug
4269e4573c Fixed lint errors. 2020-01-19 16:00:29 +00:00
orangemug
096e2b6aec Fixed image/video coord labels. 2020-01-19 15:48:11 +00:00
orangemug
33e04b3527 Added ability to add/edit image and video sources 2020-01-19 14:40:35 +00:00
orangemug
79fa2b3508 Feature to update current map view in OSM 2020-01-19 13:37:56 +00:00
orangemug
d5ef412300 Safari fixes. 2020-01-19 11:53:27 +00:00
orangemug
0726a494be Only set dirtyValue on valid change. 2020-01-19 11:41:37 +00:00
orangemug
926969b921 Always change via step from keyboard. 2020-01-19 11:28:03 +00:00
orangemug
59e070f463 Merge remote-tracking branch 'upstream/master' into feature/add-range-slider 2020-01-19 11:06:39 +00:00
orangemug
2ccd1d227e rangeValue -> dirtyValue 2020-01-19 11:06:24 +00:00
orangemug
655877f67e Fixed another bug with change events for <NumberInput/> and added range input keyboard support. Remove logging as react props/state is enough for debugging. 2020-01-19 10:30:08 +00:00
Orange Mug
6c240d53e4 Merge pull request #601 from orangemug/fix/issue-588
Added new fieldSpec.type 'resolvedImage'
2020-01-19 09:59:41 +00:00
Orange Mug
f89f8ed4ea Merge pull request #587 from pathmapper/matchingbrackets_addon
Add matchbrackets addon to JSON editor
2020-01-19 09:48:08 +00:00
Orange Mug
6123b464de Merge pull request #600 from orangemug/fix/issue-589
Fix feature selection in <FeatureLayerPopup />
2020-01-19 09:46:57 +00:00
Orange Mug
49dba02e8f Merge branch 'master' into matchingbrackets_addon 2020-01-19 08:29:25 +00:00
pathmapper
fb49a3abe5 Merge pull request #602 from orangemug/fix/issue-565
Update react-collapse
2020-01-19 08:54:28 +01:00
orangemug
c88f9ab5dc Believed fix for race condition in setting dirtyValue 2020-01-19 07:37:49 +00:00
orangemug
d886b14d09 Fix to set "editing: false" in blur/pointerup 2020-01-19 07:15:50 +00:00
orangemug
bd1204a7a5 Added react-collapse transition, required for react-collapse@^5 2020-01-18 22:11:02 +00:00
orangemug
9cadda0236 Update react-collapse which was needlessly updating and causing scroll position jump. 2020-01-18 22:03:29 +00:00
orangemug
90ea6323c1 Added new fieldSpec.type 'resolvedImage' 2020-01-18 20:57:59 +00:00
orangemug
51f2cfac16 Fix feature selection in <FeatureLayerPopup /> 2020-01-18 20:42:38 +00:00
Orange Mug
4dbb423ac2 Merge pull request #599 from pathmapper/update_omt_styles
Update OMT styles for v3.11
2020-01-15 10:06:58 +00:00
Orange Mug
a3ee1cc27e Merge pull request #595 from pathmapper/maxzoom
Increase maximum zoom level of the map to 24
2020-01-15 10:06:20 +00:00
pathmapper
fea0798349 Update OMT styles for v3.11 2020-01-14 10:28:32 +01:00
pathmapper
bd8abffa28 Merge pull request #597 from pathmapper/appveyor_vs_2019
Update appveyor to "Visual Studio 2019"
2020-01-13 09:39:48 +01:00
pathmapper
a5f3a43cde Merge pull request #596 from pathmapper/remove_node_v8
Remove node v8 from test runners
2020-01-13 09:39:28 +01:00
pathmapper
6c5dc7e06b Update appveyor to "Visual Studio 2019" 2020-01-11 10:26:07 +01:00
pathmapper
b1c8a12e88 Remove node v8 from appveyor 2020-01-11 09:39:09 +01:00
pathmapper
401c6971f4 Remove node v8 from circleci 2020-01-11 09:38:20 +01:00
pathmapper
7e5a5ce077 Set map maxZoom to 24 2020-01-10 14:06:17 +01:00
pathmapper
6b245c9894 Improve matchingbrackets and cursor visibility 2019-10-30 12:24:25 +01:00
pathmapper
b963fe9619 Add matchbrackets addon 2019-10-29 10:52:06 +01:00
Orange Mug
673887d93b Merge pull request #582 from orangemug/fix/add-source-errors
Add open modal and source http/https errors
2019-10-28 18:19:21 +00:00
Orange Mug
06898429fd Merge pull request #584 from orangemug/fix/issue-576
A hack fix for blocked popup
2019-10-28 18:18:33 +00:00
pathmapper
0196ba4eb4 Merge pull request #585 from orangemug/fix/issue-403
Update map style even if inspectModeEnabled=true
2019-10-28 06:38:49 +01:00
orangemug
ef81534a17 Update map style even if inspectModeEnabled=true 2019-10-27 20:04:57 +00:00
orangemug
a958ec943b Lint fix. 2019-10-27 19:16:13 +00:00
orangemug
4e3b395b3d A hack fix for blocked popup. 2019-10-27 19:11:34 +00:00
Orange Mug
5e7fd4f93c Update src/components/inputs/UrlInput.jsx
CORs -> CORS

Co-Authored-By: pathmapper <pathmapper@posteo.de>
2019-10-27 19:07:49 +00:00
orangemug
25cad5bb25 Added to GeoJSON url. 2019-10-27 18:58:22 +00:00
orangemug
f9c230414e Fix width of modal. 2019-10-27 18:54:40 +00:00
orangemug
866f8d034a Switched to <UrlInput/> for glyphs and sprites 2019-10-27 18:45:49 +00:00
pathmapper
be6aa559fb Merge pull request #583 from orangemug/feature/make-osm-liberty-the-default
Make osm-liberty the default style
2019-10-27 19:38:11 +01:00
orangemug
a560176d83 Add even more logging. 2019-10-27 18:15:54 +00:00
Orange Mug
4644e78fd2 Merge pull request #579 from orangemug/fix/function-field-fixes
Function field fixes
2019-10-27 17:58:06 +00:00
orangemug
237cc16b97 Make osm-liberty the default style. 2019-10-27 17:46:34 +00:00
orangemug
dffa54afb0 Removed more old styleUrlElement refs 2019-10-27 17:42:45 +00:00
orangemug
225e5c48e4 Fixed lint error. 2019-10-27 17:38:57 +00:00
orangemug
2e017d252a Added <UrlInput/> to open modal. 2019-10-27 17:36:20 +00:00
orangemug
e728e5f7e4 Exposed onInput in <UrlInput/> 2019-10-27 17:35:56 +00:00
orangemug
f0371b41b1 Also validate on constructor 2019-10-27 17:27:50 +00:00
orangemug
a51442921a Fix font-size inline with the rest of the UI. 2019-10-27 17:19:03 +00:00
orangemug
f39fb34f36 Protocol logic and lint fixes. 2019-10-27 17:15:17 +00:00
orangemug
566201fb45 Added UrlInput component to tidy things up a little. 2019-10-27 17:08:23 +00:00
orangemug
88841b56e7 Added another fix attempt and yet more logging. 2019-10-27 15:56:22 +00:00
orangemug
5aa0b4e7d9 More logging additional attempt at fixes. 2019-10-27 11:01:33 +00:00
Orange Mug
f19fc4a8a1 Merge pull request #581 from orangemug/fix/issue-535-data-field-ordering
Fix zoom ordering for data property fields
2019-10-27 10:18:34 +00:00
orangemug
cd162309a8 Added hopeful fix + logging. 2019-10-27 10:15:11 +00:00
orangemug
aead867e27 Incorrectly checking for falsey when should be checking for presence. 2019-10-27 10:05:05 +00:00
orangemug
663f295623 Added default protocol and stub in error. 2019-10-27 10:00:22 +00:00
orangemug
c588164190 console.log map errors. 2019-10-26 18:27:11 +01:00
orangemug
d61d0a5795 Fix test for range slider. 2019-10-26 18:09:07 +01:00
orangemug
dddd604f7b Removed left over conflict markers and set default. 2019-10-26 17:41:56 +01:00
orangemug
ea3b9a20c5 Merge remote-tracking branch 'upstream/master' into feature/add-range-slider 2019-10-26 17:22:52 +01:00
Orange Mug
7415b8af08 Merge pull request #580 from pathmapper/update_mb_deps
Update mbgl and style-spec
2019-10-26 17:19:04 +01:00
orangemug
d06e053d34 Fix zoom ordering for data property fields. 2019-10-26 17:15:25 +01:00
pathmapper
7075a8b05e Update mbgl and style-spec 2019-10-26 07:10:30 +02:00
orangemug
4cbcf14588 Make up defaults from function field if we can't find one from the spec. 2019-10-25 22:54:08 +01:00
orangemug
ca202d7701 Use <EnumInput/> for enums again 2019-10-25 22:48:37 +01:00
Orange Mug
8dfc16e7ee Merge pull request #577 from fredj/del_btn_style
Simplify and fix vertical alignment of the delete button
2019-10-25 22:15:23 +01:00
Frederic Junod
fbf5cec670 Simplify and fix vertical alignment
Use the same css as the `maputnik-make-data-function` and `maputnik-make-zoom-function` buttons.
2019-10-25 13:16:09 +02:00
Orange Mug
14d4383f8a Merge pull request #575 from snodnipper/feature/zoomstack-v2
set OS Zoomstack endpoints to v2
2019-10-24 17:58:54 +01:00
Oliver Snowden
58bdd39f9e set OS Zoomstack endpoints to v2 2019-10-25 00:24:55 +08:00
Orange Mug
ab9ab7acc7 Merge pull request #574 from fredj/GitHub_sponsor
Activate sponsor button in GitHub
2019-10-24 16:02:16 +01:00
Frederic Junod
be39fd2ec8 Activate sponsor button in GitHub
See: https://help.github.com/en/github/building-a-strong-community/displaying-a-sponsor-button-in-your-repository
2019-10-24 16:51:45 +02:00
pathmapper
3c0185da27 Merge pull request #573 from pathmapper/node_13
Add node v13 to test runners
2019-10-24 05:59:12 +02:00
pathmapper
b37b7276fb Add node v13 to test runners 2019-10-23 11:10:05 +02:00
Orange Mug
c45cf2f0c8 Merge pull request #563 from orangemug/feature/more-root-property-support
Added support for more root level properties
2019-10-21 10:18:37 +01:00
Orange Mug
1f03fdbb50 Merge pull request #571 from orangemug/fix/map-perf-improvement
Remove shouldComponentUpdate from <MapboxGlMap/>
2019-10-20 12:50:33 +01:00
orangemug
f3b8c5362a Remove shouldComponentUpdate because mapbox-gl will do the diff for us, so updates will no longer diff twice. 2019-10-20 12:25:43 +01:00
Orange Mug
c9a5dd01be Merge pull request #570 from orangemug/feature/issue-559-add-raw-geojson-as-source
Add support for raw GeoJSON as source
2019-10-20 12:23:37 +01:00
orangemug
0fa4d40e92 Remove duplicate import rule. 2019-10-20 11:21:38 +01:00
orangemug
8a6e64c8c2 Merge remote-tracking branch 'upstream/master' into feature/issue-559-add-raw-geojson-as-source 2019-10-20 11:19:34 +01:00
orangemug
72b6dd1ae9 Fix lint errors. 2019-10-20 11:12:54 +01:00
orangemug
ee525631fa Fixes for codemirror in sources modal
- Allows for max height
 - Override GeoJSON data when changing type
2019-10-20 11:09:20 +01:00
pathmapper
ee9e055af3 Merge pull request #562 from orangemug/feature/json-editor-perf-improvements
react-codemirror2 -> codemirror directly
2019-10-19 21:56:59 +02:00
pathmapper
b214c6ac7e Merge pull request #566 from orangemug/feature/add-profiling-build
Added profiling build to CI
2019-10-19 21:55:20 +02:00
orangemug
eb75020861 Merge branch 'feature/json-editor-perf-improvements' into fix/issue-567-better-solution-for-tooltips 2019-10-19 16:56:45 +01:00
orangemug
a44e757e31 Initial work for new help text implementation. 2019-10-19 14:43:27 +01:00
orangemug
9ac908948d Changed label/content styling for input blocks. 2019-10-19 13:12:09 +01:00
orangemug
19e82e5890 Added support for raw GeoJSON 2019-10-19 13:11:29 +01:00
orangemug
bf84fd24ee Moved profiling to wdio-steps 2019-10-19 12:33:54 +01:00
orangemug
affeb7c751 Fixed lint errors. 2019-10-19 12:21:38 +01:00
orangemug
9743361e0d Added back in 'light position' and 'center' with fixes for <ArrayInput/>
This also improves the usage of *-translate which uses the <ArrayInput/>
2019-10-19 12:16:56 +01:00
orangemug
ab16120af2 Added invalid style to inputs 2019-10-19 12:07:08 +01:00
orangemug
37e5ba0fff Remove 'center' and 'light position', as they weren't working properly. 2019-10-16 22:36:41 +01:00
orangemug
0aa0dad7fb Remove undefined root properties. 2019-10-16 22:31:29 +01:00
orangemug
2910efde6e Allow removal of light/transition properties. 2019-10-16 21:58:24 +01:00
orangemug
eac7656786 <NumberInput/> unsetting fix. 2019-10-16 21:57:29 +01:00
Orange Mug
be3175beae Merge pull request #569 from fredj/layer-type-test-issue
Fix the layer type test in LayerEditor component
2019-10-16 19:21:03 +01:00
Frederic Junod
26de95a263 Fix the layer type test in LayerEditor component
`LayerSourceLayerBlock` was always included
2019-10-16 15:36:25 +02:00
orangemug
d0a47bd122 Added support for transition root property 2019-10-16 10:15:46 +01:00
orangemug
8c760bb810 Added default support to <EnumInput/> 2019-10-16 10:12:59 +01:00
orangemug
c27deefdef Added profiling build to CI 2019-10-16 09:56:26 +01:00
orangemug
392a845460 Moved "Style Renderer" options to bottom of list. 2019-10-16 08:24:25 +01:00
orangemug
e7622c2080 Added root level light property support. 2019-10-16 08:17:25 +01:00
orangemug
3a558412ba Merge remote-tracking branch 'upstream/master' into feature/added-light-property-support 2019-10-16 08:16:28 +01:00
Orange Mug
95e205943a Merge pull request #564 from orangemug/fix/typo-in-export
Fixed typo in export
2019-10-16 08:14:21 +01:00
orangemug
eb8686325c Fixed typo in export. 2019-10-16 08:13:37 +01:00
orangemug
1f77e156e6 Added support for root level properties
- center
 - zoom
 - bearing
 - pitch
2019-10-16 07:38:50 +01:00
orangemug
92ee50a4a4 Added default support to <ArrayInput/> 2019-10-16 07:36:30 +01:00
orangemug
ef23f01e67 Removed react-codemirror2 as we no longer use it 2019-10-16 06:49:11 +01:00
orangemug
22b6a4a2bf Added max-width to allow overfow scroll on JSONEditor 2019-10-16 06:46:30 +01:00
Orange Mug
201ecac156 Merge pull request #561 from fredj/ol_attribution_color
Set a color for the OpenLayers attribution control
2019-10-15 17:37:47 +01:00
orangemug
563a78ed42 Moved away from react-codemirror2 to interacting with codemirror directly. 2019-10-15 17:34:36 +01:00
Frederic Junod
47acc2640b Set a color for the OpenLayers attribution control 2019-10-15 14:37:04 +02:00
Orange Mug
f088788246 Merge pull request #557 from orangemug/feature/perf-improvements
LayerList performance improvements
2019-10-15 09:35:56 +01:00
Orange Mug
e219dcd332 Merge pull request #556 from orangemug/feature/add-new-mgljs-features
Added new features from the mapbox-gl spec
2019-10-14 10:04:42 +01:00
orangemug
b8829d9a5c Remove *-sort-key until we support expressions. 2019-10-14 09:22:21 +01:00
pathmapper
2c83c976c6 Merge pull request #558 from pathmapper/liberty_thumbnail
Update thumbnail source for OSM Liberty
2019-10-14 06:59:58 +02:00
orangemug
d63782ddf2 Remove react warnings. 2019-10-13 18:03:58 +01:00
orangemug
3eabcbec72 Fix <NumberInput/> to allow for empty value (reset value) 2019-10-13 18:00:32 +01:00
orangemug
00ab303e44 Stop LayerEditor using same DOM elements for different layers. 2019-10-13 17:59:11 +01:00
pathmapper
38bf12701e Update thumbnail source for OSM Liberty 2019-10-13 17:30:43 +02:00
orangemug
e4ec1d155a Added mapbox-gl features 'text-variable-anchor' and 'text-radial-offset' 2019-10-13 13:32:42 +01:00
orangemug
361f083687 Merge remote-tracking branch 'upstream/master' into feature/add-new-mgljs-features 2019-10-13 12:40:56 +01:00
orangemug
c1a59200e2 1.6.1 2019-10-13 12:02:26 +01:00
orangemug
6e0432ff5e Added back in minifcation. 2019-10-13 11:43:23 +01:00
orangemug
1c83de08c1 Fixed lint errors. 2019-10-12 12:08:25 +01:00
orangemug
0af828543b Disable minify 2019-10-12 12:06:33 +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
db56ad8b2e Remove duplicate formatColor call. 2019-10-12 10:16:46 +01:00
orangemug
7fa17d81ac Some perf improvements when updating the layers list, also throttled color picker. 2019-10-12 10:10:33 +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
8d1cc340b8 Added new features text-writing-mode and *-sort-key 2019-10-10 08:52:36 +01: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
orangemug
1fec89b69e Moved back to data-wd-key approach and fixed tests 2019-05-21 19:07:28 +01:00
orangemug
911549aca3 Moved data-wd-key onto element 2019-05-21 18:54:09 +01:00
orangemug
41329ec2f8 Fixes for firefox, this makes the range input only update on pointerup 2019-05-21 18:42:19 +01:00
orangemug
15cdfbc980 Changed default of step 0.01 -> 1 2019-05-20 11:28:27 +01:00
orangemug
5053058c32 Fixed default values for range slider. 2019-05-20 11:21:23 +01:00
orangemug
8a8cfad303 Merge remote-tracking branch 'upstream/master' into feature/add-range-slider 2019-05-20 10:20:55 +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
orangemug
b1d097a40f Merge remote-tracking branch 'upstream/master' into feature/add-range-slider 2019-05-19 06:04:21 +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
orangemug
3a0fc6eeac Fixed typo 2018-11-02 08:54:20 +00: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
orangemug
b456b59c44 Fix to allow high precision on text input and integer on range. 2018-11-02 08:28:51 +00:00
pathmapper
59ad91fdf8 Add 'show-overdraw-inspector' query parameter 2018-11-02 09:07:01 +01:00
orangemug
e18d304313 Changed min/max zoom range step from 0.1 -> 1 2018-11-01 18:28:58 +00:00
orangemug
5de5281b49 Prevent title resize. 2018-11-01 18:26:21 +00:00
orangemug
fe0df2a4ef Make range step configurable 2018-11-01 08:28:49 +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
a51fdb8435 Merge remote-tracking branch 'upstream/master' into feature/add-range-slider 2018-10-30 20:35:55 +00:00
orangemug
cdd5d27908 Added range slider to <NumberInput /> 2018-10-30 20:35:22 +00: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
Lukas Martinelli
5c286f8d96 Remove static fontstacks.json 2017-01-25 13:47:37 +01:00
Lukas Martinelli
404b53587f Special fontstacks.json handling for Tileserver GL 2017-01-25 13:46:46 +01:00
Lukas Martinelli
e5fbe3b74a Collapse layer groups by default #66 2017-01-25 13:34:10 +01:00
Lukas Martinelli
3f262885ca Highlight selected layer more #62 2017-01-25 13:23:54 +01:00
Lukas Martinelli
c837179f71 Clean up layer.scss 2017-01-25 13:23:29 +01:00
Lukas Martinelli
9a947658e2 Improve default property styling #92 2017-01-25 12:54:33 +01:00
Lukas Martinelli
2458d4b637 Show inspect tooltip only on click in map #90 2017-01-22 21:16:11 +01:00
Lukas Martinelli
e4850805fb Fix default tileset of OpenMapTiles #88 2017-01-18 13:06:24 +01:00
Lukas Martinelli
3a15a3bb06 Show type of feature in popup 2017-01-18 10:03:15 +01:00
Lukas Martinelli
75ca1fa930 Deal with no metadata in style 2017-01-16 20:07:21 +01:00
Lukas Martinelli
377840ca24 Fix lint issues in _modal.scss 2017-01-16 16:34:55 +01:00
Lukas Martinelli
48e9589b58 Merge pull request #86 from klokantech/gist-preview
Gist preview & access token
2017-01-16 15:48:12 +01:00
Lukas Martinelli
11e9cef834 Improve styles and text 2017-01-16 15:43:52 +01:00
jirik
7e3aa09d3e Proview & Access Token logic when saving to Gist 2017-01-16 15:13:19 +01:00
jirik
e3b7e002b4 Hypertext links are white instead of blue 2017-01-16 15:13:11 +01:00
jirik
3b7fb7ae75 Fix checkbox not showing status 2017-01-16 15:13:02 +01:00
jirik
fab004cdfe StringInput fires change if state and props values do not match
Now it is also possible to call onChange listener if new value is empty string
2017-01-16 15:12:03 +01:00
Lukas Martinelli
07523c00f0 Point styles to master not gh-pages 2017-01-16 11:08:18 +01:00
Lukas Martinelli
c15ac14f88 Bump version to v1.0.1 2017-01-16 10:14:59 +01:00
Lukas Martinelli
8f6006c19f Less opacity for default values #73 2017-01-15 17:10:38 +01:00
Lukas Martinelli
16bedcf5b1 Add minzoom and maxzoom block #77 2017-01-15 13:46:55 +01:00
Lukas Martinelli
05349d8ffe Convert filter value to number if possible #63 2017-01-15 13:39:40 +01:00
Lukas Martinelli
a1e1895651 Deal specially with has operator #84 2017-01-15 10:42:59 +01:00
Lukas Martinelli
a111599850 Save chang event on XYZ editor #85 2017-01-15 10:36:57 +01:00
Lukas Martinelli
121a95cee8 Move my key message up 2017-01-14 15:09:27 +01:00
Lukas Martinelli
decd1f3ea2 Add tilezen style 2017-01-14 14:45:04 +01:00
Lukas Martinelli
c632718324 Remove id from empty style to generate one 2017-01-14 14:41:13 +01:00
Lukas Martinelli
9509b59696 Add open Mapbox styles to gallery 2017-01-14 14:00:32 +01:00
Lukas Martinelli
24dc71344e Merge pull request #81 from maputnik/default-access-token
Default access token
2017-01-13 17:01:14 +01:00
Lukas Martinelli
82a11e4b98 Fix style download and strip metadata 2017-01-13 15:55:49 +01:00
Lukas Martinelli
fc8665ed93 Support fallback tokens and replace key 2017-01-13 15:33:22 +01:00
Lukas Martinelli
ca9424e23d Remove interactive from style for diffing to work 2017-01-13 15:31:08 +01:00
Lukas Martinelli
99856b1bb3 Update README.md 2017-01-13 14:40:02 +01:00
Lukas Martinelli
fb518c2be5 Add sponsor logos 2017-01-13 14:12:26 +01:00
Lukas Martinelli
1248a53029 Update README.md 2017-01-13 14:08:22 +01:00
Lukas Martinelli
6ce43840e5 Update README.md 2017-01-13 10:18:20 +01:00
Lukas Martinelli
41d9fb1c44 Newest Mapbox GL Inspect plugin for less fidly selecting 2017-01-12 22:54:20 +01:00
Lukas Martinelli
fd9be8f08f Merge pull request #78 from klokantech/export
Export to Gist anonymously, related to maputnik/editor#3
2017-01-12 22:49:49 +01:00
jirik
69a665373f Export to Gist anonymously, related to maputnik/editor#3 2017-01-12 18:27:44 +01:00
Helge Fahrnberger
8c2b110115 Merge pull request #76 from maputnik/property-groups-reorder
Move line-dasharray to correct group
2017-01-12 18:15:49 +01:00
Helge Fahrnberger
5e3b2dd0df Merge branch 'master' into property-groups-reorder 2017-01-12 18:14:25 +01:00
Helge Fahrnberger
d045213fa3 Move line-dasharray to correct group 2017-01-12 18:13:15 +01:00
Lukas Martinelli
63bba67750 Merge pull request #71 from maputnik/property-groups-reorder
Reordered and renamed groups
2017-01-12 17:47:34 +01:00
Helge Fahrnberger
52e8fd2c29 Add missing properties 2017-01-12 17:44:56 +01:00
Lukas Martinelli
5479b240e1 Fix empty field causing exceptions 2017-01-12 17:28:45 +01:00
Lukas Martinelli
f209d8e9a5 Fix layout.json quote tokens 2017-01-12 17:01:30 +01:00
Lukas Martinelli
ac40d7727e Fix popup layer issue 2017-01-12 16:59:38 +01:00
Lukas Martinelli
7bd9d3f5da Remove GeoJSON example from tilesets 2017-01-12 16:59:38 +01:00
Lukas Martinelli
68685dcf42 Only set source layer if not undefined 2017-01-12 16:59:38 +01:00
jirik
6be6db8f5e Fix hidden map attributions (CSS issue) 2017-01-12 15:34:38 +01:00
Helge Fahrnberger
236dd79b85 Reordered and renamed groups
Purpose: Match structure and wording with Mapbox GL style spec.
2017-01-12 14:31:26 +01:00
Lukas Martinelli
7d905c5e06 Update dependencies 2017-01-12 11:50:08 +01:00
Lukas Martinelli
6fa2542b56 Fix color of compound filter 2017-01-12 11:35:11 +01:00
Lukas Martinelli
7627b8fb45 Fix empty style url in config 2017-01-12 11:33:46 +01:00
Lukas Martinelli
5901427534 Move empty style to config dir 2017-01-12 11:32:20 +01:00
Lukas Martinelli
a30e57c4d8 Add empty style option 2017-01-12 11:31:16 +01:00
Lukas Martinelli
69f2e12ea0 Add stylelint and fix lint issues 2017-01-12 11:23:06 +01:00
Lukas Martinelli
93c7f323fc Upgrade Mapbox GL inspect and remove unused lodash 2017-01-12 10:44:44 +01:00
Lukas Martinelli
cbe2a4c180 Fix GeoJSON and default source issue in Sources modal 2017-01-12 10:28:03 +01:00
Lukas Martinelli
2e0cc4511c Improve add layer button visually 2017-01-11 20:48:15 +01:00
Lukas Martinelli
bcab165f97 Select highlighted multibutton 2017-01-11 20:43:40 +01:00
Lukas Martinelli
2516fba105 Animate opacity on layer group collapse 2017-01-11 19:57:12 +01:00
Lukas Martinelli
9ca8760564 Absolute position to not take up space 2017-01-11 19:50:18 +01:00
Lukas Martinelli
df94d9c842 Prevent same prefix from being collapsed 2017-01-11 19:39:09 +01:00
Lukas Martinelli
abceb457c9 Collapsible layer groups #66 2017-01-11 18:18:59 +01:00
Lukas Martinelli
26a865bb50 Add missing mixins 2017-01-11 17:52:29 +01:00
Lukas Martinelli
d0f047d88a Group layers #66 2017-01-11 17:52:21 +01:00
Lukas Martinelli
76d2d06e77 Make section headers white #64 2017-01-11 16:32:31 +01:00
Lukas Martinelli
6c56006fbf Show choose public sources first #64 2017-01-11 16:26:14 +01:00
Lukas Martinelli
bbe45cf8ee Switch text in inspect button #64 2017-01-11 16:23:33 +01:00
Lukas Martinelli
82da251218 Add vendor prefixes 2017-01-11 16:20:10 +01:00
Lukas Martinelli
196d9f0a10 Move add modal to layer list 2017-01-11 15:59:51 +01:00
Lukas Martinelli
cb752c0343 Add layer button and increase contrast 2017-01-11 15:48:15 +01:00
Lukas Martinelli
3917a3e323 Fix multi button style 2017-01-11 14:13:23 +01:00
Lukas Martinelli
fed1f09434 Remove last style configs in JS 2017-01-11 14:11:45 +01:00
Lukas Martinelli
840778b64f Remove JS input config 2017-01-11 14:03:48 +01:00
Lukas Martinelli
0908856b4f Restructure CSS more 2017-01-11 13:34:38 +01:00
Lukas Martinelli
b51354ae1d All important stuff is in CSS now 2017-01-11 11:35:33 +01:00
Lukas Martinelli
9ef24428fe Style open modal 2017-01-11 09:35:48 +01:00
Lukas Martinelli
4a75b0381b Move style code to CSS 2017-01-10 21:28:30 +01:00
Lukas Martinelli
2426117233 Tweaked colors #64 2017-01-10 19:41:39 +01:00
Lukas Martinelli
d40c704c69 Upgrade mapbox-gl-inspect to v1.0.9 2017-01-10 19:27:27 +01:00
Lukas Martinelli
cb4fdb0f9f Remove rasters in inspect style 2017-01-10 19:14:14 +01:00
Lukas Martinelli
f0d04bdb07 Prepare version for release 2017-01-10 19:14:06 +01:00
Lukas Martinelli
df61ae8c7a Add filter button on the right bottom 2017-01-10 19:02:06 +01:00
Lukas Martinelli
2ff8ec07bb Update style thumbs 2017-01-10 18:42:22 +01:00
Lukas Martinelli
6021b51385 Extra padding prevents hidden layers #61 2017-01-10 15:53:22 +01:00
Lukas Martinelli
40111e0d8e Fix minzoom and maxzoom in source modal 2017-01-10 15:35:13 +01:00
Lukas Martinelli
43d9440e05 White background for OL3 2017-01-10 14:32:45 +01:00
Lukas Martinelli
3a3e90c3dc Support TileJSON sources for OL3 2017-01-10 14:24:35 +01:00
Lukas Martinelli
104d6311ec Add missing IconInput 2017-01-10 14:05:46 +01:00
Lukas Martinelli
f5256cf80a Add missing metadata lib 2017-01-10 14:05:36 +01:00
Lukas Martinelli
b470885263 Use first vector source for OL3 2017-01-10 14:05:25 +01:00
Lukas Martinelli
7ff0ac9bb5 Upgrade ol-mapbox-style to v0.14 2017-01-10 12:04:19 +01:00
Lukas Martinelli
0fb59ca544 Load icon and font metadata from endpoint 2017-01-10 11:13:53 +01:00
Lukas Martinelli
09b6b2dffe Add Roboto Medium for groups #51 2017-01-10 10:06:49 +01:00
Lukas Martinelli
a8a3b7a5ad Always have default value if value not set 2017-01-10 09:51:18 +01:00
Lukas Martinelli
766a3e387e Fix many React warnings 2017-01-10 09:38:27 +01:00
Lukas Martinelli
ec9fc8f6ad Allow passing elems to DocLabel 2017-01-09 23:06:55 +01:00
Lukas Martinelli
0f272e233b Rename to FeatureLayerPopup 2017-01-09 23:04:08 +01:00
Lukas Martinelli
f806e797fa Fix non existing value warning 2017-01-09 23:02:17 +01:00
Lukas Martinelli
cff0a15f7e Show hint when hovering over function icon 2017-01-09 22:54:30 +01:00
Lukas Martinelli
d3276829b2 Show hints in the source modal as well 2017-01-09 22:44:22 +01:00
Lukas Martinelli
a3caf8499c Add DocLabel to settings modal 2017-01-09 22:37:21 +01:00
Lukas Martinelli
d739ca812c No source blocks for background layer 2017-01-09 22:27:54 +01:00
Lukas Martinelli
cb89ca6ef7 Show text when nested filter 2017-01-09 22:20:28 +01:00
Lukas Martinelli
c3417241f1 Ensure zoom icon is nice 2017-01-09 22:09:15 +01:00
Lukas Martinelli
5d70de6202 Center checkbox 2017-01-09 21:43:14 +01:00
Lukas Martinelli
c09ffc9d41 Tweak margins to realign 2017-01-09 21:39:35 +01:00
Lukas Martinelli
e19a41d015 Change filter layout again 2017-01-09 21:30:49 +01:00
Lukas Martinelli
0a0400a297 Rearrange and simplify filter layout 2017-01-09 21:07:51 +01:00
Lukas Martinelli
153232c143 Add filter editor block 2017-01-09 20:07:48 +01:00
Lukas Martinelli
7e8813f417 Split filter editor into component per file 2017-01-09 18:56:04 +01:00
Lukas Martinelli
b72f86a78d Improve grouping 2017-01-09 18:43:04 +01:00
Lukas Martinelli
fed530f5f2 Filter out combining operator select 2017-01-09 17:47:35 +01:00
Lukas Martinelli
ba0a94f3ad Use DocLabel in input block 2017-01-09 16:45:59 +01:00
Lukas Martinelli
d9b458d7fd Add label to filter editor 2017-01-09 16:40:09 +01:00
Lukas Martinelli
ed9b806143 Add filter item 2017-01-09 16:33:26 +01:00
Lukas Martinelli
5bb68a38c2 Support delete filter 2017-01-09 16:24:42 +01:00
Lukas Martinelli
cfeaf2cdce Support turning property into zoom func #52 2017-01-09 16:08:22 +01:00
Lukas Martinelli
887b23ce1f Merge pull request #59 from maputnik/switch_gl_inspect
Switch to Mapbox GL Inspect
2017-01-09 12:04:26 +01:00
Lukas Martinelli
f227392f9b Upgrade inspect to v1.0.7 2017-01-09 12:03:47 +01:00
Lukas Martinelli
2f7658e245 Only increase stack size in travis build 2017-01-09 11:39:52 +01:00
Lukas Martinelli
4f0c641eb0 Upgrade inspect 2017-01-09 00:08:50 +01:00
Lukas Martinelli
1538f2e174 Get highlight working 2017-01-08 23:19:21 +01:00
Lukas Martinelli
580068bf63 Show popup also on normal map 2017-01-08 22:44:25 +01:00
Lukas Martinelli
91604afccb Ensure style updates are applied after inspect 2017-01-08 22:16:45 +01:00
Lukas Martinelli
c363c88f23 Use Mapbox GL Inspect 2017-01-08 22:03:21 +01:00
Lukas Martinelli
e9daee4470 Add raster layout group 2017-01-08 19:58:19 +01:00
Lukas Martinelli
118f0360d0 Hide source layer for raster source 2017-01-08 19:47:43 +01:00
Lukas Martinelli
7c9dcb3083 Refactor sources modal 2017-01-08 19:45:44 +01:00
Lukas Martinelli
7c3906fa40 Add raster XYZ and TileJSON options #57 2017-01-08 18:50:32 +01:00
Lukas Martinelli
7b24cbf39b Increase stack size in node 2017-01-08 17:15:35 +01:00
Lukas Martinelli
e7b11d8bc9 Ensure editor does not crash with raster layers 2017-01-08 17:15:35 +01:00
Lukas Martinelli
08854cd88f Merge pull request #55 from klokantech/format
Format style on download
2017-01-06 16:53:20 +01:00
jirik
cb46ac5421 Format style on download 2017-01-06 15:48:57 +01:00
Lukas Martinelli
c9fd00e2ed Update README.md 2017-01-06 09:53:57 +01:00
Lukas Martinelli
7c23fe3646 Open style from url #34 2017-01-05 19:34:53 +01:00
Lukas Martinelli
56aacb0149 Do not generate created timestamp 2017-01-05 19:34:53 +01:00
Helge Fahrnberger
12411ee886 Linked myself 2017-01-04 20:26:04 +01:00
Lukas Martinelli
85cef2945d StringInput triggers change on out of focus #46 2017-01-04 12:06:55 +01:00
Lukas Martinelli
a1dfeca6e0 Keep existing metadata when toggling inspection #45 2017-01-04 12:00:00 +01:00
Lukas Martinelli
3be6d14637 No dropping of console 2017-01-04 12:00:00 +01:00
Lukas Martinelli
74b3ef9e88 Do not set modified date when saving 2017-01-04 12:00:00 +01:00
Lukas Martinelli
019dfe9f8a Update README.md 2017-01-01 22:29:22 +01:00
Lukas Martinelli
e92dfd8284 Fix local update to right this 2017-01-01 15:51:22 +01:00
Lukas Martinelli
fa38667125 Only init websocket if local API 2017-01-01 15:12:46 +01:00
Lukas Martinelli
ce39ae723c Add support local Maputnik 2017-01-01 14:49:32 +01:00
Lukas Martinelli
99acbd4d92 Ensure GeoJSON styling works 2016-12-31 15:15:28 +01:00
Lukas Martinelli
b0e9790382 Support updating of ArrayInput #39 2016-12-31 14:56:26 +01:00
Lukas Martinelli
e00cdde3af Reset JSONEditor after it loosed focus 2016-12-31 14:37:40 +01:00
Lukas Martinelli
c3a634b216 Show undo/redo message 2016-12-31 14:32:04 +01:00
Lukas Martinelli
4f26a521a0 Fix margins in source editing area 2016-12-31 14:02:14 +01:00
Lukas Martinelli
ca6b48843c Support adding, editing and removing stops 2016-12-31 13:42:25 +01:00
Lukas Martinelli
0eb00312f4 Restyle to use border box 2016-12-31 12:17:02 +01:00
Lukas Martinelli
e7709dae15 Notice for not supported nested filter 2016-12-31 11:08:14 +01:00
Lukas Martinelli
03796c963b Fix React type warnings 2016-12-31 10:39:30 +01:00
Lukas Martinelli
b50855a4a9 Restructure webpack and add favico 2016-12-31 10:36:02 +01:00
Lukas Martinelli
24a90b3c57 Set dark scrollbar globally 2016-12-30 21:02:39 +01:00
Lukas Martinelli
cf80e80025 Switch font input to AutocompleteInput 2016-12-30 20:53:12 +01:00
Lukas Martinelli
48f10bcb73 Convert Autocmplete from tabs to spaces 2016-12-30 20:46:27 +01:00
Lukas Martinelli
7bc2323401 Introduce AutocompelteInput for source editing 2016-12-30 20:38:50 +01:00
Lukas Martinelli
a71ac502d6 Remove style from PropertyGroup 2016-12-30 20:01:14 +01:00
Lukas Martinelli
f2dd785e7b Simplify font stacks and limit to one default value 2016-12-30 18:56:16 +01:00
Lukas Martinelli
0b99e571c4 Prototype FontInput field 2016-12-30 18:13:41 +01:00
Lukas Martinelli
cfc6085718 Add missing properties to symbol layer 2016-12-30 17:17:08 +01:00
Lukas Martinelli
384b2d4bea Show default value in color field 2016-12-30 17:16:58 +01:00
Lukas Martinelli
1058dbfb5a Hide sources when adding background layer 2016-12-30 17:06:08 +01:00
Lukas Martinelli
bda7ce7390 Move change property logic to lib 2016-12-30 16:56:20 +01:00
Lukas Martinelli
7b631b0510 Garbage collect properties when change type #42 2016-12-30 16:47:47 +01:00
Lukas Martinelli
1d7768e37c Make NumberInput more tolerant to errors 2016-12-30 16:18:57 +01:00
Lukas Martinelli
89d497c73f Error panel with current map style errors #40 2016-12-30 13:21:03 +01:00
Lukas Martinelli
886c87f231 Improve groups for symbol layer 2016-12-29 23:01:31 +01:00
Lukas Martinelli
d567a4f98b Add support for circle layer #30 2016-12-29 22:58:36 +01:00
Lukas Martinelli
5eb0e36faf Decrease doc label font size 2016-12-29 22:41:39 +01:00
Lukas Martinelli
51a2eabc91 Add ArrayInput 2016-12-29 22:37:54 +01:00
Lukas Martinelli
007bdad70a Also show MultiButton for 3 options 2016-12-29 22:12:36 +01:00
Lukas Martinelli
1f1a919c77 Only update style if it is valid 2016-12-29 22:00:49 +01:00
Lukas Martinelli
3be3a716d4 Only update if structure of JSON changes 2016-12-29 21:49:40 +01:00
Lukas Martinelli
ae9afdd8d9 Replace gone style store test with sample 2016-12-29 17:40:12 +01:00
Lukas Martinelli
a5307054b3 Fix setting style properties in settings modal 2016-12-29 17:32:23 +01:00
Lukas Martinelli
d16c3f4356 Always show all features in inspect mode 2016-12-29 17:30:01 +01:00
Lukas Martinelli
853361ace7 Indicate if feature is clickable 2016-12-29 17:00:36 +01:00
Lukas Martinelli
e41e1eb2f1 Inspection map is now always aware of current layer 2016-12-29 16:54:58 +01:00
Lukas Martinelli
e36c233b49 Remove highlighted layer from metadata in style 2016-12-29 15:51:11 +01:00
Lukas Martinelli
d1b8f8d63e Change map style to add layer 2016-12-29 15:35:07 +01:00
Lukas Martinelli
29cfb58a56 Update sources if they change 2016-12-29 15:22:47 +01:00
Lukas Martinelli
bf5131cadd Restructure layer settings for add modal 2016-12-29 14:46:04 +01:00
Lukas Martinelli
ccc39b87db Move storing access token into style metadata 2016-12-28 21:50:53 +01:00
Lukas Martinelli
604be38b7c Store highlighted layer in metadata 2016-12-28 21:50:53 +01:00
Lukas Martinelli
160bd9563b Introduce MultiInputButton 2016-12-28 21:50:53 +01:00
Lukas Martinelli
488fdf2bd5 Rename icons and add layer to toolbar 2016-12-28 21:50:53 +01:00
Lukas Martinelli
a0e1e6152b Merge pull request #36 from PetersonGIS/upgrade-mapboxgl
Upgraded reference to mapbox-gl to v0.29.0 to follow their latest webpack recommendations and support Windows dev. This is addressed in mapbox/mapbox-gl-js#3724
2016-12-28 21:46:11 +01:00
PetersonGIS
58897f1856 Upgraded reference to mapbox-gl to v0.29.0 to follow their latest webpack recommendations and support Windows dev. This is addressed in mapbox/mapbox-gl-js#3724 2016-12-28 11:10:27 -07:00
Lukas Martinelli
80678af691 Implement adding public and custom sources 2016-12-28 15:57:30 +01:00
Lukas Martinelli
ba271e1fc6 Allow deleting active source 2016-12-28 15:20:07 +01:00
Lukas Martinelli
c7ac90ba15 Fill extrusion support #31 2016-12-27 12:04:36 +01:00
Lukas Martinelli
0dc335ea5f Deref style on open 2016-12-26 12:21:41 +01:00
Lukas Martinelli
acac314d27 Improve input styling (it is still hacky) 2016-12-26 12:03:12 +01:00
Lukas Martinelli
916c1dc9fc No scrollbar style for JSON mode 2016-12-26 11:55:39 +01:00
Lukas Martinelli
c159f7041f Switch from field components to input components 2016-12-26 11:51:26 +01:00
Lukas Martinelli
a3d586a75d Give more space to layer editor 2016-12-26 11:22:41 +01:00
Lukas Martinelli
6b0b29d1da Increase font size to 12px 2016-12-25 20:36:10 +01:00
Lukas Martinelli
8afda2fe28 Adapt Mapbox GL css icon colors 2016-12-25 20:26:59 +01:00
Lukas Martinelli
beb1a2a8d1 Introduce doc label for help 2016-12-25 19:00:21 +01:00
Lukas Martinelli
436e0c2095 Hack together add and delete button for stops 2016-12-25 18:30:23 +01:00
Lukas Martinelli
e1bc2a321a Improve inspect popups 2016-12-25 17:46:18 +01:00
Lukas Martinelli
720c8f108b Add codemirror as dependency 2016-12-25 13:19:33 +01:00
Lukas Martinelli
4db5c7cf68 Better inspection hover 2016-12-24 22:57:14 +01:00
Lukas Martinelli
8f561d8a27 Show layer table 2016-12-24 22:57:14 +01:00
Lukas Martinelli
0c483cffe3 Allow hash for location 2016-12-24 22:57:14 +01:00
Lukas Martinelli
def5ebb587 Show feature table on hover 2016-12-24 22:57:14 +01:00
Lukas Martinelli
6e9e66b147 Switch renderer with inspect mode 2016-12-24 22:57:14 +01:00
Lukas Martinelli
f332d517f3 Add inspection map 2016-12-24 22:57:14 +01:00
Lukas Martinelli
04eab70e27 Add missing revision store 2016-12-24 22:57:14 +01:00
Lukas Martinelli
cfbcdc7fa1 Basic redo/undo with keybindings #25 2016-12-24 22:57:14 +01:00
Lukas Martinelli
c95dd75e2a Update README.md 2016-12-22 23:16:51 +01:00
Lukas Martinelli
4408f3ab3b Update README.md 2016-12-22 23:09:42 +01:00
185 changed files with 29985 additions and 2565 deletions

20
.babelrc Normal file
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/**"]
}]
]
}
}
}

103
.circleci/config.yml Normal file
View File

@@ -0,0 +1,103 @@
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-js
- run: npm run lint-css
- 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 profiling-build
- run: npm run lint-js
- run: npm run lint-css
- 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-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-linux-node-v13:
docker:
- image: node:13
working_directory: ~/repo-linux-node-v13
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
build-osx-node-v13:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@13
working_directory: ~/repo-osx-node-v13
steps: *build-steps
workflows:
version: 2
build:
jobs:
- build-linux-node-v10
- build-linux-node-v12
- build-linux-node-v13
- build-osx-node-v10
- build-osx-node-v12
- build-osx-node-v13

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
custom: "https://maputnik.github.io/donate"

5
.gitignore vendored
View File

@@ -1,6 +1,8 @@
# Logs
logs
*.log
*.swp
*.swo
# Runtime data
pids
@@ -28,3 +30,6 @@ node_modules
# Ignore build files
public
/errorShots
/old
/build

15
.topissuesrc Normal file
View File

@@ -0,0 +1,15 @@
{
"labels": {
"bug": 5,
"maintenance": 3,
"mentioned in the 1st survey": 2
},
"reactions": {
"+1": 2,
"-1": -1,
"laugh": 1,
"hooray": 2,
"confused": 1,
"heart": 2
}
}

View File

@@ -1,21 +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
- npm run build
- npm run lint
- npm run test

View File

@@ -1,4 +1,9 @@
FROM nodesource/xenial:6.1.0
FROM node:10-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
python \
&& rm -rf /var/lib/apt/lists/*
EXPOSE 8888
@@ -9,7 +14,8 @@ COPY . ${HOME}/
WORKDIR ${HOME}
RUN npm install -d --dev
RUN npm install -d
RUN npm run build
CMD npm run start -- --host 0.0.0.0
WORKDIR ${HOME}/build/build
CMD python -m SimpleHTTPServer 8888

181
README.md
View File

@@ -1,33 +1,47 @@
# 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="media/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/)
targeted at developers and map designers. Creating your own custom map is easy with **Maputnik**.
targeted at developers and map designers.
*Maputnik is an early prototype and is under development.
[Thanks to the supporters of the Kickstarter campaign who made this project possible](https://www.kickstarter.com/projects/174808720/maputnik-visual-map-editor-for-mapbox-gl)*.
- :link: Design your maps online at **<https://maputnik.github.io/editor/>** (all in local storage)
- :link: Try out the v1.7.0-beta release at: https://maputnik.github.io/releases/v1.7.0-beta/
- :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development
## Features
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.
- [x] Completely free and open source
- [x] Visual interface for designing maps
- [x] Immediate feedback (thanks to [style diffs](https://github.com/mapbox/mapbox-gl-style-spec/blob/mb-pages/lib/diff.js))
- [x] Edit layers
- [x] Easy to deploy as single HTML file
- [ ] Support for Open Layers 3
![Demo showing interactive feedback](media/demo.gif)
## Donations
If you or your organisation has seen value from Maputnik, please consider donating at <https://maputnik.github.io/donate>
## Documentation
The documentation can be found in the [Wiki](https://github.com/maputnik/editor/wiki). You are welcome to collaborate!
- :link: **Study the [Maputnik Wiki](https://github.com/maputnik/editor/wiki)**
- :video_camera: Design a map from Scratch https://youtu.be/XoDh0gEnBQo
[![Design Map from Scratch](https://j.gifs.com/g5XMgl.gif)](https://youtu.be/XoDh0gEnBQo)
## Develop
Maputnik is written in ES6 and is using [React](https://github.com/facebook/react), [Immutable.js](https://facebook.github.io/immutable-js/) 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
- Linux, OSX and Windows
- Node >4
We ensure building and developing Maputnik works with the [current active LTS Node.js version and above](https://github.com/nodejs/Release#release-schedule).
Install the deps, start the dev server and open the web browser on `http://localhost:8888/`.
@@ -38,7 +52,18 @@ npm install
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
@@ -47,108 +72,100 @@ npm run build
Lint the JavaScript code.
```
# install lint dependencies
npm install --save-dev eslint eslint-plugin-react
# run linter
npm run lint
npm run lint-styles
```
## Docker
Start a container using the official Docker image.
```
docker run --name maputnik -p 8888:8888 -d maputnik/editor
```
## Tests
For testing we use [webdriverio](http://webdriver.io) and [selenium-standalone](https://github.com/vvo/selenium-standalone)
Stop the container
[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
```
docker stop maputnik
./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
This project would not be possible without 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.
### Gold
[![Wemap](media/sponsors/wemap.jpg)](https://getwemap.com/)
- [Wemap](https://getwemap.com/)
- [Orbicon Informatik](https://www.orbiconinformatik.dk/)
- [Terranodo](http://terranodo.io/)
[![Terranodo](media/sponsors/terranodo.png)](http://terranodo.io/)
<a href="https://getwemap.com/">
<img width="33%" alt="Wemap" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/wemap.jpg" />
</a>
<a href="http://terranodo.io/">
<img width="33%" alt="Terranodo" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/terranodo.png" />
</a>
<a href="https://www.orbiconinformatik.dk/">
<img width="32%" alt="Terranodo" style="display:inline" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/orbicon_informatik.png" />
</a>
<br/>
### Silver
- [Klokan Technologies](https://www.klokantech.com/)
- [Geofabrik](http://www.geofabrik.de/)
- [Dreipol](https://www.dreipol.ch/)
<a href="https://www.klokantech.com/">
<img alt="Klokan Technologies" style="display:inline" src="media/sponsors/klokantech.png" />
<img width="18%" alt="Klokan Technologies" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/klokantech.png" />
</a>
<a href="http://www.geofabrik.de/">
<img width="18%" alt="Geofabrik" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/geofabrik.png" />
</a>
<a href="https://www.dreipol.ch/">
<img alt="Dreipol" style="display:inline" src="media/sponsors/dreipol.png" />
<img width="18%" alt="Dreipol" style="display:inline-block" src="https://cdn.jsdelivr.net/gh/maputnik/editor@1.5.0/media/sponsors/dreipol.png" />
</a>
<br/>
### Individuals
**Influential Stakeholder**
- Alan McConchie
- Odi
- Mats Norén
- Uli [geOps](http://geops.ch/)
- Helge Fahrnberger
Kirusanth Poopalasingam
Alan McConchie, Odi, Mats Norén, Uli [geOps](http://geops.ch/), Helge Fahrnberger ([Toursprung](http://www.toursprung.com/)), Kirusanth Poopalasingam
**Stakeholder**
- Brian Flood
- Vasile Coțovanu
- Andreas Kalkbrenner
- Christian Mäder
- Gregor Wassmann
- Lee Armstrong
- Rafel
- Jon Burgess
- Lukas Lehmann
- Joachim Ungar
- Alois Ackermann
- Zsolt Ero
- Jordan Meek
Brian Flood, Vasile Coțovanu, Andreas Kalkbrenner, Christian Mäder, Gregor Wassmann, Lee Armstrong, Rafel, Jon Burgess, Lukas Lehmann, Joachim Ungar, Alois Ackermann, Zsolt Ero, Jordan Meek
**Supporter**
- Sina Martinelli
- Nicholas Doiron
- Neil Cawse
- Urs42
- Benedikt Groß
- Manuel Roth
- Janko Mihelić
- Moritz Stefaner
- Sebastian Ahoi
- Juerg Uhlmann
- Tom Wider
- Nadia Panchaud
- Oliver Snowden
- Stephan Heuel
- Tobin Bradley
- Adrian Herzog
- Antti Lehto
- Pascal Mages
- Marc Gehling
- Imre Samu
- Lauri K.
- Visahavel Parthasarathy
- Christophe Waterlot-Buisine
- Max Galka
- ubahnverleih
- Wouter van Dam
- Jakob Lobensteiner
- Samuel Kurath
- Brian Bancroft
Sina Martinelli, Nicholas Doiron, Neil Cawse, Urs42, Benedikt Groß, Manuel Roth, Janko Mihelić, Moritz Stefaner, Sebastian Ahoi, Juerg Uhlmann, Tom Wider, Nadia Panchaud, Oliver Snowden, Stephan Heuel, Tobin Bradley, Adrian Herzog, Antti Lehto, Pascal Mages, Marc Gehling, Imre Samu, Lauri K., Visahavel Parthasarathy, Christophe Waterlot-Buisine, Max Galka, ubahnverleih, Wouter van Dam, Jakob Lobensteiner, Samuel Kurath, Brian Bancroft
## License
Maputnik is [licensed under MIT](LICENSE) and is Copyright (c) Lukas Martinelli and contributors.
**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is a independent style editor for the
**Disclaimer** This project is not affiliated with Mapbox or Mapbox Studio. It is an independent style editor for the
open source technology in the Mapbox GL ecosystem.
As contributor please take extra care of not violating any Mapbox trademarks. Do not get inspired by Mapbox Studio and make your own decisions for a good style editor.

View File

@@ -1,14 +1,26 @@
image: Visual Studio 2019
environment:
matrix:
- nodejs_version: "4.6"
- nodejs_version: "5.11"
- nodejs_version: "6.1"
- nodejs_version: "10"
- nodejs_version: "12"
- nodejs_version: "13"
platform:
- x86
- x64
install:
- ps: Install-Product node $env:nodejs_version
# https://github.com/appveyor/ci/issues/2921#issuecomment-501016533
- ps: |
try {
Install-Product node $env:nodejs_version $env:platform
} catch {
echo "Unable to install node $env:nodejs_version, trying update..."
Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
}
- md public
- npm install --global windows-build-tools
- npm install
build_script:
- npm run build
test_script:
- npm run lint
- npm test
- npm run lint-js
- npm run lint-css

291
config/wdio.conf.js Normal file
View File

@@ -0,0 +1,291 @@
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.
hostname: 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.
//
// 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);
const serverHost = isDocker() ? "0.0.0.0" : "localhost";
server = new WebpackDevServer(compiler, {
host: serverHost,
stats: {
colors: true
}
});
server.listen(testConfig.port, serverHost, 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) {
//}
}

View File

@@ -1,24 +1,17 @@
"use strict";
var webpack = require('webpack');
var path = require('path');
var loaders = require('./webpack.loaders');
var rules = require('./webpack.rules');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
const HOST = process.env.HOST || "127.0.0.1";
const PORT = process.env.PORT || "8888";
// local scss modules
loaders.push({
test: /[\/\\]src[\/\\].*\.scss/,
loaders: [
'style?sourceMap',
'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]',
'sass'
]
});
module.exports = {
target: 'web',
mode: 'development',
entry: [
`webpack-dev-server/client?http://${HOST}:${PORT}`,
`webpack/hot/only-dev-server`,
@@ -26,24 +19,17 @@ module.exports = {
],
devtool: process.env.WEBPACK_DEVTOOL || 'cheap-module-source-map',
output: {
path: path.join(__dirname, 'public'),
path: path.join(__dirname, '..', 'public'),
filename: 'bundle.js'
},
resolve: {
alias: {
'webworkify': 'webworkify-webpack',
// TODO: otherwise I get a max call stack error in browser?
// 'mapbox-gl': path.resolve('./node_modules/mapbox-gl/dist/mapbox-gl.js')
},
extensions: ['', '.js', '.jsx']
extensions: ['.js', '.jsx']
},
module: {
loaders,
postLoaders: [{
include: /node_modules\/mapbox-gl\/js\/render\/shaders.js/,
loader: 'transform',
query: 'brfs'
}]
noParse: [
/mapbox-gl\/dist\/mapbox-gl.js/
],
rules: rules
},
node: {
fs: "empty",
@@ -61,14 +47,29 @@ module.exports = {
// serve index.html in place of 404 responses to allow HTML5 history
historyApiFallback: true,
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: [
new webpack.NoErrorsPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title: 'Maputnik',
template: './src/template.html'
}),
new HtmlWebpackInlineSVGPlugin({
runPreEmit: true,
}),
new CopyWebpackPlugin([
{
from: './src/manifest.json',
to: 'manifest.json'
}
])
]
};

View File

@@ -0,0 +1,66 @@
var webpack = require('webpack');
var path = require('path');
var rules = require('./webpack.rules');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-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 HtmlWebpackInlineSVGPlugin({
runPreEmit: true,
}),
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',
})
]
};

View File

@@ -0,0 +1,20 @@
const webpackProdConfig = require('./webpack.production.config');
const artifacts = require("../test/artifacts");
const OUTPATH = artifacts.pathSync("/profiling");
module.exports = {
...webpackProdConfig,
output: {
...webpackProdConfig.output,
path: OUTPATH,
},
resolve: {
...webpackProdConfig.resolve,
alias: {
...webpackProdConfig.resolve.alias,
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}
}
};

44
config/webpack.rules.js Normal file
View File

@@ -0,0 +1,44 @@
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: /\.(gif|jpg|png)$/,
use: 'file-loader?name=img/[name].[ext]'
},
{
test: /\.svg$/,
use: [
'svg-inline-loader'
]
},
{
test: /[\/\\](node_modules|global|src)[\/\\].*\.scss$/,
use: [
'style-loader',
"css-loader",
"sass-loader"
]
},
{
test: /[\/\\](node_modules|global|src)[\/\\].*\.css$/,
use: [
'style-loader',
'css-loader'
]
}
];

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'
}
});
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 37 KiB

17530
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,18 @@
{
"name": "maputnik",
"version": "0.0.1",
"version": "1.7.0-beta2",
"description": "A MapboxGL visual style editor",
"main": "''",
"scripts": {
"stats": "webpack --config webpack.production.config.js --profile --json > stats.json",
"build": "webpack --config webpack.production.config.js --progress --profile --colors",
"test": "karma start --single-run",
"test-watch": "karma start",
"start": "webpack-dev-server --progress --profile --colors --watch-poll",
"lint": "eslint --ext js --ext jsx {src,test}"
"stats": "webpack --config config/webpack.production.config.js --profile --json > stats.json",
"build": "webpack --config config/webpack.production.config.js --progress --profile --colors",
"profiling-build": "webpack --config config/webpack.profiling.config.js --progress --profile --colors",
"test": "cross-env NODE_ENV=test wdio config/wdio.conf.js",
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
"start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
"lint-js": "eslint --ext js --ext jsx src test",
"lint-css": "stylelint \"src/styles/*.scss\"",
"lint": "npm run lint-js && npm run lint-css"
},
"repository": {
"type": "git",
@@ -19,47 +22,71 @@
"license": "MIT",
"homepage": "https://github.com/maputnik/editor#readme",
"dependencies": {
"color": "^1.0.3",
"file-saver": "^1.3.2",
"@babel/runtime": "^7.8.4",
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",
"@mapbox/mapbox-gl-style-spec": "^13.12.0",
"@mdi/react": "^1.4.0",
"array-move": "^2.2.1",
"classnames": "^2.2.6",
"codemirror": "^5.52.0",
"color": "^3.1.2",
"detect-browser": "^5.0.0",
"file-saver": "^2.0.2",
"json-to-ast": "^2.1.0",
"jsonlint": "github:josdejong/jsonlint#85a19d7",
"lodash": "^4.17.15",
"lodash.capitalize": "^4.2.1",
"lodash.clamp": "^4.0.3",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"lodash.topairs": "^4.3.0",
"mapbox-gl": "mapbox/mapbox-gl-js#6c24b9621d2aa770eda67fb5638b4d78087b5624",
"mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#e85407a377510acb647161de6be6357ab4f606dd",
"ol-mapbox-style": "0.0.11",
"openlayers": "^3.19.1",
"randomcolor": "^0.4.4",
"react": "^15.4.0",
"react-addons-pure-render-mixin": "^15.4.0",
"react-codemirror": "^0.3.0",
"react-collapse": "^2.3.3",
"react-color": "^2.10.0",
"react-dom": "^15.4.0",
"react-file-reader-input": "^1.1.0",
"react-height": "^2.1.1",
"react-icon-base": "^2.0.4",
"react-icons": "^2.2.1",
"react-motion": "^0.4.7",
"react-sortable-hoc": "^0.4.5",
"request": "^2.79.0"
},
"babel": {
"presets": [
"es2015",
"react"
],
"plugins": [
"transform-object-rest-spread"
]
"mapbox-gl": "^1.9.1",
"mapbox-gl-inspect": "^1.3.1",
"maputnik-design": "github:maputnik/design#f7a2b4d",
"ol": "^6.3.1",
"ol-mapbox-style": "^6.0.1",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-accessible-accordion": "^3.0.1",
"react-aria-menubutton": "^6.3.0",
"react-aria-modal": "^4.0.0",
"react-autobind": "^1.0.6",
"react-autocomplete": "^1.8.1",
"react-collapse": "^5.0.1",
"react-color": "^2.18.0",
"react-dom": "^16.12.0",
"react-file-reader-input": "^2.0.0",
"react-icon-base": "^2.1.2",
"react-icons": "^3.9.0",
"react-motion": "^0.5.2",
"react-sortable-hoc": "^1.11.0",
"reconnecting-websocket": "^4.4.0",
"slugify": "^1.3.6",
"url": "^0.11.0"
},
"jshintConfig": {
"esversion": 6
},
"stylelint": {
"extends": "stylelint-config-recommended-scss",
"rules": {
"no-descending-specificity": null,
"media-feature-name-no-unknown": [
true,
{
"ignoreMediaFeatureNames": [
"prefers-reduced-motion"
]
}
]
}
},
"eslintConfig": {
"plugins": [
"react"
],
"extend": [
"extends": [
"plugin:react/recommended"
],
"env": {
@@ -76,43 +103,62 @@
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"settings": {
"react": {
"version": "detect"
}
}
},
"devDependencies": {
"babel-core": "6.14.0",
"babel-eslint": "^6.1.2",
"babel-loader": "6.2.4",
"babel-plugin-transform-class-properties": "^6.11.5",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-flow-strip-types": "^6.21.0",
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "6.14.0",
"babel-preset-react": "6.11.1",
"babel-runtime": "^6.11.6",
"css-loader": "0.25.0",
"eslint": "^3.5.0",
"eslint-plugin-react": "^6.2.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "0.9.0",
"html-webpack-plugin": "^2.22.0",
"json-loader": "^0.5.4",
"karma": "^1.3.0",
"karma-chrome-launcher": "^2.0.0",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.3.0",
"karma-webpack": "^1.8.0",
"mocha": "^3.1.2",
"mocha-loader": "^1.0.0",
"node-sass": "^3.9.2",
"react-hot-loader": "^3.0.0-beta.6",
"sass-loader": "^4.0.1",
"style-loader": "0.13.1",
"transform-loader": "^0.2.3",
"url-loader": "0.5.7",
"webpack": "1.13.2",
"webpack-cleanup-plugin": "^0.3.0",
"webpack-dev-server": "1.15.1",
"webworkify-webpack": "^1.1.3"
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/preset-env": "^7.6.3",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.6.3",
"@mdi/js": "^5.0.45",
"@wdio/cli": "^6.0.5",
"@wdio/local-runner": "^6.0.5",
"@wdio/mocha-framework": "^6.0.4",
"@wdio/selenium-standalone-service": "^6.0.4",
"@wdio/spec-reporter": "^6.0.4",
"@wdio/sync": "^6.0.1",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.1.0",
"babel-plugin-istanbul": "^6.0.0",
"babel-plugin-static-fs": "^3.0.0",
"copy-webpack-plugin": "^5.1.1",
"cors": "^2.8.5",
"cross-env": "^7.0.0",
"css-loader": "^3.4.2",
"eslint": "^6.8.0",
"eslint-plugin-react": "^7.18.3",
"express": "^4.17.1",
"file-loader": "^6.0.0",
"html-webpack-inline-svg-plugin": "^1.3.0",
"html-webpack-plugin": "^3.2.0",
"is-docker": "^2.0.0",
"istanbul": "^0.4.5",
"istanbul-lib-coverage": "^3.0.0",
"mkdirp": "^1.0.4",
"mocha": "^7.0.1",
"node-sass": "^4.13.1",
"react-hot-loader": "^4.12.19",
"sass-loader": "^8.0.2",
"selenium-standalone": "^6.17.0",
"style-loader": "^1.1.3",
"stylelint": "^13.3.0",
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^3.14.2",
"svg-inline-loader": "^0.8.2",
"transform-loader": "^0.2.4",
"uuid": "^7.0.3",
"webdriverio": "^6.0.5",
"webpack": "^4.41.6",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cleanup-plugin": "^0.5.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}

View File

@@ -1,68 +1,471 @@
import autoBind from 'react-autobind';
import React from 'react'
import { saveAs } from 'file-saver'
import cloneDeep from 'lodash.clonedeep'
import clamp from 'lodash.clamp'
import get from 'lodash.get'
import {unset} from 'lodash'
import arrayMove from 'array-move'
import url from 'url'
import MapboxGlMap from './map/MapboxGlMap'
import OpenLayers3Map from './map/OpenLayers3Map'
import OpenLayersMap from './map/OpenLayersMap'
import LayerList from './layers/LayerList'
import LayerEditor from './layers/LayerEditor'
import Toolbar from './Toolbar'
import AppLayout from './AppLayout'
import MessagePanel from './MessagePanel'
import style from '../libs/style.js'
import { loadDefaultStyle, SettingsStore, StyleStore } from '../libs/stylestore'
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 {latest, validate} from '@mapbox/mapbox-gl-style-spec'
import style from '../libs/style'
import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
import { undoMessages, redoMessages } from '../libs/diffmessage'
import { StyleStore } from '../libs/stylestore'
import { ApiStyleStore } from '../libs/apistore'
import { RevisionStore } from '../libs/revisions'
import LayerWatcher from '../libs/layerwatcher'
import tokens from '../config/tokens.json'
import isEqual from 'lodash.isequal'
import Debug from '../libs/debug'
import queryUtil from '../libs/query-util'
import {formatLayerId} from './util/format';
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) {
return {
...spec,
$root: {
...spec.$root,
[fieldName]: {
...spec.$root[fieldName],
values: newValues
}
}
}
}
export default class App extends React.Component {
constructor(props) {
super(props)
this.layerWatcher = new LayerWatcher()
this.styleStore = new ApiStyleStore()
this.styleStore.supported(isSupported => {
if(!isSupported) {
console.log('Falling back to local storage for storing styles')
this.styleStore = new StyleStore()
}
this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle))
autoBind(this);
this.revisionStore = new RevisionStore()
const params = new URLSearchParams(window.location.search.substring(1))
let port = params.get("localport")
if (port == null && (window.location.port != 80 && window.location.port != 443)) {
port = window.location.port
}
this.styleStore = new ApiStyleStore({
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, {save: false}),
port: port,
host: params.get("localhost")
})
this.settingsStore = new SettingsStore()
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()
if(styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?")) {
this.styleStore = new StyleStore()
loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle))
removeStyleQuerystring()
} else {
if(styleUrl) {
removeStyleQuerystring()
}
this.styleStore.init(err => {
if(err) {
console.log('Falling back to local storage for storing styles')
this.styleStore = new StyleStore()
}
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 = {
accessToken: this.settingsStore.accessToken,
errors: [],
infos: [],
mapStyle: style.emptyStyle,
selectedLayerIndex: 0,
sources: {},
vectorLayers: {},
mapState: "map",
spec: latest,
mapView: {
zoom: 0,
center: {
lng: 0,
lat: 0,
},
},
isOpen: {
settings: false,
sources: false,
open: false,
shortcuts: false,
export: false,
// TODO: Disabled for now, this should be opened on the Nth visit to the editor
survey: false,
debug: false,
},
mapboxGlDebugOptions: {
showTileBoundaries: false,
showCollisionBoxes: false,
showOverdrawInspector: false,
},
openlayersDebugOptions: {
debugToolbox: false,
},
}
this.layerWatcher = new LayerWatcher({
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);
}
}
}
onReset() {
this.styleStore.purge()
loadDefaultStyle(mapStyle => this.onStyleOpen(mapStyle))
componentDidMount() {
window.addEventListener("keydown", this.handleKeyPress);
}
onStyleDownload() {
const mapStyle = this.state.mapStyle
const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"});
saveAs(blob, mapStyle.id + ".json");
componentWillUnmount() {
window.removeEventListener("keydown", this.handleKeyPress);
}
saveStyle(snapshotStyle) {
snapshotStyle.modified = new Date().toJSON()
this.styleStore.save(snapshotStyle)
}
onStyleChanged(newStyle) {
this.saveStyle(newStyle)
this.setState({ mapStyle: newStyle })
updateFonts(urlTemplate) {
const metadata = this.state.mapStyle.metadata || {}
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
let glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate;
downloadGlyphsMetadata(glyphUrl, fonts => {
this.setState({ spec: updateRootSpec(this.state.spec, 'glyphs', fonts)})
})
}
onAccessTokenChanged(newToken) {
this.settingsStore.accessToken = newToken
this.setState({ accessToken: newToken })
updateIcons(baseUrl) {
downloadSpriteMetadata(baseUrl, icons => {
this.setState({ spec: updateRootSpec(this.state.spec, 'sprite', icons)})
})
}
onLayersChange(changedLayers) {
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, opts={}) => {
opts = {
save: true,
addRevision: true,
...opts,
};
const errors = validate(newStyle, latest) || [];
// The validate function doesn't give us errors for duplicate error with
// empty string for layer.id, manually deal with that here.
const layerErrors = [];
if (newStyle && newStyle.layers) {
const foundLayers = new Map();
newStyle.layers.forEach((layer, index) => {
if (layer.id === "" && foundLayers.has(layer.id)) {
const message = `Duplicate layer: ${formatLayerId(layer.id)}`;
const error = new Error(
`layers[${index}]: duplicate layer id [empty_string], previously used`
);
layerErrors.push(error);
}
foundLayers.set(layer.id, true);
});
}
const mappedErrors = layerErrors.concat(errors).map(error => {
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
if (dupMatch) {
const [matchStr, index, message] = dupMatch;
return {
message: error.message,
parsed: {
type: "layer",
data: {
index: parseInt(index, 10),
key: "id",
message,
}
}
}
}
// duplicate layer id
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
if (layerMatch) {
const [matchStr, index, group, property, message] = layerMatch;
const key = (group && property) ? [group, property].join(".") : property;
return {
message: error.message,
parsed: {
type: "layer",
data: {
index: parseInt(index, 10),
key,
message
}
}
}
}
else {
return {
message: error.message,
};
}
});
let dirtyMapStyle = undefined;
if (errors.length > 0) {
dirtyMapStyle = cloneDeep(newStyle);
errors.forEach(error => {
const {message} = error;
if (message) {
try {
const objPath = message.split(":")[0];
// Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^\[]+/)[0];
unset(dirtyMapStyle, unsetPath);
}
catch (err) {
console.warn(err);
}
}
});
}
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
this.updateFonts(newStyle.glyphs)
}
if(newStyle.sprite !== this.state.mapStyle.sprite) {
this.updateIcons(newStyle.sprite)
}
if (opts.addRevision) {
this.revisionStore.addRevision(newStyle);
}
if (opts.save) {
this.saveStyle(newStyle);
}
this.setState({
mapStyle: newStyle,
dirtyMapStyle: dirtyMapStyle,
errors: mappedErrors,
})
this.fetchSources();
}
onUndo = () => {
const activeStyle = this.revisionStore.undo()
const messages = undoMessages(this.state.mapStyle, activeStyle)
this.onStyleChanged(activeStyle, {addRevision: false});
this.setState({
infos: messages,
})
}
onRedo = () => {
const activeStyle = this.revisionStore.redo()
const messages = redoMessages(this.state.mapStyle, activeStyle)
this.onStyleChanged(activeStyle, {addRevision: false});
this.setState({
infos: messages,
})
}
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 = {
...this.state.mapStyle,
layers: changedLayers
@@ -70,83 +473,359 @@ export default class App extends React.Component {
this.onStyleChanged(changedStyle)
}
onLayerIdChange(oldId, newId) {
const changedLayers = this.state.mapStyle.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, oldId)
onLayerDestroy = (index) => {
let layers = this.state.mapStyle.layers;
const remainingLayers = layers.slice(0);
remainingLayers.splice(index, 1);
this.onLayersChange(remainingLayers);
}
changedLayers[idx] = {
...changedLayers[idx],
onLayerCopy = (index) => {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
const clonedLayer = cloneDeep(changedLayers[index])
clonedLayer.id = clonedLayer.id + "-copy"
changedLayers.splice(index, 0, clonedLayer)
this.onLayersChange(changedLayers)
}
onLayerVisibilityToggle = (index) => {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
const layer = { ...changedLayers[index] }
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
layer.layout = changedLayout
changedLayers[index] = layer
this.onLayersChange(changedLayers)
}
onLayerIdChange = (index, oldId, newId) => {
const changedLayers = this.state.mapStyle.layers.slice(0)
changedLayers[index] = {
...changedLayers[index],
id: newId
}
this.onLayersChange(changedLayers)
}
onLayerChanged(layer) {
onLayerChanged = (index, layer) => {
const changedLayers = this.state.mapStyle.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layer.id)
changedLayers[idx] = layer
changedLayers[index] = layer
this.onLayersChange(changedLayers)
}
mapRenderer() {
const mapProps = {
mapStyle: this.state.mapStyle,
accessToken: this.state.accessToken,
onMapLoaded: (map) => {
this.layerWatcher.map = map
setMapState = (newState) => {
this.setState({
mapState: newState
})
}
setDefaultValues = (styleObj) => {
const metadata = styleObj.metadata || {}
if(metadata['maputnik:renderer'] === undefined) {
const changedStyle = {
...styleObj,
metadata: {
...styleObj.metadata,
'maputnik:renderer': 'mbgljs'
}
}
}
const metadata = this.state.mapStyle.metadata || {}
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
// Check if OL3 code has been loaded?
if(renderer === 'ol3') {
return <OpenLayers3Map {...mapProps} />
return changedStyle
} else {
return <MapboxGlMap {...mapProps} />
return styleObj
}
}
onLayerSelect(layerId) {
const idx = style.indexOfLayer(this.state.mapStyle.layers, layerId)
this.setState({ selectedLayerIndex: idx })
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';
}
onMapChange = (mapView) => {
this.setState({
mapView,
});
}
mapRenderer() {
const {mapStyle, dirtyMapStyle} = this.state;
const metadata = this.state.mapStyle.metadata || {};
const mapProps = {
mapStyle: (dirtyMapStyle || mapStyle),
replaceAccessTokens: (mapStyle) => {
return style.replaceAccessTokens(mapStyle, {
allowFallback: true
});
},
onDataChange: (e) => {
this.layerWatcher.analyzeMap(e.map)
this.fetchSources();
},
}
const renderer = this._getRenderer();
let mapElement;
// Check if OL code has been loaded?
if(renderer === 'ol') {
mapElement = <OpenLayersMap
{...mapProps}
onChange={this.onMapChange}
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
onLayerSelect={this.onLayerSelect}
/>
} else {
mapElement = <MapboxGlMap {...mapProps}
onChange={this.onMapChange}
options={this.state.mapboxGlDebugOptions}
inspectModeEnabled={this.state.mapState === "inspect"}
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
onLayerSelect={this.onLayerSelect} />
}
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 = (index) => {
this.setState({ selectedLayerIndex: index })
}
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() {
const layers = this.state.mapStyle.layers || []
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
const metadata = this.state.mapStyle.metadata || {}
const toolbar = <Toolbar
renderer={this._getRenderer()}
mapState={this.state.mapState}
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged.bind(this)}
onStyleOpen={this.onStyleChanged.bind(this)}
onStyleDownload={this.onStyleDownload.bind(this)}
inspectModeEnabled={this.state.mapState === "inspect"}
sources={this.state.sources}
onStyleChanged={this.onStyleChanged}
onStyleOpen={this.onStyleChanged}
onSetMapState={this.setMapState}
onToggleModal={this.toggleModal.bind(this)}
/>
const layerList = <LayerList
onLayersChange={this.onLayersChange.bind(this)}
onLayerSelect={this.onLayerSelect.bind(this)}
onMoveLayer={this.onMoveLayer}
onLayerDestroy={this.onLayerDestroy}
onLayerCopy={this.onLayerCopy}
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
onLayersChange={this.onLayersChange}
onLayerSelect={this.onLayerSelect}
selectedLayerIndex={this.state.selectedLayerIndex}
layers={layers}
sources={this.state.sources}
errors={this.state.errors}
/>
const layerEditor = selectedLayer ? <LayerEditor
key={selectedLayer.id}
layer={selectedLayer}
sources={this.layerWatcher.sources}
vectorLayers={this.layerWatcher.vectorLayers}
onLayerChanged={this.onLayerChanged.bind(this)}
onLayerIdChange={this.onLayerIdChange.bind(this)}
layerIndex={this.state.selectedLayerIndex}
isFirstLayer={this.state.selectedLayerIndex < 1}
isLastLayer={this.state.selectedLayerIndex === this.state.mapStyle.layers.length-1}
sources={this.state.sources}
vectorLayers={this.state.vectorLayers}
spec={this.state.spec}
onMoveLayer={this.onMoveLayer}
onLayerChanged={this.onLayerChanged}
onLayerDestroy={this.onLayerDestroy}
onLayerCopy={this.onLayerCopy}
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
onLayerIdChange={this.onLayerIdChange}
errors={this.state.errors}
/> : null
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
currentLayer={selectedLayer}
selectedLayerIndex={this.state.selectedLayerIndex}
onLayerSelect={this.onLayerSelect}
mapStyle={this.state.mapStyle}
errors={this.state.errors}
infos={this.state.infos}
/> : 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')}
mapView={this.state.mapView}
/>
<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
toolbar={toolbar}
layerList={layerList}
layerEditor={layerEditor}
map={this.mapRenderer()}
bottom={bottomPanel}
modals={modals}
/>
}
}

View File

@@ -1,19 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import ScrollContainer from './ScrollContainer'
import theme from '../config/theme'
import colors from '../config/colors'
class AppLayout extends React.Component {
static propTypes = {
toolbar: React.PropTypes.element.isRequired,
layerList: React.PropTypes.element.isRequired,
layerEditor: React.PropTypes.element,
map: React.PropTypes.element.isRequired,
toolbar: PropTypes.element.isRequired,
layerList: PropTypes.element.isRequired,
layerEditor: PropTypes.element,
map: PropTypes.element.isRequired,
bottom: PropTypes.element,
modals: PropTypes.node,
}
static childContextTypes = {
reactIconBase: React.PropTypes.object
reactIconBase: PropTypes.object
}
getChildContext() {
@@ -23,42 +23,22 @@ class AppLayout extends React.Component {
}
render() {
return <div style={{
fontFamily: theme.fontFamily,
color: theme.color,
fontWeight: 300
}}>
return <div className="maputnik-layout">
{this.props.toolbar}
<div style={{
position: 'fixed',
bottom: 0,
height: "100%",
top: 40,
left: 0,
zIndex: 1,
width: 200,
overflow: "hidden",
backgroundColor: colors.black
}}>
<ScrollContainer>
{this.props.layerList}
</ScrollContainer>
<div className="maputnik-layout-list">
{this.props.layerList}
</div>
<div style={{
position: 'fixed',
bottom: 0,
height: "100%",
top: 40,
left: 200,
zIndex: 1,
width: 300,
backgroundColor: colors.black
}}>
<div className="maputnik-layout-drawer">
<ScrollContainer>
{this.props.layerEditor}
</ScrollContainer>
</div>
{this.props.map}
{this.props.bottom && <div className="maputnik-layout-bottom">
{this.props.bottom}
</div>
}
{this.props.modals}
</div>
}
}

View File

@@ -1,28 +1,29 @@
import React from 'react'
import colors from '../config/colors'
import { margins, fontSizes } from '../config/scales'
import PropTypes from 'prop-types'
import classnames from 'classnames'
class Button extends React.Component {
static propTypes = {
onClick: React.PropTypes.func,
style: React.PropTypes.object,
"data-wd-key": PropTypes.string,
"aria-label": PropTypes.string,
onClick: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string,
children: PropTypes.node,
disabled: PropTypes.bool,
}
render() {
return <a
return <button
onClick={this.props.onClick}
style={{
cursor: 'pointer',
backgroundColor: colors.midgray,
color: colors.lowgray,
fontSize: fontSizes[4],
padding: margins[1],
userSelect: 'none',
borderRadius: 2,
...this.props.style,
}}>
disabled={this.props.disabled}
aria-label={this.props["aria-label"]}
className={classnames("maputnik-button", this.props.className)}
data-wd-key={this.props["data-wd-key"]}
style={this.props.style}
>
{this.props.children}
</a>
</button>
}
}

View File

@@ -1,32 +0,0 @@
import React from 'react'
import { fontSizes, margins } from '../config/scales'
class Heading extends React.Component {
static propTypes = {
level: React.PropTypes.number.isRequired,
style: React.PropTypes.object,
}
render() {
const headingProps = {
style: {
fontWeight: 400,
fontSize: fontSizes[this.props.level - 1],
marginBottom: margins[1],
...this.props.style
}
}
switch(this.props.level) {
case 1: return <h1 {...headingProps}>{this.props.children}</h1>
case 2: return <h2 {...headingProps}>{this.props.children}</h2>
case 3: return <h3 {...headingProps}>{this.props.children}</h3>
case 4: return <h4 {...headingProps}>{this.props.children}</h4>
case 5: return <h5 {...headingProps}>{this.props.children}</h5>
default: return <h6 {...headingProps}>{this.props.children}</h6>
}
}
}
export default Heading

View File

@@ -0,0 +1,64 @@
import React from 'react'
import PropTypes from 'prop-types'
import {formatLayerId} from './util/format';
class MessagePanel extends React.Component {
static propTypes = {
errors: PropTypes.array,
infos: PropTypes.array,
mapStyle: PropTypes.object,
onLayerSelect: PropTypes.func,
currentLayer: PropTypes.object,
selectedLayerIndex: PropTypes.number,
}
static defaultProps = {
onLayerSelect: () => {},
}
render() {
const {selectedLayerIndex} = this.props;
const errors = this.props.errors.map((error, idx) => {
let content;
if (error.parsed && error.parsed.type === "layer") {
const {parsed} = error;
const {mapStyle, currentLayer} = this.props;
const layerId = mapStyle.layers[parsed.data.index].id;
content = (
<>
Layer <span>{formatLayerId(layerId)}</span>: {parsed.data.message}
{selectedLayerIndex !== parsed.data.index &&
<>
&nbsp;&mdash;&nbsp;
<button
className="maputnik-message-panel__switch-button"
onClick={() => this.props.onLayerSelect(parsed.data.index)}
>
switch to layer
</button>
</>
}
</>
);
}
else {
content = error.message;
}
return <p key={"error-"+idx} className="maputnik-message-panel-error">
{content}
</p>
})
const infos = this.props.infos.map((m, i) => {
return <p key={"info-"+i}>{m}</p>
})
return <div className="maputnik-message-panel">
{errors}
{infos}
</div>
}
}
export default MessagePanel

View File

@@ -1,13 +0,0 @@
import React from 'react'
import colors from '../config/colors'
import { margins, fontSizes } from '../config/scales'
const Paragraph = (props) => <p style={{
color: colors.lowgray,
fontSize: fontSizes[5],
...props.style
}}>
{props.children}
</p>
export default Paragraph

View File

@@ -1,18 +1,16 @@
import React from 'react'
import scrollbars from './scrollbars.scss'
import PropTypes from 'prop-types'
const ScrollContainer = (props) => {
return <div className={scrollbars.darkScrollbar} style={{
overflowX: "visible",
overflowY: "scroll",
bottom:0,
left:0,
right:0,
top:1,
position: "absolute",
}}>
{props.children}
</div>
class ScrollContainer extends React.Component {
static propTypes = {
children: PropTypes.node
}
render() {
return <div className="maputnik-scroll-container">
{this.props.children}
</div>
}
}
export default ScrollContainer

View File

@@ -1,173 +1,236 @@
import React from 'react'
import FileReaderInput from 'react-file-reader-input'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {detect} from 'detect-browser';
import MdFileDownload from 'react-icons/lib/md/file-download'
import MdFileUpload from 'react-icons/lib/md/file-upload'
import MdOpenInBrowser from 'react-icons/lib/md/open-in-browser'
import MdSettings from 'react-icons/lib/md/settings'
import MdInfo from 'react-icons/lib/md/info'
import MdLayers 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 MdHelpOutline from 'react-icons/lib/md/help-outline'
import MdFindInPage from 'react-icons/lib/md/find-in-page'
import {MdFileDownload, MdOpenInBrowser, MdSettings, MdLayers, MdHelpOutline, MdFindInPage, MdAssignmentTurnedIn} from 'react-icons/md'
import SettingsModal from './modals/SettingsModal'
import SourcesModal from './modals/SourcesModal'
import OpenModal from './modals/OpenModal'
import style from '../libs/style'
import colors from '../config/colors'
import { margins, fontSizes } from '../config/scales'
import logoImage from 'maputnik-design/logos/logo-color.svg'
import pkgJson from '../../package.json'
const IconText = props => <span style={{ paddingLeft: margins[0] }}>
{props.children}
</span>
const actionStyle = {
display: "inline-block",
padding: 12.5,
fontSize: fontSizes[4],
cursor: "pointer",
color: colors.white,
textDecoration: 'none',
}
// 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;
const ToolbarLink = props => <a
href={props.href}
target={"blank"}
style={{
...actionStyle,
...props.style,
}}>
{props.children}
</a>
class ToolbarAction extends React.Component {
class IconText extends React.Component {
static propTypes = {
onClick: React.PropTypes.func.isRequired,
children: PropTypes.node,
}
constructor(props) {
super(props)
this.state = { hover: false }
render() {
return <span className="maputnik-icon-text">{this.props.children}</span>
}
}
class ToolbarLink extends React.Component {
static propTypes = {
className: PropTypes.string,
children: PropTypes.node,
href: PropTypes.string,
onToggleModal: PropTypes.func,
}
render() {
return <a
onClick={this.props.onClick}
onMouseOver={e => this.setState({hover: true})}
onMouseOut={e => this.setState({hover: false})}
style={{
...actionStyle,
...this.props.style,
backgroundColor: this.state.hover ? colors.gray : null,
}}>
className={classnames('maputnik-toolbar-link', this.props.className)}
href={this.props.href}
rel="noopener noreferrer"
target="_blank"
>
{this.props.children}
</a>
}
}
export default class Toolbar extends React.Component {
class ToolbarLinkHighlighted extends React.Component {
static propTypes = {
mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
// A new style has been uploaded
onStyleOpen: React.PropTypes.func.isRequired,
// Current style is requested for download
onStyleDownload: React.PropTypes.func.isRequired,
}
constructor(props) {
super(props)
this.state = {
isOpen: {
settings: false,
sources: false,
open: false,
}
}
}
downloadButton() {
return <ToolbarAction onClick={this.props.onStyleDownload}>
<MdFileDownload />
<IconText>Download</IconText>
</ToolbarAction>
}
toggleModal(modalName) {
this.setState({
isOpen: {
...this.state.isOpen,
[modalName]: !this.state.isOpen[modalName]
}
})
className: PropTypes.string,
children: PropTypes.node,
href: PropTypes.string,
onToggleModal: PropTypes.func
}
render() {
return <div style={{
position: "fixed",
height: 40,
width: '100%',
zIndex: 100,
left: 0,
top: 0,
backgroundColor: colors.black
}}>
<SettingsModal
mapStyle={this.props.mapStyle}
onStyleChanged={this.props.onStyleChanged}
isOpen={this.state.isOpen.settings}
onOpenToggle={this.toggleModal.bind(this, 'settings')}
/>
<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"}
style={{
width: 180,
textAlign: 'left',
backgroundColor: colors.black,
padding: 5,
}}
>
<img src="https://github.com/maputnik/editor/raw/master/media/maputnik.png" alt="Maputnik" style={{width: 30, height: 30, paddingRight: 5, verticalAlign: 'middle'}}/>
<span style={{fontSize: 20, verticalAlign: 'middle' }}>Maputnik</span>
</ToolbarLink>
<ToolbarAction onClick={this.toggleModal.bind(this, 'open')}>
<MdOpenInBrowser />
<IconText>Open</IconText>
</ToolbarAction>
{this.downloadButton()}
<ToolbarAction onClick={this.toggleModal.bind(this, 'sources')}>
<MdLayers />
<IconText>Sources</IconText>
</ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'settings')}>
<MdSettings />
<IconText>Style Settings</IconText>
</ToolbarAction>
<ToolbarAction onClick={this.toggleModal.bind(this, 'settings')}>
<MdFindInPage />
<IconText>Inspect</IconText>
</ToolbarAction>
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
<MdHelpOutline />
<IconText>Help</IconText>
</ToolbarLink>
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'
data-wd-key={this.props.wdKey}
onClick={this.props.onClick}
>
{this.props.children}
</button>
}
}
export default class Toolbar extends React.Component {
static propTypes = {
mapStyle: PropTypes.object.isRequired,
inspectModeEnabled: PropTypes.bool.isRequired,
onStyleChanged: PropTypes.func.isRequired,
// A new style has been uploaded
onStyleOpen: PropTypes.func.isRequired,
// A dict of source id's and the available source layers
sources: PropTypes.object.isRequired,
children: PropTypes.node,
onToggleModal: PropTypes.func,
onSetMapState: PropTypes.func,
mapState: PropTypes.string,
renderer: PropTypes.string,
}
state = {
isOpen: {
settings: false,
sources: false,
open: false,
add: false,
export: false,
}
}
handleSelection(val) {
this.props.onSetMapState(val);
}
render() {
const views = [
{
id: "map",
title: "Map",
},
{
id: "inspect",
title: "Inspect",
disabled: this.props.renderer !== 'mbgljs',
},
{
id: "filter-deuteranopia",
title: "Map (deuteranopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-protanopia",
title: "Map (protanopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-tritanopia",
title: "Map (tritanopia)",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-achromatopsia",
title: "Map (achromatopsia)",
disabled: !colorAccessibilityFiltersEnabled,
},
];
const currentView = views.find((view) => {
return view.id === this.props.mapState;
});
return <div className='maputnik-toolbar'>
<div className="maputnik-toolbar__inner">
<div
className="maputnik-toolbar-logo-container"
>
<a className="maputnik-toolbar-skip" href="#skip-menu">
Skip navigation
</a>
<a
href="https://github.com/maputnik/editor"
rel="noopener noreferrer"
target="_blank"
className="maputnik-toolbar-logo"
>
<span dangerouslySetInnerHTML={{__html: logoImage}} />
<h1>
<span className="maputnik-toolbar-name">{pkgJson.name}</span>
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
</h1>
</a>
</div>
<div className="maputnik-toolbar__actions">
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
<MdOpenInBrowser />
<IconText>Open</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
<MdFileDownload />
<IconText>Export</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
<MdLayers />
<IconText>Data Sources</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
<MdSettings />
<IconText>Style Settings</IconText>
</ToolbarAction>
<ToolbarSelect wdKey="nav:inspect">
<MdFindInPage />
<IconText>View </IconText>
<select onChange={(e) => this.handleSelection(e.target.value)} value={currentView.id}>
{views.map((item) => {
return (
<option key={item.id} value={item.id} disabled={item.disabled}>
{item.title}
</option>
);
})}
</select>
</ToolbarSelect>
<ToolbarLink href={"https://github.com/maputnik/editor/wiki"}>
<MdHelpOutline />
<IconText>Help</IconText>
</ToolbarLink>
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
<MdAssignmentTurnedIn />
<IconText>Take the Maputnik Survey</IconText>
</ToolbarLinkHighlighted>
</div>
</div>
</div>
}
}

View File

@@ -1,75 +0,0 @@
import React from 'react'
import input from '../../config/input'
import colors from '../../config/colors'
import { margins } from '../../config/scales'
class BooleanField extends React.Component {
static propTypes = {
onChange: React.PropTypes.func.isRequired,
name: React.PropTypes.string.isRequired,
value: React.PropTypes.bool,
doc: React.PropTypes.string,
style: React.PropTypes.object,
}
render() {
const styles = {
root: {
...input.base,
padding: 0,
position: 'relative',
textAlign: 'center ',
cursor: 'pointer'
},
input: {
position: 'absolute',
zIndex: -1,
opacity: 0
},
box: {
display: 'inline-block',
textAlign: 'center ',
height: 15,
width: 15,
marginRight: margins[1],
marginBottom: null,
backgroundColor: colors.gray,
borderRadius: 2,
borderStyle: 'solid',
borderWidth: 2,
borderColor: colors.gray,
transition: 'background-color .1s ease-out'
},
icon: {
display: this.props.value ? null : 'none',
width: '75%',
height: '75%',
marginTop: 1,
fill: colors.lowgray
}
}
return <label style={styles.root}>
<input
type="checkbox"
style={{
...styles.input,
...this.props.style,
}}
value={this.props.value}
onChange={e => {this.props.onChange(!this.props.value)}}
checked={this.props.value}
/>
<div style={styles.box}>
<svg
viewBox='0 0 32 32'
style={styles.icon}>
<path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' />
</svg>
</div>
</label>
}
}
export default BooleanField

View File

@@ -1,8 +1,8 @@
import React from 'react'
import Color from 'color'
import ChromePicker from 'react-color/lib/components/chrome/Chrome'
import input from '../../config/input.js'
import PropTypes from 'prop-types'
import lodash from 'lodash';
function formatColor(color) {
const rgb = color.rgb
@@ -12,63 +12,91 @@ function formatColor(color) {
/*** Number fields with support for min, max and units and documentation*/
class ColorField extends React.Component {
static propTypes = {
onChange: React.PropTypes.func.isRequired,
name: React.PropTypes.string.isRequired,
value: React.PropTypes.string,
doc: React.PropTypes.string,
style: React.PropTypes.object,
onChange: PropTypes.func.isRequired,
name: PropTypes.string,
value: PropTypes.string,
doc: PropTypes.string,
style: PropTypes.object,
default: PropTypes.string,
}
constructor(props) {
super(props)
this.state = {
pickerOpened: false,
}
state = {
pickerOpened: false
}
constructor () {
super();
this.onChangeNoCheck = lodash.throttle(this.onChangeNoCheck, 1000/30);
}
onChangeNoCheck (v) {
this.props.onChange(v);
}
//TODO: I much rather would do this with absolute positioning
//but I am too stupid to get it to work together with fixed position
//and scrollbars so I have to fallback to JavaScript
calcPickerOffset() {
const elem = this.refs.colorInput
calcPickerOffset = () => {
const elem = this.colorInput
if(elem) {
const pos = elem.getBoundingClientRect()
return {
top: pos.top,
left: pos.left + 165,
left: pos.left + 196,
}
} else {
console.warn('Color field has no element to adjust position')
return {
top: 160,
left: 500,
left: 555,
}
}
}
togglePicker() {
togglePicker = () => {
this.setState({ pickerOpened: !this.state.pickerOpened })
}
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() {
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
className="maputnik-color-picker-offset"
style={{
position: 'fixed',
zIndex: 1,
position: 'fixed',
zIndex: 1,
left: offset.left,
top: offset.top,
}}>
<ChromePicker
color={this.color.object()}
onChange={c => this.props.onChange(formatColor(c))}
color={currentColor}
onChange={c => this.onChangeNoCheck(formatColor(c))}
/>
<div
onClick={this.togglePicker.bind(this)}
className="maputnik-color-picker-offset"
onClick={this.togglePicker}
style={{
zIndex: -1,
position: 'fixed',
@@ -80,23 +108,23 @@ class ColorField extends React.Component {
/>
</div>
return <div style={{
...input.property,
position: 'relative',
display: 'inline',
}}>
var swatchStyle = {
backgroundColor: this.props.value
};
return <div className="maputnik-color-wrapper">
{this.state.pickerOpened && picker}
<div className="maputnik-color-swatch" style={swatchStyle}></div>
<input
ref="colorInput"
onClick={this.togglePicker.bind(this)}
style={{
...input.select,
...this.props.style
}}
spellCheck="false"
className="maputnik-color"
ref={(input) => this.colorInput = input}
onClick={this.togglePicker}
style={this.props.style}
name={this.props.name}
placeholder={this.props.default}
value={this.props.value ? this.props.value : ""}
onChange={(e) => this.props.onChange(e.target.value)}
onChange={(e) => this.onChange(e.target.value)}
/>
</div>
}

View File

@@ -0,0 +1,63 @@
import React from 'react'
import PropTypes from 'prop-types'
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
export default class DocLabel extends React.Component {
static propTypes = {
label: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]).isRequired,
fieldSpec: PropTypes.object,
onToggleDoc: PropTypes.func,
}
constructor (props) {
super(props);
this.state = {
open: false,
}
}
onToggleDoc = (open) => {
this.setState({
open,
}, () => {
if (this.props.onToggleDoc) {
this.props.onToggleDoc(this.state.open);
}
});
}
render() {
const {label, fieldSpec} = this.props;
const {doc} = fieldSpec || {};
if (doc) {
return <label className="maputnik-doc-wrapper">
<div className="maputnik-doc-target">
{label}
{'\xa0'}
<button
aria-label={this.state.open ? "close property documentation" : "open property documentation"}
className={`maputnik-doc-button maputnik-doc-button--${this.state.open ? 'open' : 'closed'}`}
onClick={() => this.onToggleDoc(!this.state.open)}
>
{this.state.open ? <MdHighlightOff /> : <MdInfoOutline />}
</button>
</div>
</label>
}
else if (label) {
return <label className="maputnik-doc-wrapper">
<div className="maputnik-doc-target">
{label}
</div>
</label>
}
else {
<div />
}
}
}

View File

@@ -1,36 +0,0 @@
import React from 'react'
import input from '../../config/input.js'
class EnumField extends React.Component {
static propTypes = {
onChange: React.PropTypes.func.isRequired,
name: React.PropTypes.string.isRequired,
value: React.PropTypes.string,
allowedValues: React.PropTypes.array.isRequired,
doc: React.PropTypes.string,
style: React.PropTypes.object,
}
onChange(e) {
return this.props.onChange(e.target.value)
}
render() {
const options = this.props.allowedValues.map(val => {
return <option key={val} value={val}>{val}</option>
})
return <select
style={{
...input.select,
...this.props.style
}}
value={this.props.value}
onChange={this.onChange.bind(this)}
>
{options}
</select>
}
}
export default EnumField

View File

@@ -0,0 +1,333 @@
import React from 'react'
import PropTypes from 'prop-types'
import SpecProperty from './_SpecProperty'
import DataProperty from './_DataProperty'
import ZoomProperty from './_ZoomProperty'
import ExpressionProperty from './_ExpressionProperty'
import {function as styleFunction} from '@mapbox/mapbox-gl-style-spec';
function isLiteralExpression (value) {
return (Array.isArray(value) && value.length === 2 && value[0] === "literal");
}
function isZoomField(value) {
return (
typeof(value) === 'object' &&
value.stops &&
typeof(value.property) === 'undefined' &&
Array.isArray(value.stops) &&
value.stops.length > 1 &&
value.stops.every(stop => {
return (
Array.isArray(stop) &&
stop.length === 2
);
})
);
}
function isDataField(value) {
return (
typeof(value) === 'object' &&
value.stops &&
typeof(value.property) !== 'undefined' &&
value.stops.length > 1 &&
Array.isArray(value.stops) &&
value.stops.every(stop => {
return (
Array.isArray(stop) &&
stop.length === 2 &&
typeof(stop[0]) === 'object'
);
})
);
}
function isPrimative (value) {
const valid = ["string", "boolean", "number"];
return valid.includes(typeof(value));
}
function isArrayOfPrimatives (values) {
if (Array.isArray(values)) {
return values.every(isPrimative);
}
return false;
}
function getDataType (value, fieldSpec={}) {
if (value === undefined) {
return "value";
}
else if (isPrimative(value)) {
return "value";
}
else if (fieldSpec.type === "array" && isArrayOfPrimatives(value)) {
return "value";
}
else if (isZoomField(value)) {
return "zoom_function";
}
else if (isDataField(value)) {
return "data_function";
}
else {
return "expression";
}
}
/**
* If we don't have a default value just make one up
*/
function findDefaultFromSpec (spec) {
if (spec.hasOwnProperty('default')) {
return spec.default;
}
const defaults = {
'color': '#000000',
'string': '',
'boolean': false,
'number': 0,
'array': [],
}
return defaults[spec.type] || '';
}
/** Supports displaying spec field for zoom function objects
* 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,
fieldType: PropTypes.string.isRequired,
fieldSpec: PropTypes.object.isRequired,
errors: PropTypes.object,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
}
constructor (props) {
super();
this.state = {
dataType: getDataType(props.value, props.fieldSpec),
isEditing: false,
}
}
static getDerivedStateFromProps(props, state) {
// Because otherwise when editing values we end up accidentally changing field type.
if (state.isEditing) {
return {};
}
else {
return {
isEditing: false,
dataType: getDataType(props.value, props.fieldSpec)
};
}
}
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)
}
deleteExpression = () => {
const {fieldSpec, fieldName} = this.props;
this.props.onChange(fieldName, fieldSpec.default);
this.setState({
dataType: "value",
});
}
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 || findDefaultFromSpec(this.props.fieldSpec)],
[10, this.props.value || findDefaultFromSpec(this.props.fieldSpec)]
]
}
this.props.onChange(this.props.fieldName, zoomFunc)
}
undoExpression = () => {
const {value, fieldName} = this.props;
if (isLiteralExpression(value)) {
this.props.onChange(fieldName, value[1]);
this.setState({
dataType: "value",
});
}
}
canUndo = () => {
const {value, fieldSpec} = this.props;
return (
isLiteralExpression(value) ||
isPrimative(value) ||
(Array.isArray(value) && fieldSpec.type === "array")
);
}
makeExpression = () => {
const {value, fieldSpec} = this.props;
let expression;
if (typeof(value) === "object" && 'stops' in value) {
expression = styleFunction.convertFunction(value, fieldSpec);
}
else {
expression = ["literal", value || this.props.fieldSpec.default];
}
this.props.onChange(this.props.fieldName, expression);
}
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 || findDefaultFromSpec(this.props.fieldSpec)],
[{zoom: 10, value: stopValue}, this.props.value || findDefaultFromSpec(this.props.fieldSpec)]
]
}
this.props.onChange(this.props.fieldName, dataFunc)
}
onMarkEditing = () => {
this.setState({isEditing: true});
}
onUnmarkEditing = () => {
this.setState({isEditing: false});
}
render() {
const {dataType} = this.state;
const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
let specField;
if (dataType === "expression") {
specField = (
<ExpressionProperty
errors={this.props.errors}
onChange={this.props.onChange.bind(this, this.props.fieldName)}
canUndo={this.canUndo}
onUndo={this.undoExpression}
onDelete={this.deleteExpression}
fieldType={this.props.fieldType}
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onFocus={this.onMarkEditing}
onBlur={this.onUnmarkEditing}
/>
);
}
else if (dataType === "zoom_function") {
specField = (
<ZoomProperty
errors={this.props.errors}
onChange={this.props.onChange.bind(this)}
fieldType={this.props.fieldType}
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onDeleteStop={this.deleteStop}
onAddStop={this.addStop}
onExpressionClick={this.makeExpression}
/>
)
}
else if (dataType === "data_function") {
specField = (
<DataProperty
errors={this.props.errors}
onChange={this.props.onChange.bind(this)}
fieldType={this.props.fieldType}
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onDeleteStop={this.deleteStop}
onAddStop={this.addStop}
onExpressionClick={this.makeExpression}
/>
)
}
else {
specField = (
<SpecProperty
errors={this.props.errors}
onChange={this.props.onChange.bind(this)}
fieldType={this.props.fieldType}
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value}
onZoomClick={this.makeZoomFunction}
onDataClick={this.makeDataFunction}
onExpressionClick={this.makeExpression}
/>
)
}
return <div className={propClass} data-wd-key={"spec-field:"+this.props.fieldName}>
{specField}
</div>
}
}

View File

@@ -1,50 +0,0 @@
import React from 'react'
import input from '../../config/input.js'
/*** Number fields with support for min, max and units and documentation*/
class NumberField extends React.Component {
static propTypes = {
onChange: React.PropTypes.func.isRequired,
name: React.PropTypes.string.isRequired,
value: React.PropTypes.number,
default: React.PropTypes.number,
unit: React.PropTypes.string,
min: React.PropTypes.number,
max: React.PropTypes.number,
doc: React.PropTypes.string,
style: React.PropTypes.object,
}
onChange(e) {
const value = parseFloat(e.target.value)
/*TODO: we can do range validation already here?
if(this.props.min && value < this.props.min) return
if(this.props.max && value > this.props.max) return
*/
this.props.onChange(value)
}
render() {
let stepSize = null
if(this.props.max && this.props.min) {
stepSize = (this.props.max - this.props.min) / 10
}
return <input
style={{
...input.input,
...this.props.style
}}
type="number"
min={this.props.min}
max={this.props.max}
step={stepSize}
name={this.props.name}
placeholder={this.props.default}
value={this.props.value}
onChange={this.onChange.bind(this)}
/>
}
}
export default NumberField

View File

@@ -1,20 +1,32 @@
import React from 'react'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js'
import PropTypes from 'prop-types'
import ZoomSpecField from './ZoomSpecField'
import colors from '../../config/colors'
import { margins } from '../../config/scales'
import FunctionSpecField from './FunctionSpecField'
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
/** Extract field spec by {@fieldName} from the {@layerType} in the
* style specification from either the paint or layout group */
function getFieldSpec(layerType, fieldName) {
const groupName = getGroupName(layerType, fieldName)
const group = GlSpec[groupName + '_' + layerType]
return group[fieldName]
function getFieldSpec(spec, layerType, fieldName) {
const groupName = getGroupName(spec, layerType, fieldName)
const group = spec[groupName + '_' + layerType]
const fieldSpec = group[fieldName]
if(iconProperties.indexOf(fieldName) >= 0) {
return {
...fieldSpec,
values: spec.$root.sprite.values
}
}
if(fieldName === 'text-font') {
return {
...fieldSpec,
values: spec.$root.glyphs.values
}
}
return fieldSpec
}
function getGroupName(layerType, fieldName) {
const paint = GlSpec['paint_' + layerType] || {}
function getGroupName(spec, layerType, fieldName) {
const paint = spec['paint_' + layerType] || {}
if (fieldName in paint) {
return 'paint'
} else {
@@ -24,38 +36,40 @@ function getGroupName(layerType, fieldName) {
export default class PropertyGroup extends React.Component {
static propTypes = {
layer: React.PropTypes.object.isRequired,
groupFields: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired,
layer: PropTypes.object.isRequired,
groupFields: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
spec: PropTypes.object.isRequired,
errors: PropTypes.object,
}
onPropertyChange(property, newValue) {
const group = getGroupName(this.props.layer.type, property)
this.props.onChange(group , property ,newValue)
onPropertyChange = (property, newValue) => {
const group = getGroupName(this.props.spec, this.props.layer.type, property)
this.props.onChange(group , property, newValue)
}
render() {
const {errors} = this.props;
const fields = this.props.groupFields.map(fieldName => {
const fieldSpec = getFieldSpec(this.props.layer.type, fieldName)
const fieldSpec = getFieldSpec(this.props.spec, this.props.layer.type, fieldName)
const paint = this.props.layer.paint || {}
const layout = this.props.layer.layout || {}
const fieldValue = paint[fieldName] || layout[fieldName]
const fieldValue = fieldName in paint ? paint[fieldName] : layout[fieldName]
const fieldType = fieldName in paint ? 'paint' : 'layout';
return <ZoomSpecField
onChange={this.onPropertyChange.bind(this)}
return <FunctionSpecField
errors={errors}
onChange={this.onPropertyChange}
key={fieldName}
fieldName={fieldName}
value={fieldValue}
fieldType={fieldType}
fieldSpec={fieldSpec}
/>
})
return <div style={{
padding: margins[2],
paddingRight: 0,
backgroundColor: colors.black,
}}>
return <div className="maputnik-property-group">
{fields}
</div>
}

View File

@@ -1,14 +1,20 @@
import React from 'react'
import color from 'color'
import PropTypes from 'prop-types'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js'
import NumberField from './NumberField'
import EnumField from './EnumField'
import BooleanField from './BooleanField'
import ColorField from './ColorField'
import StringField from './StringField'
import NumberInput from '../inputs/NumberInput'
import CheckboxInput from '../inputs/CheckboxInput'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import MultiButtonInput from '../inputs/MultiButtonInput'
import ArrayInput from '../inputs/ArrayInput'
import DynamicArrayInput from '../inputs/DynamicArrayInput'
import FontInput from '../inputs/FontInput'
import IconInput from '../inputs/IconInput'
import EnumInput from '../inputs/EnumInput'
import capitalize from 'lodash.capitalize'
import input from '../../config/input.js'
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
function labelFromFieldName(fieldName) {
let label = fieldName.split('-').slice(1).join(' ')
@@ -18,63 +24,109 @@ function labelFromFieldName(fieldName) {
return label
}
function optionsLabelLength(options) {
let sum = 0;
options.forEach(([_, label]) => {
sum += label.length
})
return sum
}
/** Display any field from the Mapbox GL style spec and
* choose the correct field component based on the @{fieldSpec}
* to display @{value}. */
export default class SpecField 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.string,
React.PropTypes.number,
onChange: PropTypes.func.isRequired,
fieldName: PropTypes.string.isRequired,
fieldSpec: PropTypes.object.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.array,
PropTypes.bool
]),
/** Override the style of the field */
style: React.PropTypes.object,
style: PropTypes.object,
}
render() {
const commonProps = {
doc: this.props.fieldSpec.doc,
style: this.props.style,
value: this.props.value,
default: this.props.fieldSpec.default,
name: this.props.fieldName,
onChange: newValue => this.props.onChange(this.props.fieldName, newValue)
}
switch(this.props.fieldSpec.type) {
case 'number': return (
<NumberField
{...commonProps}
default={this.props.fieldSpec.default}
min={this.props.fieldSpec.minimum}
max={this.props.fieldSpec.maximum}
unit={this.props.fieldSpec.unit}
/>
)
case 'enum': return (
<EnumField
{...commonProps}
allowedValues={Object.keys(this.props.fieldSpec.values)}
/>
)
case 'string': return (
<StringField
{...commonProps}
/>
)
case 'color': return (
<ColorField
{...commonProps}
/>
)
case 'boolean': return (
<BooleanField
{...commonProps}
/>
)
default: return null
function childNodes() {
switch(this.props.fieldSpec.type) {
case 'number': return (
<NumberInput
{...commonProps}
min={this.props.fieldSpec.minimum}
max={this.props.fieldSpec.maximum}
/>
)
case 'enum':
const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)])
return <EnumInput
{...commonProps}
options={options}
/>
case 'resolvedImage':
case 'formatted':
case 'string':
if(iconProperties.indexOf(this.props.fieldName) >= 0) {
return <IconInput
{...commonProps}
icons={this.props.fieldSpec.values}
/>
} else {
return <StringInput
{...commonProps}
/>
}
case 'color': return (
<ColorField
{...commonProps}
/>
)
case 'boolean': return (
<CheckboxInput
{...commonProps}
/>
)
case 'array':
if(this.props.fieldName === 'text-font') {
return <FontInput
{...commonProps}
fonts={this.props.fieldSpec.values}
/>
} else {
if (this.props.fieldSpec.length) {
return <ArrayInput
{...commonProps}
type={this.props.fieldSpec.value}
length={this.props.fieldSpec.length}
/>
} else {
return <DynamicArrayInput
{...commonProps}
fieldSpec={this.props.fieldSpec}
type={this.props.fieldSpec.value}
/>
}
}
default: return null
}
}
return (
<div data-wd-key={"spec-field:"+this.props.fieldName}>
{childNodes.call(this)}
</div>
);
}
}

View File

@@ -1,34 +0,0 @@
import React from 'react'
import input from '../../config/input.js'
/*** Number fields with support for min, max and units and documentation*/
class StringField extends React.Component {
static propTypes = {
onChange: React.PropTypes.func.isRequired,
name: React.PropTypes.string.isRequired,
value: React.PropTypes.string,
default: React.PropTypes.number,
doc: React.PropTypes.string,
style: React.PropTypes.object,
}
onChange(e) {
const value = e.target.value
return this.props.onChange(value === "" ? null: value)
}
render() {
return <input
style={{
...input.input,
...this.props.style
}}
name={this.props.name}
placeholder={this.props.default}
value={this.props.value ? this.props.value : ""}
onChange={this.onChange.bind(this)}
/>
}
}
export default StringField

View File

@@ -1,97 +0,0 @@
import React from 'react'
import Color from 'color'
import NumberField from './NumberField'
import EnumField from './EnumField'
import BooleanField from './BooleanField'
import ColorField from './ColorField'
import StringField from './StringField'
import SpecField from './SpecField'
import input from '../../config/input.js'
import colors from '../../config/colors.js'
import { margins } from '../../config/scales.js'
function isZoomField(value) {
return typeof value === 'object' && value.stops
}
const specFieldProps = {
onChange: React.PropTypes.func.isRequired,
fieldName: React.PropTypes.string.isRequired,
fieldSpec: React.PropTypes.object.isRequired,
}
/** Supports displaying spec field for zoom function objects
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
*/
export default class ZoomSpecField 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,
]),
}
render() {
const label = <label style={input.label}>
{labelFromFieldName(this.props.fieldName)}
</label>
if(isZoomField(this.props.value)) {
const zoomFields = this.props.value.stops.map(stop => {
const zoomLevel = stop[0]
const value = stop[1]
return <div style={input.property} key={zoomLevel}>
{label}
<SpecField {...this.props}
value={value}
style={{
width: '33%'
}}
/>
<input
style={{
...input.input,
width: '10%',
marginLeft: margins[0],
}}
type="number"
value={zoomLevel}
min={0}
max={22}
/>
</div>
})
return <div style={{
border: 1,
borderStyle: 'solid',
borderColor: Color(colors.gray).lighten(0.1).string(),
padding: margins[1],
}}>
{zoomFields}
</div>
} else {
return <div style={input.property}>
{label}
<SpecField {...this.props} />
</div>
}
}
}
function labelFromFieldName(fieldName) {
let label = fieldName.split('-').slice(1).join(' ')
if(label.length > 0) {
label = label.charAt(0).toUpperCase() + label.slice(1);
}
return label
}

View File

@@ -0,0 +1,281 @@
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 docUid from '../../libs/document-uid'
import sortNumerically from '../../libs/sort-numerically'
import labelFromFieldName from './_labelFromFieldName'
import DeleteStopButton from './_DeleteStopButton'
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 DataProperty extends React.Component {
static propTypes = {
onChange: PropTypes.func,
onDeleteStop: PropTypes.func,
onAddStop: PropTypes.func,
onExpressionClick: PropTypes.func,
fieldName: PropTypes.string,
fieldType: PropTypes.string,
fieldSpec: PropTypes.object,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
errors: PropTypes.object,
}
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;
}
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"]
}
}
// 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].zoom, b.data[0].zoom));
// 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);
}
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 orderedStops = this.orderStopsByZoom(stops);
const changedValue = {
...this.props.value,
stops: orderedStops,
}
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() {
const {fieldName, fieldType, errors} = this.props;
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 key = this.state.refs[idx];
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
const value = stop[1]
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop.bind(this, idx)} />
const dataProps = {
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>
}
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
const foundErrors = Object.entries(errors).filter(([key, error]) => {
return key.startsWith(errorKeyStart);
});
const message = foundErrors.map(([key, error]) => {
return error.message;
}).join("");
const error = message ? {message} : undefined;
return <InputBlock
error={error}
key={key}
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
fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)}
>
<div className="maputnik-data-spec-property-group">
<DocLabel
label="Property"
/>
<div className="maputnik-data-spec-property-input">
<StringInput
value={this.props.value.property}
title={"Input a data property to base styles off of."}
onChange={propVal => this.changeDataProperty("property", propVal)}
/>
</div>
</div>
<div className="maputnik-data-spec-property-group">
<DocLabel
label="Type"
/>
<div className="maputnik-data-spec-property-input">
<SelectInput
value={this.props.value.type}
onChange={propVal => this.changeDataProperty("type", propVal)}
title={"Select a type of data scale (default is 'categorical')."}
options={this.getDataFunctionTypes(this.props.fieldSpec)}
/>
</div>
</div>
<div className="maputnik-data-spec-property-group">
<DocLabel
label="Default"
/>
<div className="maputnik-data-spec-property-input">
<SpecField
fieldName={this.props.fieldName}
fieldSpec={this.props.fieldSpec}
value={this.props.value.default}
onChange={(_, propVal) => this.changeDataProperty("default", propVal)}
/>
</div>
</div>
</InputBlock>
</div>
{dataFields}
<Button
className="maputnik-add-stop"
onClick={this.props.onAddStop.bind(this)}
>
Add stop
</Button>
<Button
className="maputnik-add-stop"
onClick={this.props.onExpressionClick.bind(this)}
>
Convert to expression
</Button>
</div>
}
}

View File

@@ -0,0 +1,22 @@
import React from 'react'
import PropTypes from 'prop-types'
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}
title={"Remove zoom level stop."}
>
<MdDelete />
</Button>
}
}

View File

@@ -0,0 +1,135 @@
import React from 'react'
import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock'
import Button from '../Button'
import {MdDelete, MdUndo} from 'react-icons/md'
import StringInput from '../inputs/StringInput'
import labelFromFieldName from './_labelFromFieldName'
import stringifyPretty from 'json-stringify-pretty-compact'
import JSONEditor from '../layers/JSONEditor'
export default class ExpressionProperty extends React.Component {
static propTypes = {
onDelete: PropTypes.func,
fieldName: PropTypes.string,
fieldType: PropTypes.string,
fieldSpec: PropTypes.object,
value: PropTypes.any,
errors: PropTypes.object,
onChange: PropTypes.func,
onUndo: PropTypes.func,
canUndo: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
}
static defaultProps = {
errors: {},
onFocus: () => {},
onBlur: () => {},
}
constructor (props) {
super();
this.state = {
jsonError: false,
};
}
onJSONInvalid = (err) => {
this.setState({
jsonError: true,
})
}
onJSONValid = () => {
this.setState({
jsonError: false,
})
}
render() {
const {errors, fieldName, fieldType, value, canUndo} = this.props;
const {jsonError} = this.state;
const undoDisabled = canUndo ? !canUndo() : true;
const deleteStopBtn = (
<>
{this.props.onUndo &&
<Button
key="undo_action"
onClick={this.props.onUndo}
disabled={undoDisabled}
className="maputnik-delete-stop"
>
<MdUndo />
</Button>
}
<Button
key="delete_action"
onClick={this.props.onDelete}
className="maputnik-delete-stop"
>
<MdDelete />
</Button>
</>
);
const fieldKey = fieldType === undefined ? fieldName : `${fieldType}.${fieldName}`;
const fieldError = errors[fieldKey];
const errorKeyStart = `${fieldKey}[`;
const foundErrors = [];
function getValue (data) {
return stringifyPretty(data, {indent: 2, maxLength: 38})
}
if (jsonError) {
foundErrors.push({message: "Invalid JSON"});
}
else {
Object.entries(errors)
.filter(([key, error]) => {
return key.startsWith(errorKeyStart);
})
.forEach(([key, error]) => {
return foundErrors.push(error);
})
if (fieldError) {
foundErrors.push(fieldError);
}
}
return <InputBlock
error={foundErrors}
fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)}
action={deleteStopBtn}
wideMode={true}
>
<JSONEditor
mode={{name: "mgl"}}
lint={{
context: "expression",
spec: this.props.fieldSpec,
}}
className="maputnik-expression-editor"
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
onJSONInvalid={this.onJSONInvalid}
onJSONValid={this.onJSONValid}
layer={value}
lineNumbers={false}
maxHeight={200}
lineWrapping={true}
getValue={getValue}
onChange={this.props.onChange}
/>
</InputBlock>
}
}

View File

@@ -0,0 +1,77 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import {MdFunctions, MdInsertChart} from 'react-icons/md'
import {mdiFunctionVariant} from '@mdi/js';
/**
* So here we can't just check is `Array.isArray(value)` because certain
* properties accept arrays as values, for example `text-font`. So we must try
* and create an expression.
*/
function isExpression(value, fieldSpec={}) {
if (!Array.isArray(value)) {
return false;
}
try {
expression.createExpression(value, fieldSpec);
return true;
}
catch (err) {
return false;
}
}
export default class FunctionButtons extends React.Component {
static propTypes = {
fieldSpec: PropTypes.object,
onZoomClick: PropTypes.func,
onDataClick: PropTypes.func,
onExpressionClick: PropTypes.func,
}
render() {
let makeZoomButton, makeDataButton, expressionButton;
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
expressionButton = (
<Button
className="maputnik-make-zoom-function"
onClick={this.props.onExpressionClick}
>
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiFunctionVariant} />
</svg>
</Button>
);
makeZoomButton = <Button
className="maputnik-make-zoom-function"
onClick={this.props.onZoomClick}
title={"Turn property into a zoom function to enable a map feature to change with map's zoom level."}
>
<MdFunctions />
</Button>
if (this.props.fieldSpec['property-type'] === 'data-driven') {
makeDataButton = <Button
className="maputnik-make-data-function"
onClick={this.props.onDataClick}
title={"Turn property into a data function to enable a map feature to change according to data properties and the map's zoom level."}
>
<MdInsertChart />
</Button>
}
return <div>
{expressionButton}
{makeDataButton}
{makeZoomButton}
</div>
}
else {
return <div>{expressionButton}</div>
}
}
}

View File

@@ -0,0 +1,49 @@
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,
fieldType: PropTypes.string,
fieldSpec: PropTypes.object,
value: PropTypes.any,
errors: PropTypes.object,
onExpressionClick: PropTypes.func,
}
static defaultProps = {
errors: {},
}
render() {
const {errors, fieldName, fieldType} = this.props;
const functionBtn = <FunctionButtons
fieldSpec={this.props.fieldSpec}
onZoomClick={this.props.onZoomClick}
onDataClick={this.props.onDataClick}
value={this.props.value}
onExpressionClick={this.props.onExpressionClick}
/>
const error = errors[fieldType+"."+fieldName];
return <InputBlock
error={error}
fieldSpec={this.props.fieldSpec}
label={labelFromFieldName(this.props.fieldName)}
action={functionBtn}
>
<SpecField {...this.props} />
</InputBlock>
}
}

View File

@@ -0,0 +1,189 @@
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,
onExpressionClick: PropTypes.func,
fieldType: PropTypes.string,
fieldName: PropTypes.string,
fieldSpec: PropTypes.object,
errors: PropTypes.object,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.array
]),
}
static defaultProps = {
errors: {},
}
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 {fieldName, fieldType, errors} = this.props;
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)} />
const errorKeyStart = `${fieldType}.${fieldName}.stops[${idx}]`;
const foundErrors = Object.entries(errors).filter(([key, error]) => {
return key.startsWith(errorKeyStart);
});
const message = foundErrors.map(([key, error]) => {
return error.message;
}).join("");
const error = message ? {message} : undefined;
return <InputBlock
error={error}
key={key}
fieldSpec={this.props.fieldSpec}
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>
<Button
className="maputnik-add-stop"
onClick={this.props.onExpressionClick.bind(this)}
>
Convert to expression
</Button>
</div>
}
}

View File

@@ -0,0 +1,13 @@
import capitalize from 'lodash.capitalize'
export default function labelFromFieldName(fieldName) {
let label;
const parts = fieldName.split('-');
if (parts.length > 1) {
label = fieldName.split('-').slice(1).join(' ');
}
else {
label = fieldName;
}
return capitalize(label);
}

View File

@@ -1,180 +1,309 @@
import React from 'react'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js'
import PropTypes from 'prop-types'
import { combiningFilterOps } from '../../libs/filterops.js'
import input from '../../config/input.js'
import colors from '../../config/colors.js'
import { margins } from '../../config/scales.js'
import {latest, validate, migrate} from '@mapbox/mapbox-gl-style-spec'
import DocLabel from '../fields/DocLabel'
import SelectInput from '../inputs/SelectInput'
import InputBlock from '../inputs/InputBlock'
import SingleFilterEditor from './SingleFilterEditor'
import FilterEditorBlock from './FilterEditorBlock'
import Button from '../Button'
import SpecDoc from '../inputs/SpecDoc'
import ExpressionProperty from '../fields/_ExpressionProperty';
import {mdiFunctionVariant} from '@mdi/js';
const combiningFilterOps = ['all', 'any', 'none']
const setFilterOps = ['in', '!in']
const otherFilterOps = Object
.keys(GlSpec.filter_operator.values)
.filter(op => combiningFilterOps.indexOf(op) < 0)
class CombiningOperatorSelect extends React.Component {
static propTypes = {
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
function combiningFilter (props) {
let filter = props.filter || ['all'];
if (!Array.isArray(filter)) {
return filter;
}
render() {
const options = combiningFilterOps.map(op => {
return <option key={op} value={op}>{op}</option>
})
let combiningOp = filter[0];
let filters = filter.slice(1);
return <div>
<select
style={{
...input.select,
width: '20.5%',
margin: margins[0],
}}
value={this.props.value}
onChange={e => this.props.onChange(e.target.value)}
>
{options}
</select>
<label style={{
...input.label,
width: '60%',
marginLeft: margins[0],
}}>
of the filters matches
</label>
</div>
if(combiningFilterOps.indexOf(combiningOp) < 0) {
combiningOp = 'all';
filters = [filter.slice(0)];
}
return [combiningOp, ...filters];
}
class OperatorSelect extends React.Component {
static propTypes = {
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
}
render() {
const options = otherFilterOps.map(op => {
return <option key={op} value={op}>{op}</option>
})
return <select
style={{
...input.select,
width: '15%',
margin: margins[0]
}}
value={this.props.value}
onChange={e => this.props.onChange(e.target.value)}
>
{options}
</select>
}
function migrateFilter (filter) {
return migrate(createStyleFromFilter(filter)).layers[0].filter;
}
class SingleFilterEditor extends React.Component {
static propTypes = {
filter: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired,
properties: React.PropTypes.object,
function createStyleFromFilter (filter) {
return {
"id": "tmp",
"version": 8,
"name": "Empty Style",
"metadata": {"maputnik:renderer": "mbgljs"},
"sources": {
"tmp": {
"type": "geojson",
"data": {}
}
},
"sprite": "",
"glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
"layers": [
{
id: "tmp",
type: "fill",
source: "tmp",
filter: filter,
},
],
};
}
/**
* This is doing way more work than we need it to, however validating a whole
* style if the only thing that's exported from mapbox-gl-style-spec at the
* moment. Not really an issue though as it take ~0.1ms to calculate.
*/
function checkIfSimpleFilter (filter) {
if (!filter || !combiningFilterOps.includes(filter[0])) {
return false;
}
static defaultProps = {
properties: {},
// Because "none" isn't supported by the next expression syntax we can test
// with ["none", ...] because it'll return false if it's a new style
// expression.
const moddedFilter = ["none", ...filter.slice(1)];
const tmpStyle = createStyleFromFilter(moddedFilter)
const errors = validate(tmpStyle);
return (errors.length < 1);
}
function hasCombiningFilter(filter) {
return combiningFilterOps.indexOf(filter[0]) >= 0
}
function hasNestedCombiningFilter(filter) {
if(hasCombiningFilter(filter)) {
const combinedFilters = filter.slice(1)
return filter.slice(1).map(f => hasCombiningFilter(f)).filter(f => f == true).length > 0
}
onFilterPartChanged(filterOp, propertyName, filterArgs) {
const newFilter = [filterOp, propertyName, ...filterArgs]
this.props.onChange(newFilter)
}
render() {
const f = this.props.filter
const filterOp = f[0]
const propertyName = f[1]
const filterArgs = f.slice(2)
return <div>
<select
style={{
...input.select,
width: '17%',
margin: margins[0]
}}
value={propertyName}
onChange={newPropertyName => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)}
>
{Object.keys(this.props.properties).map(propName => {
return <option key={propName} value={propName}>{propName}</option>
})}
</select>
<OperatorSelect
value={filterOp}
onChange={newFilterOp => this.onFilterPartChanged(newFilterOp, propertyName, filterArgs)}
/>
<input
style={{
...input.input,
width: '53%',
margin: margins[0]
}}
value={filterArgs.join(',')}
onChange={e => {
this.onFilterPartChanged(filterOp, propertyName, e.target.value.split(','))}}
/>
</div>
}
return false
}
export default class CombiningFilterEditor extends React.Component {
static propTypes = {
/** Properties of the vector layer and the available fields */
properties: React.PropTypes.object.isRequired,
filter: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired,
properties: PropTypes.object,
filter: PropTypes.array,
errors: PropTypes.object,
onChange: PropTypes.func.isRequired,
}
static defaultProps = {
filter: ["all"],
}
constructor (props) {
super();
this.state = {
showDoc: false,
displaySimpleFilter: checkIfSimpleFilter(combiningFilter(props)),
};
}
// Convert filter to combining filter
combiningFilter() {
let combiningOp = this.props.filter[0]
let filters = this.props.filter.slice(1)
if(combiningFilterOps.indexOf(combiningOp) < 0) {
combiningOp = 'all'
filters = [this.props.filter.slice(0)]
}
return [combiningOp, ...filters]
}
onFilterPartChanged(filterIdx, newPart) {
const newFilter = this.combiningFilter().slice(0)
const newFilter = combiningFilter(this.props).slice(0)
newFilter[filterIdx] = newPart
this.props.onChange(newFilter)
}
render() {
const filter = this.combiningFilter()
let combiningOp = filter[0]
let filters = filter.slice(1)
deleteFilterItem(filterIdx) {
const newFilter = combiningFilter(this.props).slice(0)
newFilter.splice(filterIdx + 1, 1)
this.props.onChange(newFilter)
}
const filterEditors = filters.map((f, idx) => {
return <SingleFilterEditor
key={idx}
properties={this.props.properties}
filter={f}
onChange={this.onFilterPartChanged.bind(this, idx + 1)}
/>
addFilterItem = () => {
const newFilterItem = combiningFilter(this.props).slice(0)
newFilterItem.push(['==', 'name', ''])
this.props.onChange(newFilterItem)
}
onToggleDoc = (val) => {
this.setState({
showDoc: val
});
}
makeFilter = () => {
this.setState({
displaySimpleFilter: true,
})
}
return <div style={{
padding: margins[2],
paddingRight: 0,
backgroundColor: colors.black
}}>
<CombiningOperatorSelect
value={combiningOp}
onChange={this.onFilterPartChanged.bind(this, 0)}
/>
{filterEditors}
</div>
makeExpression = () => {
let filter = combiningFilter(this.props);
this.props.onChange(migrateFilter(filter));
this.setState({
displaySimpleFilter: false,
})
}
static getDerivedStateFromProps (props, currentState) {
const {filter} = props;
const displaySimpleFilter = checkIfSimpleFilter(combiningFilter(props));
// Upgrade but never downgrade
if (!displaySimpleFilter && currentState.displaySimpleFilter === true) {
return {
displaySimpleFilter: false,
valueIsSimpleFilter: false,
};
}
else if (displaySimpleFilter && currentState.displaySimpleFilter === false) {
return {
valueIsSimpleFilter: true,
}
}
else {
return {
valueIsSimpleFilter: false,
};
}
}
render() {
const {errors} = this.props;
const {displaySimpleFilter} = this.state;
const fieldSpec={
doc: latest.layer.filter.doc + " Combine multiple filters together by using a compound filter."
};
const defaultFilter = ["all"];
const isNestedCombiningFilter = displaySimpleFilter && hasNestedCombiningFilter(combiningFilter(this.props));
if (isNestedCombiningFilter) {
return <div className="maputnik-filter-editor-unsupported">
<p>
Nested filters are not supported.
</p>
<Button
onClick={this.makeExpression}
>
<svg style={{marginRight: "0.2em", width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiFunctionVariant} />
</svg>
Upgrade to expression
</Button>
</div>
}
else if (displaySimpleFilter) {
const filter = combiningFilter(this.props);
let combiningOp = filter[0];
let filters = filter.slice(1)
const actions = (
<div>
<Button
onClick={this.makeExpression}
className="maputnik-make-zoom-function"
>
<svg style={{width:"14px", height:"14px", verticalAlign: "middle"}} viewBox="0 0 24 24">
<path fill="currentColor" d={mdiFunctionVariant} />
</svg>
</Button>
</div>
);
const editorBlocks = filters.map((f, idx) => {
const error = errors[`filter[${idx+1}]`];
return (
<div key={`block-${idx}`}>
<FilterEditorBlock key={idx} onDelete={this.deleteFilterItem.bind(this, idx)}>
<SingleFilterEditor
properties={this.props.properties}
filter={f}
onChange={this.onFilterPartChanged.bind(this, idx + 1)}
/>
</FilterEditorBlock>
{error &&
<div key="error" className="maputnik-inline-error">{error.message}</div>
}
</div>
);
})
return (
<>
<InputBlock
key="top"
fieldSpec={fieldSpec}
label={"Filter"}
action={actions}
>
<SelectInput
value={combiningOp}
onChange={this.onFilterPartChanged.bind(this, 0)}
options={[["all", "every filter matches"], ["none", "no filter matches"], ["any", "any filter matches"]]}
/>
</InputBlock>
{editorBlocks}
<div
key="buttons"
className="maputnik-filter-editor-add-wrapper"
>
<Button
data-wd-key="layer-filter-button"
className="maputnik-add-filter"
onClick={this.addFilterItem}>
Add filter
</Button>
</div>
<div
key="doc"
className="maputnik-doc-inline"
style={{display: this.state.showDoc ? '' : 'none'}}
>
<SpecDoc fieldSpec={fieldSpec} />
</div>
</>
);
}
else {
let {filter} = this.props;
return (
<>
<ExpressionProperty
onDelete={() => {
this.setState({displaySimpleFilter: true});
this.props.onChange(defaultFilter);
}}
fieldName="filter"
fieldSpec={fieldSpec}
value={filter}
errors={errors}
onChange={this.props.onChange}
/>
{this.state.valueIsSimpleFilter &&
<div className="maputnik-expr-infobox">
You&apos;ve entered a old style filter,{' '}
<button
onClick={this.makeFilter}
className="maputnik-expr-infobox__button"
>
switch to filter editor
</button>
</div>
}
</>
);
}
}
}

View File

@@ -0,0 +1,29 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import {MdDelete} from 'react-icons/md'
class FilterEditorBlock extends React.Component {
static propTypes = {
onDelete: PropTypes.func.isRequired,
children: PropTypes.element.isRequired,
}
render() {
return <div className="maputnik-filter-editor-block">
<div className="maputnik-filter-editor-block-action">
<Button
className="maputnik-delete-filter"
onClick={this.props.onDelete}
>
<MdDelete />
</Button>
</div>
<div className="maputnik-filter-editor-block-content">
{this.props.children}
</div>
</div>
}
}
export default FilterEditorBlock

View File

@@ -0,0 +1,93 @@
import React from 'react'
import PropTypes from 'prop-types'
import { otherFilterOps } from '../../libs/filterops.js'
import StringInput from '../inputs/StringInput'
import AutocompleteInput from '../inputs/AutocompleteInput'
import SelectInput from '../inputs/SelectInput'
function tryParseInt(v) {
if (v === '') return v
if (isNaN(v)) return 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 {
static propTypes = {
filter: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
properties: PropTypes.object,
}
static defaultProps = {
properties: {},
}
onFilterPartChanged(filterOp, propertyName, filterArgs) {
let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)]
if(filterOp === 'has' || filterOp === '!has') {
newFilter = [filterOp, propertyName]
} else if(filterArgs.length === 0) {
newFilter = [filterOp, propertyName, '']
}
this.props.onChange(newFilter)
}
render() {
const f = this.props.filter
const filterOp = f[0]
const propertyName = f[1]
const filterArgs = f.slice(2)
return <div className="maputnik-filter-editor-single">
<div className="maputnik-filter-editor-property">
<AutocompleteInput
value={propertyName}
options={Object.keys(this.props.properties).map(propName => [propName, propName])}
onChange={newPropertyName => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)}
/>
</div>
<div className="maputnik-filter-editor-operator">
<SelectInput
value={filterOp}
onChange={newFilterOp => this.onFilterPartChanged(newFilterOp, propertyName, filterArgs)}
options={otherFilterOps}
/>
</div>
{filterArgs.length > 0 &&
<div className="maputnik-filter-editor-args">
<StringInput
value={filterArgs.join(',')}
onChange={ v=> this.onFilterPartChanged(filterOp, propertyName, v.split(','))}
/>
</div>
}
</div>
}
}
export default SingleFilterEditor

View File

@@ -0,0 +1,15 @@
import React from 'react'
import IconBase from 'react-icon-base'
export default class FillIcon extends React.Component {
render() {
return (
<IconBase viewBox="0 0 20 20" {...this.props}>
<path transform="translate(2 2)" d="M7.5,0C11.6422,0,15,3.3578,15,7.5S11.6422,15,7.5,15 S0,11.6422,0,7.5S3.3578,0,7.5,0z M7.5,1.6666c-3.2217,0-5.8333,2.6117-5.8333,5.8334S4.2783,13.3334,7.5,13.3334 s5.8333-2.6117,5.8333-5.8334S10.7217,1.6666,7.5,1.6666z"></path>
</IconBase>
)
}
}

View File

@@ -1,24 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'
import LineIcon from './LineIcon.jsx'
import FillIcon from './FillIcon.jsx'
import SymbolIcon from './SymbolIcon.jsx'
import BackgroundIcon from './BackgroundIcon.jsx'
import CircleIcon from './CircleIcon.jsx'
import MissingIcon from './MissingIcon.jsx'
class LayerIcon extends React.Component {
static propTypes = {
type: React.PropTypes.string.isRequired,
style: React.PropTypes.object,
type: PropTypes.string.isRequired,
style: PropTypes.object,
}
render() {
const iconProps = { style: this.props.style }
switch(this.props.type) {
case 'fill-extrusion': return <BackgroundIcon {...iconProps} />
case 'raster': return <FillIcon {...iconProps} />
case 'hillshade': return <FillIcon {...iconProps} />
case 'heatmap': return <FillIcon {...iconProps} />
case 'fill': return <FillIcon {...iconProps} />
case 'background': return <BackgroundIcon {...iconProps} />
case 'line': return <LineIcon {...iconProps} />
case 'symbol': return <SymbolIcon {...iconProps} />
default: return null
case 'circle': return <CircleIcon {...iconProps} />
default: return <MissingIcon {...iconProps} />
}
}
}

View File

@@ -6,7 +6,7 @@ export default class FillIcon extends React.Component {
render() {
return (
<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>
)
}

View File

@@ -0,0 +1,11 @@
import React from 'react'
import {MdPriorityHigh} from 'react-icons/md'
export default class MissingIcon extends React.Component {
render() {
return (
<MdPriorityHigh {...this.props} />
)
}
}

View File

@@ -6,8 +6,8 @@ export default class SymbolIcon extends React.Component {
render() {
return (
<IconBase viewBox="0 0 20 20" {...this.props}>
<g id="svg_1" 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" />
<g transform="matrix(1.2718518,0,0,1.2601269,16.559526,-7.4065264)">
<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>
</IconBase>
)

View File

@@ -0,0 +1,109 @@
import React from 'react'
import PropTypes from 'prop-types'
import StringInput from './StringInput'
import NumberInput from './NumberInput'
class ArrayInput extends React.Component {
static propTypes = {
value: PropTypes.array,
type: PropTypes.string,
length: PropTypes.number,
default: PropTypes.array,
onChange: PropTypes.func,
}
static defaultProps = {
value: [],
default: [],
}
constructor (props) {
super(props);
this.state = {
value: this.props.value.slice(0),
// This is so we can compare changes in getDerivedStateFromProps
initialPropsValue: this.props.value.slice(0),
};
}
static getDerivedStateFromProps(props, state) {
const value = [];
const initialPropsValue = state.initialPropsValue.slice(0);
Array(props.length).fill(null).map((_, i) => {
if (props.value[i] === state.initialPropsValue[i]) {
value[i] = state.value[i];
}
else {
value[i] = state.value[i];
initialPropsValue[i] = state.value[i];
}
})
return {
value,
initialPropsValue,
};
}
isComplete (value) {
return Array(this.props.length).fill(null).every((_, i) => {
const val = value[i]
return !(val === undefined || val === "");
});
}
changeValue(idx, newValue) {
const value = this.state.value.slice(0);
value[idx] = newValue;
this.setState({
value,
}, () => {
if (this.isComplete(value)) {
this.props.onChange(value);
}
else {
// Unset until complete
this.props.onChange(undefined);
}
});
}
render() {
const {value} = this.state;
const containsValues = (
value.length > 0 &&
!value.every(val => {
return (val === "" || val === undefined)
})
);
const inputs = Array(this.props.length).fill(null).map((_, i) => {
if(this.props.type === 'number') {
return <NumberInput
key={i}
default={containsValues ? undefined : this.props.default[i]}
value={value[i]}
required={containsValues ? true : false}
onChange={this.changeValue.bind(this, i)}
/>
} else {
return <StringInput
key={i}
default={containsValues ? undefined : this.props.default[i]}
value={value[i]}
required={containsValues ? true : false}
onChange={this.changeValue.bind(this, i)}
/>
}
})
return <div className="maputnik-array">
{inputs}
</div>
}
}
export default ArrayInput

View File

@@ -0,0 +1,98 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Autocomplete from 'react-autocomplete'
const MAX_HEIGHT = 140;
class AutocompleteInput extends React.Component {
static propTypes = {
value: PropTypes.string,
options: PropTypes.array,
onChange: PropTypes.func,
keepMenuWithinWindowBounds: PropTypes.bool
}
state = {
maxHeight: MAX_HEIGHT
}
static defaultProps = {
onChange: () => {},
options: [],
}
calcMaxHeight() {
if(this.props.keepMenuWithinWindowBounds) {
const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
const limitedMaxHeight = Math.min(maxHeight, MAX_HEIGHT);
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={{
className: "maputnik-autocomplete",
style: null
}}
inputProps={{
className: "maputnik-string",
spellCheck: false
}}
value={this.props.value}
items={this.props.options}
getItemValue={(item) => item[0]}
onSelect={v => this.onChange(v)}
onChange={(e, v) => this.onChange(v)}
shouldItemRender={(item, value="") => {
if (typeof(value) === "string") {
return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
}
}}
renderItem={(item, isHighlighted) => (
<div
key={item[0]}
className={classnames({
"maputnik-autocomplete-menu-item": true,
"maputnik-autocomplete-menu-item-selected": isHighlighted,
})}
>
{item[1]}
</div>
)}
/>
</div>
}
}
export default AutocompleteInput

View File

@@ -0,0 +1,35 @@
import React from 'react'
import PropTypes from 'prop-types'
class CheckboxInput extends React.Component {
static propTypes = {
value: PropTypes.bool,
style: PropTypes.object,
onChange: PropTypes.func,
}
static defaultProps = {
value: false,
}
render() {
return <label className="maputnik-checkbox-wrapper">
<input
className="maputnik-checkbox"
type="checkbox"
style={this.props.style}
onChange={e => this.props.onChange(!this.props.value)}
checked={this.props.value}
/>
<div className="maputnik-checkbox-box">
<svg style={{
display: this.props.value ? 'inline' : 'none'
}} className="maputnik-checkbox-icon" viewBox='0 0 32 32'>
<path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' />
</svg>
</div>
</label>
}
}
export default CheckboxInput

View File

@@ -0,0 +1,126 @@
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'
import EnumInput from '../inputs/SelectInput'
import capitalize from 'lodash.capitalize'
class DynamicArrayInput extends React.Component {
static propTypes = {
value: PropTypes.array,
type: PropTypes.string,
default: PropTypes.array,
onChange: PropTypes.func,
style: PropTypes.object,
fieldSpec: 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 if (this.props.type === 'enum') {
const {fieldSpec} = this.props;
const defaultValue = Object.keys(fieldSpec.values)[0];
values.push(defaultValue);
} 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)} />
let input;
if (this.props.type === 'number') {
input = <NumberInput
value={v}
onChange={this.changeValue.bind(this, i)}
/>
}
else if (this.props.type === 'enum') {
const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)]);
input = <EnumInput
options={options}
value={v}
onChange={this.changeValue.bind(this, i)}
/>
}
else {
input = <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

View File

@@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import SelectInput from '../inputs/SelectInput'
import MultiButtonInput from '../inputs/MultiButtonInput'
function optionsLabelLength(options) {
let sum = 0;
options.forEach(([_, label]) => {
sum += label.length
})
return sum
}
class EnumInput extends React.Component {
static propTypes = {
"data-wd-key": PropTypes.string,
value: PropTypes.string,
style: PropTypes.object,
default: PropTypes.string,
onChange: PropTypes.func,
options: PropTypes.array,
}
render() {
const {options, value, onChange} = this.props;
if(options.length <= 3 && optionsLabelLength(options) <= 20) {
return <MultiButtonInput
options={options}
value={value || this.props.default}
onChange={onChange}
/>
} else {
return <SelectInput
options={options}
value={value || this.props.default}
onChange={onChange}
/>
}
}
}
export default EnumInput

View File

@@ -0,0 +1,56 @@
import React from 'react'
import PropTypes from 'prop-types'
import AutocompleteInput from './AutocompleteInput'
class FontInput extends React.Component {
static propTypes = {
value: PropTypes.array.isRequired,
default: PropTypes.array,
fonts: PropTypes.array,
style: PropTypes.object,
onChange: PropTypes.func.isRequired,
}
static defaultProps = {
fonts: []
}
get values() {
const out = this.props.value || this.props.default.slice(1) || [""];
// Always put a "" in the last field to you can keep adding entries
if (out[out.length-1] !== ""){
return out.concat("");
}
else {
return out;
}
}
changeFont(idx, newValue) {
const changedValues = this.values.slice(0)
changedValues[idx] = newValue
const filteredValues = changedValues
.filter(v => v !== undefined)
.filter(v => v !== "")
this.props.onChange(filteredValues);
}
render() {
const inputs = this.values.map((value, i) => {
return <AutocompleteInput
key={i}
value={value}
options={this.props.fonts.map(f => [f, f])}
onChange={this.changeFont.bind(this, i)}
/>
})
return <div className="maputnik-font">
{inputs}
</div>
}
}
export default FontInput

View File

@@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import AutocompleteInput from './AutocompleteInput'
class IconInput extends React.Component {
static propTypes = {
value: PropTypes.string,
icons: PropTypes.array,
style: PropTypes.object,
onChange: PropTypes.func.isRequired,
}
static defaultProps = {
icons: []
}
render() {
return <AutocompleteInput
value={this.props.value}
options={this.props.icons.map(f => [f, f])}
onChange={this.props.onChange}
wrapperStyle={this.props.style}
/>
}
}
export default IconInput

View File

@@ -1,27 +1,93 @@
import React from 'react'
import input from '../../config/input'
import { margins } from '../../config/scales'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import DocLabel from '../fields/DocLabel'
import SpecDoc from './SpecDoc'
/** Wrap a component with a label */
class InputBlock extends React.Component {
static propTypes = {
label: React.PropTypes.string.isRequired,
children: React.PropTypes.element.isRequired,
"data-wd-key": PropTypes.string,
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]).isRequired,
action: PropTypes.element,
children: PropTypes.node.isRequired,
style: PropTypes.object,
onChange: PropTypes.func,
fieldSpec: PropTypes.object,
wideMode: PropTypes.bool,
error: PropTypes.array,
}
constructor (props) {
super(props);
this.state = {
showDoc: false,
}
}
onChange(e) {
const value = e.target.value
return this.props.onChange(value === "" ? null: value)
return this.props.onChange(value === "" ? undefined : value)
}
onToggleDoc = (val) => {
this.setState({
showDoc: val
});
}
render() {
return <div style={{
display: 'block',
marginTop: margins[2],
marginBottom: margins[2],
}}>
<label style={input.label}>{this.props.label}</label>
{this.props.children}
const errors = [].concat(this.props.error || []);
return <div style={this.props.style}
data-wd-key={this.props["data-wd-key"]}
className={classnames({
"maputnik-input-block": true,
"maputnik-input-block--wide": this.props.wideMode,
"maputnik-action-block": this.props.action
})}
>
{this.props.fieldSpec &&
<div className="maputnik-input-block-label">
<DocLabel
label={this.props.label}
onToggleDoc={this.onToggleDoc}
fieldSpec={this.props.fieldSpec}
/>
</div>
}
{!this.props.fieldSpec &&
<label className="maputnik-input-block-label">
{this.props.label}
</label>
}
{this.props.action &&
<div className="maputnik-input-block-action">
{this.props.action}
</div>
}
<div className="maputnik-input-block-content">
{this.props.children}
</div>
{errors.length > 0 &&
<div className="maputnik-inline-error">
{[].concat(this.props.error).map((error, idx) => {
return <div key={idx}>{error.message}</div>
})}
</div>
}
{this.props.fieldSpec &&
<div
className="maputnik-doc-inline"
style={{display: this.state.showDoc ? '' : 'none'}}
>
<SpecDoc fieldSpec={this.props.fieldSpec} />
</div>
}
</div>
}
}

View File

@@ -0,0 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Button from '../Button'
class MultiButtonInput extends React.Component {
static propTypes = {
value: PropTypes.string.isRequired,
options: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
}
render() {
let options = this.props.options
if(options.length > 0 && !Array.isArray(options[0])) {
options = options.map(v => [v, v])
}
const selectedValue = this.props.value || options[0][0]
const buttons = options.map(([val, label])=> {
return <Button
key={val}
onClick={e => this.props.onChange(val)}
className={classnames({"maputnik-button-selected": val === selectedValue})}
>
{label}
</Button>
})
return <div className="maputnik-multibutton">
{buttons}
</div>
}
}
export default MultiButtonInput

View File

@@ -0,0 +1,233 @@
import React from 'react'
import PropTypes from 'prop-types'
let IDX = 0;
class NumberInput extends React.Component {
static propTypes = {
value: PropTypes.number,
default: PropTypes.number,
min: PropTypes.number,
max: PropTypes.number,
onChange: PropTypes.func,
allowRange: PropTypes.bool,
rangeStep: PropTypes.number,
wdKey: PropTypes.string,
required: PropTypes.bool,
}
static defaultProps = {
rangeStep: 1
}
constructor(props) {
super(props)
this.state = {
uuid: IDX++,
editing: false,
value: props.value,
dirtyValue: props.value,
}
}
static getDerivedStateFromProps(props, state) {
if (!state.editing && props.value !== state.value) {
return {
value: props.value,
dirtyValue: props.value,
};
}
return null;
}
changeValue(newValue) {
const value = (newValue === "" || newValue === undefined) ?
undefined :
parseFloat(newValue);
const hasChanged = this.props.value !== value;
if(this.isValid(value) && hasChanged) {
this.props.onChange(value)
this.setState({
value: newValue,
});
}
else if (!this.isValid(value) && hasChanged) {
this.setState({
value: undefined,
});
}
this.setState({
dirtyValue: newValue === "" ? undefined : newValue,
})
}
isValid(v) {
if (v === undefined) {
return true;
}
const value = parseFloat(v)
if(isNaN(value)) {
return false
}
if(!isNaN(this.props.min) && value < this.props.min) {
return false
}
if(!isNaN(this.props.max) && value > this.props.max) {
return false
}
return true
}
resetValue = () => {
this.setState({editing: false});
// Reset explicitly to default value if value has been cleared
if(this.state.value === "") {
return;
}
// If set value is invalid fall back to the last valid value from props or at last resort the default value
if (!this.isValid(this.state.value)) {
if(this.isValid(this.props.value)) {
this.changeValue(this.props.value)
this.setState({dirtyValue: this.props.value});
} else {
this.changeValue(undefined);
this.setState({dirtyValue: undefined});
}
}
}
onChangeRange = (e) => {
let value = parseFloat(e.target.value, 10);
const step = this.props.rangeStep;
let dirtyValue = value;
if(step) {
// Can't do this with the <input/> range step attribute else we won't be able to set a high precision value via the text input.
const snap = value % step;
// Round up/down to step
if (this._keyboardEvent) {
// If it's keyboard event we might get a low positive/negative value,
// for example we might go from 13 to 13.23, however because we know
// that came from a keyboard event we always want to increase by a
// single step value.
if (value < this.state.dirtyValue) {
value = this.state.value - step;
}
else {
value = this.state.value + step
}
dirtyValue = value;
}
else {
if (snap < step/2) {
value = value - snap;
}
else {
value = value + (step - snap);
};
}
}
this._keyboardEvent = false;
// Clamp between min/max
value = Math.max(this.props.min, Math.min(this.props.max, value));
this.setState({value, dirtyValue});
this.props.onChange(value);
}
render() {
if(
this.props.hasOwnProperty("min") && this.props.hasOwnProperty("max") &&
this.props.min !== undefined && this.props.max !== undefined &&
this.props.allowRange
) {
const value = this.state.editing ? this.state.dirtyValue : this.state.value;
const defaultValue = this.props.default === undefined ? "" : this.props.default;
let inputValue;
if (this.state.editingRange) {
inputValue = this.state.value;
}
else {
inputValue = value;
}
return <div className="maputnik-number-container">
<input
className="maputnik-number-range"
key="range"
type="range"
max={this.props.max}
min={this.props.min}
step="any"
spellCheck="false"
value={value === undefined ? defaultValue : value}
aria-hidden="true"
onChange={this.onChangeRange}
onKeyDown={() => {
this._keyboardEvent = true;
}}
onPointerDown={() => {
this.setState({editing: true, editingRange: true});
}}
onPointerUp={() => {
// Safari doesn't get onBlur event
this.setState({editing: false, editingRange: false});
}}
onBlur={() => {
this.setState({
editing: false,
editingRange: false,
dirtyValue: this.state.value,
});
}}
/>
<input
key="text"
type="text"
spellCheck="false"
className="maputnik-number"
placeholder={this.props.default}
value={inputValue === undefined ? "" : inputValue}
onFocus={e => {
this.setState({editing: true});
}}
onChange={e => {
this.changeValue(e.target.value);
}}
onBlur={e => {
this.setState({editing: false});
this.resetValue()
}}
/>
</div>
}
else {
const value = this.state.editing ? this.state.dirtyValue : this.state.value;
return <input
spellCheck="false"
className="maputnik-number"
placeholder={this.props.default}
value={value === undefined ? "" : value}
onChange={e => this.changeValue(e.target.value)}
onFocus={() => {
this.setState({editing: true});
}}
onBlur={this.resetValue}
required={this.props.required}
/>
}
}
}
export default NumberInput

View File

@@ -1,29 +1,32 @@
import React from 'react'
import input from '../../config/input.js'
import PropTypes from 'prop-types'
class SelectInput extends React.Component {
static propTypes = {
value: React.PropTypes.string.isRequired,
options: React.PropTypes.array.isRequired,
style: React.PropTypes.object,
onChange: React.PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
"data-wd-key": PropTypes.string,
options: PropTypes.array.isRequired,
style: PropTypes.object,
onChange: PropTypes.func.isRequired,
title: PropTypes.string,
}
render() {
const options = this.props.options.map(([val, label])=> {
return <option key={val} value={val}>{label}</option>
})
let options = this.props.options
if(options.length > 0 && !Array.isArray(options[0])) {
options = options.map(v => [v, v])
}
return <select
style={{
...input.select,
...this.props.style
}}
className="maputnik-select"
data-wd-key={this.props["data-wd-key"]}
style={this.props.style}
title={this.props.title}
value={this.props.value}
onChange={e => this.props.onChange(e.target.value)}
>
{options}
{ options.map(([val, label]) => <option key={val} value={val}>{label}</option>) }
</select>
}
}

View File

@@ -0,0 +1,83 @@
import React from 'react'
import PropTypes from 'prop-types'
export default class SpecDoc extends React.Component {
static propTypes = {
fieldSpec: PropTypes.object.isRequired,
}
render () {
const {fieldSpec} = this.props;
const {doc, values} = fieldSpec;
const sdkSupport = fieldSpec['sdk-support'];
const headers = {
js: "JS",
android: "Android",
ios: "iOS",
macos: "macOS",
};
const renderValues = (
!!values &&
// HACK: Currently we merge additional values into the stylespec, so this is required
// See <https://github.com/maputnik/editor/blob/master/src/components/fields/PropertyGroup.jsx#L16>
!Array.isArray(values)
);
return (
<>
{doc &&
<div className="SpecDoc">
<div className="SpecDoc__doc">{doc}</div>
{renderValues &&
<ul className="SpecDoc__values">
{Object.entries(values).map(([key, value]) => {
return (
<li key={key}>
<code>{JSON.stringify(key)}</code>
<div>{value.doc}</div>
</li>
);
})}
</ul>
}
</div>
}
{sdkSupport &&
<div className="SpecDoc__sdk-support">
<table className="SpecDoc__sdk-support__table">
<thead>
<tr>
<th></th>
{Object.values(headers).map(header => {
return <th key={header}>{header}</th>;
})}
</tr>
</thead>
<tbody>
{Object.entries(sdkSupport).map(([key, supportObj]) => {
return (
<tr key={key}>
<td>{key}</td>
{Object.keys(headers).map(k => {
const value = supportObj[k];
if (supportObj.hasOwnProperty(k)) {
return <td key={k}>{supportObj[k]}</td>;
}
else {
return <td key={k}>no</td>;
}
})}
</tr>
);
})}
</tbody>
</table>
</div>
}
</>
);
}
}

View File

@@ -1,22 +1,92 @@
import React from 'react'
import input from '../../config/input.js'
import PropTypes from 'prop-types'
class StringInput extends React.Component {
static propTypes = {
value: React.PropTypes.string,
style: React.PropTypes.object,
onChange: React.PropTypes.func,
"data-wd-key": PropTypes.string,
value: PropTypes.string,
style: PropTypes.object,
default: PropTypes.string,
onChange: PropTypes.func,
onInput: PropTypes.func,
multi: PropTypes.bool,
required: PropTypes.bool,
disabled: PropTypes.bool,
spellCheck: PropTypes.bool,
}
static defaultProps = {
onInput: () => {},
}
constructor(props) {
super(props)
this.state = {
editing: false,
value: props.value || ''
}
}
static getDerivedStateFromProps(props, state) {
if (!state.editing) {
return {
value: props.value
};
}
return {};
}
render() {
return <input
style={{
...input.input,
...this.props.style
}}
value={this.props.value}
onChange={e => this.props.onChange(e.target.value)}
/>
let tag;
let classes;
if(!!this.props.multi) {
tag = "textarea"
classes = [
"maputnik-string",
"maputnik-string--multi"
]
}
else {
tag = "input"
classes = [
"maputnik-string"
]
}
if(!!this.props.disabled) {
classes.push("maputnik-string--disabled");
}
return React.createElement(tag, {
"data-wd-key": this.props["data-wd-key"],
spellCheck: this.props.hasOwnProperty("spellCheck") ? this.props.spellCheck : !(tag === "input"),
disabled: this.props.disabled,
className: classes.join(" "),
style: this.props.style,
value: this.state.value === undefined ? "" : this.state.value,
placeholder: this.props.default,
onChange: e => {
this.setState({
editing: true,
value: e.target.value
}, () => {
this.props.onInput(this.state.value);
});
},
onBlur: () => {
if(this.state.value!==this.props.value) {
this.setState({editing: false});
this.props.onChange(this.state.value);
}
},
onKeyDown: (e) => {
if (e.keyCode === 13) {
this.props.onChange(this.state.value);
}
},
required: this.props.required,
});
}
}

View File

@@ -0,0 +1,77 @@
import React from 'react'
import PropTypes from 'prop-types'
import StringInput from './StringInput'
import SmallError from '../util/SmallError'
function validate (url) {
let error;
const getProtocol = (url) => {
try {
const urlObj = new URL(url);
return urlObj.protocol;
}
catch (err) {
return undefined;
}
};
const protocol = getProtocol(url);
if (
protocol &&
protocol === "http:" &&
window.location.protocol === "https:"
) {
error = (
<SmallError>
CORS policy won&apos;t allow fetching resources served over http from https, use a <code>https://</code> domain
</SmallError>
);
}
return error;
}
class UrlInput extends React.Component {
static propTypes = {
"data-wd-key": PropTypes.string,
value: PropTypes.string,
style: PropTypes.object,
default: PropTypes.string,
onChange: PropTypes.func,
onInput: PropTypes.func,
multi: PropTypes.bool,
required: PropTypes.bool,
}
static defaultProps = {
onInput: () => {},
}
constructor (props) {
super(props);
this.state = {
error: validate(props.value)
};
}
onInput = (url) => {
this.setState({
error: validate(url)
});
this.props.onInput(url);
}
render () {
return (
<div>
<StringInput
{...this.props}
onInput={this.onInput}
/>
{this.state.error}
</div>
);
}
}
export default UrlInput

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

View File

@@ -0,0 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md'
export default class Collapser extends React.Component {
static propTypes = {
isCollapsed: PropTypes.bool.isRequired,
style: PropTypes.object,
}
render() {
const iconStyle = {
width: 20,
height: 20,
...this.props.style,
}
return this.props.isCollapsed ? <MdArrowDropUp style={iconStyle}/> : <MdArrowDropDown style={iconStyle} />
}
}

View File

@@ -0,0 +1,33 @@
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() {
const fieldSpec = {
doc: "Comments for the current layer. This is non-standard and not in the spec."
};
return <InputBlock
label={"Comments"}
fieldSpec={fieldSpec}
data-wd-key="layer-comment"
>
<StringInput
multi={true}
value={this.props.value}
onChange={this.props.onChange}
default="Comment..."
/>
</InputBlock>
}
}
export default MetadataBlock

View File

@@ -1,62 +1,159 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames';
import CodeMirror from 'react-codemirror'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import colors from '../../config/colors'
import { margins } from '../../config/scales'
import CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint'
import 'codemirror/addon/edit/matchbrackets'
import 'codemirror/lib/codemirror.css'
import '../../codemirror-maputnik.css'
import 'codemirror/addon/lint/lint.css'
import jsonlint from 'jsonlint'
import stringifyPretty from 'json-stringify-pretty-compact'
import '../util/codemirror-mgl';
class JSONEditor extends React.Component {
static propTypes = {
layer: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func,
layer: PropTypes.any.isRequired,
maxHeight: PropTypes.number,
onChange: PropTypes.func,
lineNumbers: PropTypes.bool,
lineWrapping: PropTypes.bool,
getValue: PropTypes.func,
gutters: PropTypes.array,
className: PropTypes.string,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onJSONValid: PropTypes.func,
onJSONInvalid: PropTypes.func,
mode: PropTypes.object,
lint: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.object,
]),
}
static defaultProps = {
lineNumbers: true,
lineWrapping: false,
gutters: ["CodeMirror-lint-markers"],
getValue: (data) => {
return stringifyPretty(data, {indent: 2, maxLength: 40});
},
onFocus: () => {},
onBlur: () => {},
onJSONInvalid: () => {},
onJSONValid: () => {},
}
constructor(props) {
super(props)
this.state = {
code: JSON.stringify(props.layer, null, 2)
}
isEditing: false,
prevValue: this.props.getValue(this.props.layer),
};
}
componentWillReceiveProps(nextProps) {
this.setState({
code: JSON.stringify(nextProps.layer, null, 2)
})
}
onCodeUpdate(newCode) {
try {
const parsedLayer = JSON.parse(newCode)
this.props.onChange(parsedLayer)
} catch(err) {
console.warn(err)
this.setState({
code: newCode
})
}
}
render() {
const codeMirrorOptions = {
mode: {name: "javascript", json: true},
componentDidMount () {
this._doc = CodeMirror(this._el, {
value: this.props.getValue(this.props.layer),
mode: this.props.mode || {
name: "mgl",
},
lineWrapping: this.props.lineWrapping,
tabSize: 2,
theme: 'maputnik',
viewportMargin: Infinity,
lineNumbers: false,
lineNumbers: this.props.lineNumbers,
lint: this.props.lint || {
context: "layer"
},
matchBrackets: true,
gutters: this.props.gutters,
scrollbarStyle: "null",
});
this._doc.on('change', this.onChange);
this._doc.on('focus', this.onFocus);
this._doc.on('blur', this.onBlur);
}
onFocus = () => {
this.props.onFocus();
this.setState({
isEditing: true
});
}
onBlur = () => {
this.props.onBlur();
this.setState({
isEditing: false
});
}
componentWillUnMount () {
this._doc.off('change', this.onChange);
this._doc.off('focus', this.onFocus);
this._doc.off('blur', this.onBlur);
}
componentDidUpdate(prevProps) {
if (!this.state.isEditing && prevProps.layer !== this.props.layer) {
this._cancelNextChange = true;
this._doc.setValue(
this.props.getValue(this.props.layer),
)
}
}
onChange = (e) => {
if (this._cancelNextChange) {
this._cancelNextChange = false;
this.setState({
prevValue: this._doc.getValue(),
})
return;
}
const newCode = this._doc.getValue();
if (this.state.prevValue !== newCode) {
let parsedLayer, err;
try {
parsedLayer = JSON.parse(newCode);
} catch(_err) {
err = _err;
console.warn(_err)
}
if (err) {
this.props.onJSONInvalid();
}
else {
this.props.onChange(parsedLayer)
this.props.onJSONValid();
}
}
return <CodeMirror
value={this.state.code}
onChange={this.onCodeUpdate.bind(this)}
options={codeMirrorOptions}
this.setState({
prevValue: newCode,
});
}
render() {
const style = {};
if (this.props.maxHeight) {
style.maxHeight = this.props.maxHeight;
}
return <div
className={classnames("codemirror-container", this.props.className)}
ref={(el) => this._el = el}
style={style}
/>
}
}

View File

@@ -1,30 +1,66 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
import JSONEditor from './JSONEditor'
import SourceEditor from './SourceEditor'
import FilterEditor from '../filter/FilterEditor'
import PropertyGroup from '../fields/PropertyGroup'
import LayerEditorGroup from './LayerEditorGroup'
import LayerSettings from './LayerSettings'
import LayerTypeBlock from './LayerTypeBlock'
import LayerIdBlock from './LayerIdBlock'
import MinZoomBlock from './MinZoomBlock'
import MaxZoomBlock from './MaxZoomBlock'
import CommentBlock from './CommentBlock'
import LayerSourceBlock from './LayerSourceBlock'
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
import {Accordion} from 'react-accessible-accordion';
import {MdMoreVert} from 'react-icons/md'
import { changeType, changeProperty } from '../../libs/layer'
import layout from '../../config/layout.json'
import { margins, fontSizes } from '../../config/scales'
import colors from '../../config/colors'
import {formatLayerId} from '../util/format';
class UnsupportedLayer extends React.Component {
render() {
return <div></div>
function getLayoutForType (type) {
return layout[type] ? layout[type] : layout.invalid;
}
function layoutGroups(layerType) {
const layerGroup = {
title: 'Layer',
type: 'layer'
}
const filterGroup = {
title: 'Filter',
type: 'filter'
}
const editorGroup = {
title: 'JSON Editor',
type: 'jsoneditor'
}
return [layerGroup, filterGroup]
.concat(getLayoutForType(layerType).groups)
.concat([editorGroup])
}
/** Layer editor supporting multiple types of layers. */
export default class LayerEditor extends React.Component {
static propTypes = {
layer: React.PropTypes.object.isRequired,
sources: React.PropTypes.object,
vectorLayers: React.PropTypes.object,
onLayerChanged: React.PropTypes.func,
onLayerIdChange: React.PropTypes.func,
layer: PropTypes.object.isRequired,
sources: PropTypes.object,
vectorLayers: PropTypes.object,
spec: PropTypes.object.isRequired,
onLayerChanged: 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,
errors: PropTypes.array,
}
static defaultProps = {
@@ -34,7 +70,7 @@ export default class LayerEditor extends React.Component {
}
static childContextTypes = {
reactIconBase: React.PropTypes.object
reactIconBase: PropTypes.object
}
constructor(props) {
@@ -42,62 +78,41 @@ export default class LayerEditor extends React.Component {
//TODO: Clean this up and refactor into function
const editorGroups = {}
layout[this.props.layer.type].groups.forEach(group => {
layoutGroups(this.props.layer.type).forEach(group => {
editorGroups[group.title] = true
})
this.state = { editorGroups }
}
componentWillReceiveProps(nextProps) {
const additionalGroups = { ...this.state.editorGroups }
static getDerivedStateFromProps(props, state) {
const additionalGroups = { ...state.editorGroups }
layout[nextProps.layer.type].groups.forEach(group => {
getLayoutForType(props.layer.type).groups.forEach(group => {
if(!(group.title in additionalGroups)) {
additionalGroups[group.title] = true
}
})
this.setState({
return {
editorGroups: additionalGroups
})
};
}
getChildContext () {
return {
reactIconBase: {
size: fontSizes[4],
color: colors.lowgray,
size: 14,
color: '#8e8e8e',
}
}
}
/** A {@property} in either the paint our layout {@group} has changed
* to a {@newValue}.
*/
onPropertyChange(group, property, newValue) {
if(group) {
this.props.onLayerChanged({
...this.props.layer,
[group]: {
...this.props.layer[group],
[property]: newValue
}
})
} else {
this.props.onLayerChanged({
...this.props.layer,
[property]: newValue
})
}
}
onFilterChange(newValue) {
const changedLayer = {
...this.props.layer,
filter: newValue
}
this.props.onLayerChanged(changedLayer)
changeProperty(group, property, newValue) {
this.props.onLayerChanged(
this.props.layerIndex,
changeProperty(this.props.layer, group, property, newValue)
)
}
onGroupToggle(groupTitle, active) {
@@ -111,46 +126,125 @@ export default class LayerEditor extends React.Component {
}
renderGroupType(type, fields) {
let comment = ""
if(this.props.layer.metadata) {
comment = this.props.layer.metadata['maputnik:comment']
}
const {errors, layerIndex} = this.props;
const errorData = {};
errors.forEach(error => {
if (
error.parsed &&
error.parsed.type === "layer" &&
error.parsed.data.index == layerIndex
) {
errorData[error.parsed.data.key] = {
message: error.parsed.data.message
};
}
})
let sourceLayerIds;
if(this.props.sources.hasOwnProperty(this.props.layer.source)) {
sourceLayerIds = this.props.sources[this.props.layer.source].layers;
}
switch(type) {
case 'settings': return <LayerSettings
id={this.props.layer.id}
type={this.props.layer.type}
onTypeChange={v => this.onPropertyChange(null, 'type', v)}
onIdChange={newId => this.props.onLayerIdChange(this.props.layer.id, newId)}
/>
case 'source': return <div>
<FilterEditor
filter={this.props.layer.filter}
properties={this.props.vectorLayers[this.props.layer['source-layer']]}
onChange={f => this.onFilterChange(f)}
case 'layer': return <div>
<LayerIdBlock
value={this.props.layer.id}
wdKey="layer-editor.layer-id"
error={errorData.id}
onChange={newId => this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)}
/>
<SourceEditor
source={this.props.layer.source}
sourceLayer={this.props.layer['source-layer']}
sources={this.props.sources}
onSourceChange={console.log}
onSourceLayerChange={console.log}
<LayerTypeBlock
disabled={true}
error={errorData.type}
value={this.props.layer.type}
onChange={newType => this.props.onLayerChanged(
this.props.layerIndex,
changeType(this.props.layer, newType)
)}
/>
{this.props.layer.type !== 'background' && <LayerSourceBlock
error={errorData.sources}
sourceIds={Object.keys(this.props.sources)}
value={this.props.layer.source}
onChange={v => this.changeProperty(null, 'source', v)}
/>
}
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 &&
<LayerSourceLayerBlock
error={errorData['source-layer']}
sourceLayerIds={sourceLayerIds}
value={this.props.layer['source-layer']}
onChange={v => this.changeProperty(null, 'source-layer', v)}
/>
}
<MinZoomBlock
error={errorData.minzoom}
value={this.props.layer.minzoom}
onChange={v => this.changeProperty(null, 'minzoom', v)}
/>
<MaxZoomBlock
error={errorData.maxzoom}
value={this.props.layer.maxzoom}
onChange={v => this.changeProperty(null, 'maxzoom', v)}
/>
<CommentBlock
error={errorData.comment}
value={comment}
onChange={v => this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)}
/>
</div>
case 'filter': return <div>
<div className="maputnik-filter-editor-wrapper">
<FilterEditor
errors={errorData}
filter={this.props.layer.filter}
properties={this.props.vectorLayers[this.props.layer['source-layer']]}
onChange={f => this.changeProperty(null, 'filter', f)}
/>
</div>
</div>
case 'properties': return <PropertyGroup
errors={errorData}
layer={this.props.layer}
groupFields={fields}
onChange={this.onPropertyChange.bind(this)}
spec={this.props.spec}
onChange={this.changeProperty.bind(this)}
/>
case 'jsoneditor': return <JSONEditor
layer={this.props.layer}
onChange={this.props.onLayerChanged}
onChange={(layer) => {
this.props.onLayerChanged(
this.props.layerIndex,
layer
);
}}
/>
default: return null
}
}
moveLayer(offset) {
this.props.onMoveLayer({
oldIndex: this.props.layerIndex,
newIndex: this.props.layerIndex+offset
})
}
render() {
const groupIds = [];
const layerType = this.props.layer.type
const layoutGroups = layout[layerType].groups.filter(group => {
return !(this.props.layer.type === 'background' && group.type === 'source')
const groups = layoutGroups(layerType).filter(group => {
return !(layerType === 'background' && group.type === 'source')
}).map(group => {
const groupId = group.title.replace(/ /g, "_");
groupIds.push(groupId);
return <LayerEditorGroup
data-wd-key={group.title}
id={groupId}
key={group.title}
title={group.title}
isActive={this.state.editorGroups[group.title]}
@@ -160,8 +254,80 @@ export default class LayerEditor extends React.Component {
</LayerEditorGroup>
})
return <div>
{layoutGroups}
const layout = this.props.layer.layout || {}
const items = {
delete: {
text: "Delete",
handler: () => this.props.onLayerDestroy(this.props.layerIndex)
},
duplicate: {
text: "Duplicate",
handler: () => this.props.onLayerCopy(this.props.layerIndex)
},
hide: {
text: (layout.visibility === "none") ? "Show" : "Hide",
handler: () => this.props.onLayerVisibilityToggle(this.props.layerIndex)
},
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"
>
<header>
<div className="layer-header">
<h2 className="layer-header__title">
Layer: {formatLayerId(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>
<Accordion
allowMultipleExpanded={true}
allowZeroExpanded={true}
preExpanded={groupIds}
>
{groups}
</Accordion>
</div>
}
}

View File

@@ -1,63 +1,51 @@
import React from 'react'
import Color from 'color'
import colors from '../../config/colors'
import { margins, fontSizes } from '../../config/scales'
import PropTypes from 'prop-types'
import Icon from '@mdi/react'
import {
mdiMenuDown,
mdiMenuUp
} from '@mdi/js';
import {
AccordionItem,
AccordionItemHeading,
AccordionItemButton,
AccordionItemPanel,
} from 'react-accessible-accordion';
import Collapse from 'react-collapse'
import CollapseOpenIcon from 'react-icons/lib/md/arrow-drop-down'
import CollapseCloseIcon from 'react-icons/lib/md/arrow-drop-up'
class Collapser extends React.Component {
static propTypes = {
isCollapsed: React.PropTypes.bool.isRequired,
}
render() {
const iconStyle = {
width: 20,
height: 20,
}
return this.props.isCollapsed ? <CollapseCloseIcon style={iconStyle}/> : <CollapseOpenIcon style={iconStyle} />
}
}
export default class LayerEditorGroup extends React.Component {
static propTypes = {
title: React.PropTypes.string.isRequired,
isActive: React.PropTypes.bool.isRequired,
children: React.PropTypes.element.isRequired,
onActiveToggle: React.PropTypes.func.isRequired
}
constructor(props) {
super(props)
this.state = { hover: false }
"id": PropTypes.string,
"data-wd-key": PropTypes.string,
title: PropTypes.string.isRequired,
isActive: PropTypes.bool.isRequired,
children: PropTypes.element.isRequired,
onActiveToggle: PropTypes.func.isRequired
}
render() {
return <div>
<div style={{
fontSize: fontSizes[4],
backgroundColor: this.state.hover ? Color(colors.black).lighten(0.30).string() : Color(colors.black).lighten(0.15).string(),
color: colors.lowgray,
cursor: 'pointer',
userSelect: 'none',
padding: margins[1],
display: 'flex',
flexDirection: 'row',
lineHeight: '20px',
}}
onMouseOver={e => this.setState({hover: true})}
onMouseOut={e => this.setState({hover: false})}
return <AccordionItem uuid={this.props.id}>
<AccordionItemHeading className="maputnik-layer-editor-group"
data-wd-key={"layer-editor-group:"+this.props["data-wd-key"]}
onClick={e => this.props.onActiveToggle(!this.props.isActive)}
>
<span>{this.props.title}</span>
<span style={{flexGrow: 1}} />
<Collapser isCollapsed={this.props.isActive} />
</div>
<Collapse isOpened={this.props.isActive}>
<AccordionItemButton className="maputnik-layer-editor-group__button">
<span style={{flexGrow: 1}}>{this.props.title}</span>
<Icon
path={mdiMenuUp}
size={1}
className="maputnik-layer-editor-group__button__icon maputnik-layer-editor-group__button__icon--up"
/>
<Icon
path={mdiMenuDown}
size={1}
className="maputnik-layer-editor-group__button__icon maputnik-layer-editor-group__button__icon--down"
/>
</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>
{this.props.children}
</Collapse>
</div>
</AccordionItemPanel>
</AccordionItem>
}
}

View File

@@ -0,0 +1,29 @@
import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
class LayerIdBlock extends React.Component {
static propTypes = {
value: PropTypes.string.isRequired,
wdKey: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
error: PropTypes.object,
}
render() {
return <InputBlock label={"ID"} fieldSpec={latest.layer.id}
data-wd-key={this.props.wdKey}
error={this.props.error}
>
<StringInput
value={this.props.value}
onChange={this.props.onChange}
/>
</InputBlock>
}
}
export default LayerIdBlock

View File

@@ -1,98 +1,305 @@
import React from 'react'
import cloneDeep from 'lodash.clonedeep'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import lodash from 'lodash';
import LayerListGroup from './LayerListGroup'
import LayerListItem from './LayerListItem'
import AddModal from '../modals/AddModal'
import style from '../../libs/style.js'
import { margins } from '../../config/scales.js'
import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
import {SortableContainer} from 'react-sortable-hoc';
const layerListPropTypes = {
layers: React.PropTypes.array.isRequired,
selectedLayerIndex: React.PropTypes.number.isRequired,
onLayersChange: React.PropTypes.func.isRequired,
onLayerSelect: React.PropTypes.func,
layers: PropTypes.array.isRequired,
selectedLayerIndex: PropTypes.number.isRequired,
onLayersChange: PropTypes.func.isRequired,
onLayerSelect: PropTypes.func,
sources: PropTypes.object.isRequired,
}
function layerPrefix(name) {
return name.replace(' ', '-').replace('_', '-').split('-')[0]
}
function findClosestCommonPrefix(layers, idx) {
const currentLayerPrefix = layerPrefix(layers[idx].id)
let closestIdx = idx
for (let i = idx; i > 0; i--) {
const previousLayerPrefix = layerPrefix(layers[i-1].id)
if(previousLayerPrefix === currentLayerPrefix) {
closestIdx = i - 1
} else {
return closestIdx
}
}
return closestIdx
}
// List of collapsible layer editors
@SortableContainer
class LayerListContainer extends React.Component {
static propTypes = {...layerListPropTypes}
static defaultProps = {
onLayerSelect: () => {},
}
onLayerDestroy(layerId) {
const remainingLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(remainingLayers, layerId)
remainingLayers.splice(idx, 1);
this.props.onLayersChange(remainingLayers)
constructor(props) {
super(props);
this.selectedItemRef = React.createRef();
this.scrollContainerRef = React.createRef();
this.state = {
collapsedGroups: {},
areAllGroupsExpanded: false,
isOpen: {
add: false,
}
}
}
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)
toggleModal(modalName) {
this.setState({
isOpen: {
...this.state.isOpen,
[modalName]: !this.state.isOpen[modalName]
}
})
}
onLayerVisibilityToggle(layerId) {
const changedLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
toggleLayers = () => {
let idx=0
const layer = { ...changedLayers[idx] }
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
let newGroups=[]
layer.layout = changedLayout
changedLayers[idx] = layer
this.props.onLayersChange(changedLayers)
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() {
const groups = []
for (let i = 0; i < this.props.layers.length; i++) {
const previousLayer = this.props.layers[i-1]
const layer = this.props.layers[i]
if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
const lastGroup = groups[groups.length - 1]
lastGroup.push(layer)
} else {
groups.push([layer])
}
}
return groups
}
toggleLayerGroup(groupPrefix, idx) {
const lookupKey = [groupPrefix, idx].join('-')
const newGroups = { ...this.state.collapsedGroups }
if(lookupKey in this.state.collapsedGroups) {
newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey]
} else {
newGroups[lookupKey] = false
}
this.setState({
collapsedGroups: newGroups
})
}
isCollapsed(groupPrefix, idx) {
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')]
return collapsed === undefined ? true : collapsed
}
shouldComponentUpdate (nextProps, nextState) {
// Always update on state change
if (this.state !== nextState) {
return true;
}
// This component tree only requires id and visibility from the layers
// objects
function getRequiredProps (layer) {
const out = {
id: layer.id,
};
if (layer.layout) {
out.layout = {
visibility: layer.layout.visibility
};
}
return out;
}
const layersEqual = lodash.isEqual(
nextProps.layers.map(getRequiredProps),
this.props.layers.map(getRequiredProps),
);
function withoutLayers (props) {
const out = {
...props
};
delete out['layers'];
return out;
}
// Compare the props without layers because we've already compared them
// efficiently above.
const propsEqual = lodash.isEqual(
withoutLayers(this.props),
withoutLayers(nextProps)
);
const propsChanged = !(layersEqual && propsEqual);
return propsChanged;
}
componentDidUpdate (prevProps) {
if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) {
const selectedItemNode = this.selectedItemRef.current;
if (selectedItemNode && selectedItemNode.node) {
const target = selectedItemNode.node;
const options = {
root: this.scrollContainerRef.current,
threshold: 1.0
}
const observer = new IntersectionObserver(entries => {
observer.unobserve(target);
if (entries.length > 0 && entries[0].intersectionRatio < 1) {
target.scrollIntoView();
}
}, options);
observer.observe(target);
}
}
}
render() {
const layerPanels = this.props.layers.map((layer, index) => {
const layerId = layer.id
return <LayerListItem
index={index}
key={layerId}
layerId={layerId}
layerType={layer.type}
visibility={(layer.layout || {}).visibility}
isSelected={index === this.props.selectedLayerIndex}
onLayerSelect={this.props.onLayerSelect}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
/>
const listItems = []
let idx = 0
const layerIdCount = new Map();
const layersByGroup = this.groupedLayers();
layersByGroup.forEach(layers => {
const groupPrefix = layerPrefix(layers[0].id)
if(layers.length > 1) {
const grp = <LayerListGroup
data-wd-key={[groupPrefix, idx].join('-')}
key={`group-${groupPrefix}-${idx}`}
title={groupPrefix}
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
/>
listItems.push(grp)
}
layers.forEach((layer, idxInGroup) => {
const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
const layerError = this.props.errors.find(error => {
return (
error.parsed &&
error.parsed.type === "layer" &&
error.parsed.data.index == idx
);
});
const additionalProps = {};
if (idx === this.props.selectedLayerIndex) {
additionalProps.ref = this.selectedItemRef;
}
layerIdCount.set(layer.id,
layerIdCount.has(layer.id) ? layerIdCount.get(layer.id) + 1 : 0
);
const key = `${layer.id}-${layerIdCount.get(layer.id)}`;
const listItem = <LayerListItem
className={classnames({
'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--error': !!layerError
})}
index={idx}
key={key}
layerId={layer.id}
layerIndex={idx}
layerType={layer.type}
visibility={(layer.layout || {}).visibility}
isSelected={idx === this.props.selectedLayerIndex}
onLayerSelect={this.props.onLayerSelect}
onLayerDestroy={this.props.onLayerDestroy.bind(this)}
onLayerCopy={this.props.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
{...additionalProps}
/>
listItems.push(listItem)
idx += 1
})
})
return <ul style={{
padding: 0,
margin: 0
}}>
{layerPanels}
</ul>
return <div className="maputnik-layer-list" ref={this.scrollContainerRef}>
<AddModal
layers={this.props.layers}
sources={this.props.sources}
isOpen={this.state.isOpen.add}
onOpenToggle={this.toggleModal.bind(this, 'add')}
onLayersChange={this.props.onLayersChange}
/>
<header className="maputnik-layer-list-header">
<span className="maputnik-layer-list-header-title">Layers</span>
<span className="maputnik-space" />
<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')}
data-wd-key="layer-list:add-layer"
className="maputnik-button maputnik-button-selected">
Add Layer
</button>
</div>
</div>
</header>
<ul className="maputnik-layer-list-container">
{listItems}
</ul>
</div>
}
}
const LayerListContainerSortable = SortableContainer((props) => <LayerListContainer {...props} />)
export default class LayerList extends React.Component {
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() {
return <LayerListContainer
return <LayerListContainerSortable
{...this.props}
onSortEnd={this.onSortEnd.bind(this)}
helperClass='sortableHelper'
onSortEnd={this.props.onMoveLayer.bind(this)}
useDragHandle={true}
/>
}

View File

@@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import Collapser from './Collapser'
export default class LayerListGroup extends React.Component {
static propTypes = {
title: PropTypes.string.isRequired,
"data-wd-key": PropTypes.string,
isActive: PropTypes.bool.isRequired,
onActiveToggle: PropTypes.func.isRequired
}
render() {
return <li className="maputnik-layer-list-group">
<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)}
>
<span className="maputnik-layer-list-group-title">{this.props.title}</span>
<span className="maputnik-space" />
<Collapser
style={{ height: 14, width: 14 }}
isCollapsed={this.props.isActive}
/>
</div>
</li>
}
}

View File

@@ -1,97 +1,78 @@
import React from 'react'
import Color from 'color'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import CopyIcon from 'react-icons/lib/md/content-copy'
import VisibilityIcon from 'react-icons/lib/md/visibility'
import VisibilityOffIcon from 'react-icons/lib/md/visibility-off'
import DeleteIcon from 'react-icons/lib/md/delete'
import {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from 'react-icons/md'
import LayerIcon from '../icons/LayerIcon'
import LayerEditor from './LayerEditor'
import {SortableElement, SortableHandle} from 'react-sortable-hoc'
import colors from '../../config/colors.js'
import { fontSizes, margins } from '../../config/scales.js'
@SortableHandle
class LayerTypeDragHandle extends React.Component {
static propTypes = LayerIcon.propTypes
render() {
return <LayerIcon
{...this.props}
style={{
cursor: 'move',
width: 15,
height: 15,
paddingRight: margins[0],
}}
const DraggableLabel = SortableHandle((props) => {
return <div className="maputnik-layer-list-item-handle">
<LayerIcon
className="layer-handle__icon"
type={props.layerType}
/>
}
}
<span className="maputnik-layer-list-item-id">{props.layerId}</span>
</div>
});
class IconAction extends React.Component {
static propTypes = {
action: React.PropTypes.string.isRequired,
active: React.PropTypes.bool,
onClick: React.PropTypes.func.isRequired,
}
constructor(props) {
super(props)
this.state = { hover: false }
action: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
wdKey: PropTypes.string,
classBlockName: PropTypes.string,
classBlockModifier: PropTypes.string,
}
renderIcon() {
const iconStyle = {
fill: colors.black
}
if(this.props.active) {
iconStyle.fill = colors.midgray
}
if(this.state.hover) {
iconStyle.fill = colors.lowgray
}
switch(this.props.action) {
case 'copy': return <CopyIcon style={iconStyle} />
case 'show': return <VisibilityIcon style={iconStyle} />
case 'hide': return <VisibilityOffIcon style={iconStyle} />
case 'delete': return <DeleteIcon style={iconStyle} />
default: return null
case 'duplicate': return <MdContentCopy />
case 'show': return <MdVisibility />
case 'hide': return <MdVisibilityOff />
case 'delete': return <MdDelete />
}
}
render() {
return <a
style={{
display: "inline",
marginLeft: margins[0],
...this.props.style
}}
const {classBlockName, classBlockModifier} = this.props;
let classAdditions = '';
if (classBlockName) {
classAdditions = `maputnik-layer-list-icon-action__${classBlockName}`;
if (classBlockModifier) {
classAdditions += ` maputnik-layer-list-icon-action__${classBlockName}--${classBlockModifier}`;
}
}
return <button
tabIndex="-1"
title={this.props.action}
className={`maputnik-layer-list-icon-action ${classAdditions}`}
data-wd-key={this.props.wdKey}
onClick={this.props.onClick}
onMouseOver={e => this.setState({hover: true})}
onMouseOut={e => this.setState({hover: false})}
>
{this.renderIcon()}
</a>
</button>
}
}
@SortableElement
class LayerListItem extends React.Component {
static propTypes = {
layerId: React.PropTypes.string.isRequired,
layerType: React.PropTypes.string.isRequired,
isSelected: React.PropTypes.bool,
visibility: React.PropTypes.string,
layerIndex: PropTypes.number.isRequired,
layerId: PropTypes.string.isRequired,
layerType: PropTypes.string.isRequired,
isSelected: PropTypes.bool,
visibility: PropTypes.string,
className: PropTypes.string,
onLayerSelect: React.PropTypes.func.isRequired,
onLayerCopy: React.PropTypes.func,
onLayerDestroy: React.PropTypes.func,
onLayerVisibilityToggle: React.PropTypes.func,
onLayerSelect: PropTypes.func.isRequired,
onLayerCopy: PropTypes.func,
onLayerDestroy: PropTypes.func,
onLayerVisibilityToggle: PropTypes.func,
}
static defaultProps = {
@@ -103,88 +84,52 @@ class LayerListItem extends React.Component {
}
static childContextTypes = {
reactIconBase: React.PropTypes.object
}
constructor(props) {
super(props)
this.state = {
hover: false
}
reactIconBase: PropTypes.object
}
getChildContext() {
return {
reactIconBase: { size: 12 }
reactIconBase: { size: 14 }
}
}
render() {
const itemStyle = {
fontWeight: 400,
color: colors.lowgray,
fontSize: fontSizes[5],
borderLeft: 0,
borderTop: 0,
borderBottom: 1,
borderRight: 0,
borderStyle: "solid",
userSelect: 'none',
listStyle: 'none',
zIndex: 2000,
cursor: 'pointer',
position: 'relative',
padding: margins[1],
borderColor: Color(colors.black).lighten(0.10).string(),
backgroundColor: colors.black,
}
if(this.state.hover) {
itemStyle.backgroundColor = Color(colors.black).lighten(0.10).string()
}
if(this.props.isSelected) {
itemStyle.backgroundColor = Color(colors.black).lighten(0.15).string()
}
const iconProps = {
active: this.state.hover || this.props.isSelected
}
const visibilityAction = this.props.visibility === 'visible' ? 'show' : 'hide';
return <li
key={this.props.layerId}
onClick={e => this.props.onLayerSelect(this.props.layerId)}
onMouseOver={e => this.setState({hover: true})}
onMouseOut={e => this.setState({hover: false})}
style={itemStyle}>
<div style={{
display: 'flex',
flexDirection: 'row'
}}>
<LayerTypeDragHandle type={this.props.layerType} />
<span style={{
width: 115,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>{this.props.layerId}</span>
onClick={e => this.props.onLayerSelect(this.props.layerIndex)}
data-wd-key={"layer-list-item:"+this.props.layerId}
className={classnames({
"maputnik-layer-list-item": true,
"maputnik-layer-list-item-selected": this.props.isSelected,
[this.props.className]: true,
})}>
<DraggableLabel {...this.props} />
<span style={{flexGrow: 1}} />
<IconAction {...iconProps}
<IconAction
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
action={'delete'}
onClick={e => this.props.onLayerDestroy(this.props.layerId)}
classBlockName="delete"
onClick={e => this.props.onLayerDestroy(this.props.layerIndex)}
/>
<IconAction {...iconProps}
action={'copy'}
onClick={e => this.props.onLayerCopy(this.props.layerId)}
<IconAction
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
action={'duplicate'}
classBlockName="duplicate"
onClick={e => this.props.onLayerCopy(this.props.layerIndex)}
/>
<IconAction {...iconProps}
active={this.state.hover || this.props.isSelected || this.props.visibility === 'none'}
action={this.props.visibility === 'visible' ? 'hide' : 'show'}
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerId)}
<IconAction
wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
action={visibilityAction}
classBlockName="visibility"
classBlockModifier={visibilityAction}
onClick={e => this.props.onLayerVisibilityToggle(this.props.layerIndex)}
/>
</div>
</li>
}
}
export default LayerListItem;
const LayerListItemSortable = SortableElement((props) => <LayerListItem {...props} />);
export default LayerListItemSortable;

View File

@@ -1,50 +0,0 @@
import React from 'react'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
import colors from '../../config/colors'
import { margins } from '../../config/scales'
class LayerSettings extends React.Component {
static propTypes = {
id: React.PropTypes.string.isRequired,
type: React.PropTypes.oneOf(Object.keys(GlSpec.layer.type.values)).isRequired,
onIdChange: React.PropTypes.func.isRequired,
onTypeChange: React.PropTypes.func.isRequired,
}
render() {
return <div style={{
padding: margins[2],
paddingRight: 0,
backgroundColor: colors.black,
}}>
<InputBlock label={"Layer ID"}>
<StringInput
value={this.props.id}
onChange={this.props.onIdChange}
/>
</InputBlock>
<InputBlock label={"Layer Type"}>
<SelectInput
options={[
['background', 'Background'],
['fill', 'Fill'],
['line', 'Line'],
['symbol', 'Symbol'],
['raster', 'Raster'],
['circle', 'Circle'],
['fill-extrusion', 'Fill Extrusion'],
]}
onChange={this.props.onTypeChange}
value={this.props.type}
/>
</InputBlock>
</div>
}
}
export default LayerSettings

View File

@@ -0,0 +1,34 @@
import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceBlock extends React.Component {
static propTypes = {
value: PropTypes.string,
wdKey: PropTypes.string,
onChange: PropTypes.func,
sourceIds: PropTypes.array,
}
static defaultProps = {
onChange: () => {},
sourceIds: [],
}
render() {
return <InputBlock label={"Source"} fieldSpec={latest.layer.source}
data-wd-key={this.props.wdKey}
>
<AutocompleteInput
value={this.props.value}
onChange={this.props.onChange}
options={this.props.sourceIds.map(src => [src, src])}
/>
</InputBlock>
}
}
export default LayerSourceBlock

View File

@@ -0,0 +1,36 @@
import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import AutocompleteInput from '../inputs/AutocompleteInput'
class LayerSourceLayer extends React.Component {
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func,
sourceLayerIds: PropTypes.array,
isFixed: PropTypes.bool,
}
static defaultProps = {
onChange: () => {},
sourceLayerIds: [],
isFixed: false
}
render() {
return <InputBlock label={"Source Layer"} fieldSpec={latest.layer['source-layer']}
data-wd-key="layer-source-layer"
>
<AutocompleteInput
keepMenuWithinWindowBounds={!!this.props.isFixed}
value={this.props.value}
onChange={this.props.onChange}
options={this.props.sourceLayerIds.map(l => [l, l])}
/>
</InputBlock>
}
}
export default LayerSourceLayer

View File

@@ -0,0 +1,54 @@
import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import SelectInput from '../inputs/SelectInput'
import StringInput from '../inputs/StringInput'
class LayerTypeBlock extends React.Component {
static propTypes = {
value: PropTypes.string.isRequired,
wdKey: PropTypes.string,
onChange: PropTypes.func.isRequired,
error: PropTypes.object,
disabled: PropTypes.bool,
}
static defaultProps = {
disabled: false,
}
render() {
return <InputBlock label={"Type"} fieldSpec={latest.layer.type}
data-wd-key={this.props.wdKey}
error={this.props.error}
>
{this.props.disabled &&
<StringInput
value={this.props.value}
disabled={true}
/>
}
{!this.props.disabled &&
<SelectInput
options={[
['background', 'Background'],
['fill', 'Fill'],
['line', 'Line'],
['symbol', 'Symbol'],
['raster', 'Raster'],
['circle', 'Circle'],
['fill-extrusion', 'Fill Extrusion'],
['hillshade', 'Hillshade'],
['heatmap', 'Heatmap'],
]}
onChange={this.props.onChange}
value={this.props.value}
/>
}
</InputBlock>
}
}
export default LayerTypeBlock

View File

@@ -0,0 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput'
class MaxZoomBlock extends React.Component {
static propTypes = {
value: PropTypes.number,
onChange: PropTypes.func.isRequired,
error: PropTypes.object,
}
render() {
return <InputBlock label={"Max Zoom"} fieldSpec={latest.layer.maxzoom}
error={this.props.error}
data-wd-key="max-zoom"
>
<NumberInput
allowRange={true}
value={this.props.value}
onChange={this.props.onChange}
min={latest.layer.maxzoom.minimum}
max={latest.layer.maxzoom.maximum}
default={latest.layer.maxzoom.maximum}
/>
</InputBlock>
}
}
export default MaxZoomBlock

View File

@@ -0,0 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput'
class MinZoomBlock extends React.Component {
static propTypes = {
value: PropTypes.number,
onChange: PropTypes.func.isRequired,
error: PropTypes.object,
}
render() {
return <InputBlock label={"Min Zoom"} fieldSpec={latest.layer.minzoom}
error={this.props.error}
data-wd-key="min-zoom"
>
<NumberInput
allowRange={true}
value={this.props.value}
onChange={this.props.onChange}
min={latest.layer.minzoom.minimum}
max={latest.layer.minzoom.maximum}
default={latest.layer.minzoom.minimum}
/>
</InputBlock>
}
}
export default MinZoomBlock

View File

@@ -1,54 +0,0 @@
import React from 'react'
import PropertyGroup from '../fields/PropertyGroup'
import input from '../../config/input.js'
/** Choose tileset (source) and the source layer */
export default class SourceEditor extends React.Component {
static propTypes = {
source: React.PropTypes.string.isRequired,
sourceLayer: React.PropTypes.string.isRequired,
onSourceChange: React.PropTypes.func.isRequired,
onSourceLayerChange: React.PropTypes.func.isRequired,
/** List of available sources in the style
* https://www.mapbox.com/mapbox-gl-style-spec/#root-sources */
sources: React.PropTypes.object.isRequired,
}
render() {
const options = Object.keys(this.props.sources).map(sourceId => {
return <option key={sourceId} value={sourceId}>{sourceId}</option>
})
const layerOptions = this.props.sources[this.props.source].map(vectorLayerId => {
const id = vectorLayerId
return <option key={id} value={id}>{id}</option>
})
return <div>
<div style={input.property}>
<label style={input.label}>Source</label>
<select
style={input.select}
value={this.props.source}
onChange={(e) => this.onSourceChange(e.target.value)}
>
{options}
</select>
</div>
<div style={input.property}>
<label style={input.label}>Source Layer</label>
<select
style={input.select}
value={this.props.sourceLayer}
onChange={(e) => this.onSourceLayerChange(e.target.value)}
>
{layerOptions}
</select>
</div>
</div>
}
}

View File

@@ -0,0 +1,123 @@
import React from 'react'
import PropTypes from 'prop-types'
import LayerIcon from '../icons/LayerIcon'
import {latest, expression, function as styleFunction} from '@mapbox/mapbox-gl-style-spec'
function groupFeaturesBySourceLayer(features) {
const sources = {}
let returnedFeatures = {};
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']].push(feature)
returnedFeatures[feature.layer.id] = 1
}
})
return sources
}
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];
return String(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.warn("Unable to get feature color, error:", err);
return "black";
}
}
render() {
const sources = groupFeaturesBySourceLayer(this.props.features)
const items = Object.keys(sources).map(vectorLayerId => {
const layers = sources[vectorLayerId].map((feature, idx) => {
const featureColor = this._getFeatureColor(feature, this.props.zoom);
return <div
key={idx}
className="maputnik-popup-layer"
>
<div
className="maputnik-popup-layer__swatch"
style={{background: featureColor}}
></div>
<label
className="maputnik-popup-layer__label"
onClick={() => {
this.props.onLayerSelect(feature.layer.id)
}}
>
{feature.layer.type &&
<LayerIcon type={feature.layer.type} style={{
width: 14,
height: 14,
paddingRight: 3
}}/>
}
{feature.layer.id}
{feature.counter && <span> × {feature.counter}</span>}
</label>
</div>
})
return <div key={vectorLayerId}>
<div className="maputnik-popup-layer-id">{vectorLayerId}</div>
{layers}
</div>
})
return <div className="maputnik-feature-layer-popup">
{items}
</div>
}
}
export default FeatureLayerPopup

View File

@@ -0,0 +1,71 @@
import React from 'react'
import PropTypes from 'prop-types'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
function displayValue(value) {
if (typeof value === 'undefined' || value === null) return value;
if (value instanceof Date) return value.toLocaleString();
if (typeof value === 'object' ||
typeof value === 'number' ||
typeof value === 'string') return value.toString();
return value;
}
function renderProperties(feature) {
return Object.keys(feature.properties).map(propertyName => {
const property = feature.properties[propertyName]
return <InputBlock key={propertyName} label={propertyName}>
<StringInput value={displayValue(property)} style={{backgroundColor: 'transparent'}}/>
</InputBlock>
})
}
function renderFeature(feature, idx) {
return <div key={`${feature.sourceLayer}-${idx}`}>
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</div>
<InputBlock key={"property-type"} label={"$type"}>
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
</InputBlock>
{renderProperties(feature)}
</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 {
static propTypes = {
features: PropTypes.array
}
render() {
const features = removeDuplicatedFeatures(this.props.features)
return <div className="maputnik-feature-property-popup">
{features.map(renderFeature)}
</div>
}
}
export default FeaturePropertyPopup

View File

@@ -1,20 +0,0 @@
import React from 'react'
export default class Map extends React.Component {
static propTypes = {
mapStyle: React.PropTypes.object.isRequired,
accessToken: React.PropTypes.string,
}
render() {
return <div
ref={x => this.container = x}
style={{
position: "fixed",
top: 0,
bottom: 0,
height: "100%",
width: "100%",
}}></div>
}
}

View File

@@ -1,42 +1,233 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import MapboxGl from 'mapbox-gl'
import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color'
import MapboxInspect from 'mapbox-gl-inspect'
import FeatureLayerPopup from './FeatureLayerPopup'
import FeaturePropertyPopup from './FeaturePropertyPopup'
import tokens from '../../config/tokens.json'
import colors from 'mapbox-gl-inspect/lib/colors'
import Color from 'color'
import ZoomControl from '../../libs/zoomcontrol'
import { colorHighlightedLayer } from '../../libs/highlight'
import 'mapbox-gl/dist/mapbox-gl.css'
import '../../mapboxgl.css'
import '../../libs/mapbox-rtl'
import Map from './Map.jsx'
import style from '../../libs/style.js'
export default class MapboxGlMap extends Map {
const IS_SUPPORTED = MapboxGl.supported();
function renderPopup(popup, mountNode) {
ReactDOM.render(popup, mountNode);
return mountNode;
}
function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
const backgroundLayer = {
"id": "background",
"type": "background",
"paint": {
"background-color": '#1c1f24',
}
}
const layer = colorHighlightedLayer(highlightedLayer)
if(layer) {
coloredLayers.push(layer)
}
const sources = {}
Object.keys(originalMapStyle.sources).forEach(sourceId => {
const source = originalMapStyle.sources[sourceId]
if(source.type !== 'raster' && source.type !== 'raster-dem') {
sources[sourceId] = source
}
})
const inspectStyle = {
...originalMapStyle,
sources: sources,
layers: [backgroundLayer].concat(coloredLayers)
}
return inspectStyle
}
export default class MapboxGlMap extends React.Component {
static propTypes = {
onMapLoaded: React.PropTypes.func,
onDataChange: PropTypes.func,
onLayerSelect: PropTypes.func.isRequired,
mapStyle: PropTypes.object.isRequired,
inspectModeEnabled: PropTypes.bool.isRequired,
highlightedLayer: PropTypes.object,
options: PropTypes.object,
replaceAccessTokens: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
}
static defaultProps = {
onMapLoaded: () => {}
onMapLoaded: () => {},
onDataChange: () => {},
onLayerSelect: () => {},
onChange: () => {},
mapboxAccessToken: tokens.mapbox,
options: {},
}
constructor(props) {
super(props)
this.state = { map: null }
MapboxGl.accessToken = tokens.mapbox
this.state = {
map: null,
inspect: null,
}
}
componentWillReceiveProps(nextProps) {
updateMapFromProps(props) {
if(!IS_SUPPORTED) return;
if(!this.state.map) return
const metadata = props.mapStyle.metadata || {}
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
//Mapbox GL now does diffing natively so we don't need to calculate
//the necessary operations ourselves!
this.state.map.setStyle(nextProps.mapStyle, { diff: true})
this.state.map.setStyle(
this.props.replaceAccessTokens(props.mapStyle),
{diff: true}
)
}
componentDidUpdate(prevProps) {
if(!IS_SUPPORTED) return;
const map = this.state.map;
this.updateMapFromProps(this.props);
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
// HACK: Fix for <https://github.com/maputnik/editor/issues/576>, while we wait for a proper fix.
// eslint-disable-next-line
this.state.inspect._popupBlocked = false;
this.state.inspect.toggleInspector()
}
if(this.props.inspectModeEnabled) {
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() {
MapboxGl.accessToken = this.props.accessToken
if(!IS_SUPPORTED) return;
const map = new MapboxGl.Map({
const mapOpts = {
...this.props.options,
container: this.container,
style: this.props.mapStyle,
hash: true,
maxZoom: 24
}
const map = new MapboxGl.Map(mapOpts);
const mapViewChange = () => {
const center = map.getCenter();
const zoom = map.getZoom();
this.props.onChange({center, zoom});
}
mapViewChange();
map.showTileBoundaries = mapOpts.showTileBoundaries;
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
map.showOverdrawInspector = mapOpts.showOverdrawInspector;
const zoomControl = new ZoomControl;
map.addControl(zoomControl, 'top-right');
const nav = new MapboxGl.NavigationControl({visualizePitch:true});
map.addControl(nav, 'top-right');
const tmpNode = document.createElement('div');
const inspect = new MapboxInspect({
popup: new MapboxGl.Popup({
closeOnClick: false
}),
showMapPopup: true,
showMapPopupOnHover: false,
showInspectMapPopupOnHover: true,
showInspectButton: false,
blockHoverPopupOnClick: true,
assignLayerColor: (layerId, alpha) => {
return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string()
},
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
renderPopup: features => {
if(this.props.inspectModeEnabled) {
return renderPopup(<FeaturePropertyPopup features={features} />, tmpNode);
} else {
return renderPopup(<FeatureLayerPopup features={features} onLayerSelect={this.onLayerSelectById} zoom={this.state.zoom} />, tmpNode);
}
}
})
map.addControl(inspect)
map.on("style.load", () => {
this.setState({
map,
inspect,
zoom: map.getZoom()
});
if(this.props.inspectModeEnabled) {
inspect.toggleInspector();
}
})
map.on("data", e => {
if(e.dataType !== 'tile') return
this.props.onDataChange({
map: this.state.map
})
})
map.on("error", e => {
console.log("ERROR", e);
})
map.on("zoom", e => {
this.setState({
zoom: map.getZoom()
});
});
map.on("style.load", (...args) => {
this.props.onMapLoaded(map)
this.setState({ map });
});
map.on("dragend", mapViewChange);
map.on("zoomend", mapViewChange);
}
onLayerSelectById = (id) => {
const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id);
this.props.onLayerSelect(index);
}
render() {
if(IS_SUPPORTED) {
return <div
className="maputnik-map__map"
ref={x => this.container = x}
></div>
}
else {
return <div
className="maputnik-map maputnik-map--error"
>
<div className="maputnik-map__error-message">
Error: Cannot load MapboxGL, WebGL is either unsupported or disabled
</div>
</div>
}
}
}

View File

@@ -1,68 +0,0 @@
import React from 'react'
import Map from './Map'
import style from '../../libs/style.js'
class OpenLayers3Map extends Map {
constructor(props) {
super(props)
}
componentWillReceiveProps(nextProps) {
require.ensure(["openlayers", "ol-mapbox-style"], ()=> {
const ol = require('openlayers')
const olms = require('ol-mapbox-style')
const jsonStyle = nextProps.mapStyle
const styleFunc = olms.getStyleFunction(jsonStyle, 'openmaptiles', this.resolutions)
console.log('New style babee')
const layer = this.layer
layer.setStyle(styleFunc)
//NOTE: We need to mark the source as changed in order
//to trigger a rerender
layer.getSource().changed()
this.state.map.render()
})
}
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')
const tilegrid = ol.tilegrid.createXYZ({tileSize: 512, maxZoom: 22})
this.resolutions = tilegrid.getResolutions()
this.layer = new ol.layer.VectorTile({
source: new ol.source.VectorTile({
attributions: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>',
format: new ol.format.MVT(),
tileGrid: tilegrid,
tilePixelRatio: 8,
url: 'https://free-0.tilehosting.com/data/v3/{z}/{x}/{y}.pbf?key=tXiQqN3lIgskyDErJCeY'
})
})
const jsonStyle = this.props.mapStyle
const styleFunc = olms.getStyleFunction(jsonStyle, 'openmaptiles', this.resolutions)
this.layer.setStyle(styleFunc)
const map = new ol.Map({
target: this.container,
layers: [this.layer],
view: new ol.View({
center: jsonStyle.center,
zoom: 2,
//zoom: jsonStyle.zoom,
})
})
map.addControl(new ol.control.Zoom());
this.setState({ map });
})
}
}
export default OpenLayers3Map

View File

@@ -0,0 +1,189 @@
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,
replaceAccessTokens: PropTypes.func.isRequired,
onChange: PropTypes.func.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.replaceAccessTokens(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)
]
})
})
const onMoveEnd = () => {
const zoom = map.getView().getZoom();
const center = toLonLat(map.getView().getCenter());
this.props.onChange({
zoom,
center: {
lng: center[0],
lat: center[1],
},
});
}
onMoveEnd();
map.on('moveend', onMoveEnd);
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.replaceAccessTokens(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: {this.state.zoom}
</div>
{this.props.debugToolbox &&
<div className="maputnik-ol-debug">
<div>
<label>cursor: </label>
<span>{renderCoords(this.state.cursor)}</span>
</div>
<div>
<label>center: </label>
<span>{renderCoords(this.state.center)}</span>
</div>
<div>
<label>rotation: </label>
<span>{this.state.rotation}</span>
</div>
</div>
}
<div
className="maputnik-ol"
ref={x => this.container = x}
style={{
...this.props.style,
}}>
</div>
</div>
}
}

View File

@@ -0,0 +1,172 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
import LayerTypeBlock from '../layers/LayerTypeBlock'
import LayerIdBlock from '../layers/LayerIdBlock'
import LayerSourceBlock from '../layers/LayerSourceBlock'
import LayerSourceLayerBlock from '../layers/LayerSourceLayerBlock'
class AddModal extends React.Component {
static propTypes = {
layers: PropTypes.array.isRequired,
onLayersChange: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
// A dict of source id's and the available source layers
sources: PropTypes.object.isRequired,
}
addLayer = () => {
const changedLayers = this.props.layers.slice(0)
const layer = {
id: this.state.id,
type: this.state.type,
}
if(this.state.type !== 'background') {
layer.source = this.state.source
if(this.state.type !== 'raster' && this.state['source-layer']) {
layer['source-layer'] = this.state['source-layer']
}
}
changedLayers.push(layer)
this.props.onLayersChange(changedLayers)
this.props.onOpenToggle(false)
}
constructor(props) {
super(props)
this.state = {
type: 'fill',
id: '',
}
if(props.sources.length > 0) {
this.state.source = Object.keys(this.props.sources)[0]
this.state['source-layer'] = this.props.sources[this.state.source][0]
}
}
componentDidUpdate(prevProps, prevState) {
// Check if source is valid for new type
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({
source: ""
});
}
}
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() {
const sources = this.getSources(this.state.type);
const layers = this.getLayersForSource(this.state.source);
return <Modal
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Add Layer'}
data-wd-key="modal:add-layer"
className="maputnik-add-modal"
>
<div className="maputnik-add-layer">
<LayerIdBlock
value={this.state.id}
wdKey="add-layer.layer-id"
onChange={v => {
this.setState({ id: v })
}}
/>
<LayerTypeBlock
value={this.state.type}
wdKey="add-layer.layer-type"
onChange={v => this.setState({ type: v })}
/>
{this.state.type !== 'background' &&
<LayerSourceBlock
sourceIds={sources}
wdKey="add-layer.layer-source-block"
value={this.state.source}
onChange={v => this.setState({ source: v })}
/>
}
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
<LayerSourceLayerBlock
isFixed={true}
sourceLayerIds={layers}
value={this.state['source-layer']}
onChange={v => this.setState({ 'source-layer': v })}
/>
}
<Button
className="maputnik-add-layer-button"
onClick={this.addLayer}
data-wd-key="add-layer"
>
Add Layer
</Button>
</div>
</Modal>
}
}
export default AddModal

View File

@@ -0,0 +1,73 @@
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,
mapView: PropTypes.object,
}
render() {
const {mapView} = this.props;
const osmZoom = Math.round(mapView.zoom)+1;
const osmLon = Number.parseFloat(mapView.center.lng).toFixed(5);
const osmLat = Number.parseFloat(mapView.center.lat).toFixed(5);
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">
<h4>Options</h4>
{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>
<div className="maputnik-modal-section">
<h4>Links</h4>
<p>
<a
target="_blank"
rel="noopener noreferrer"
href={`https://www.openstreetmap.org/#map=${osmZoom}/${osmLat}/${osmLon}`}
>
Open in OSM
</a> &mdash; Opens the current view on openstreetmap.org
</p>
</div>
</Modal>
}
}
export default DebugModal;

View File

@@ -0,0 +1,123 @@
import React from 'react'
import PropTypes from 'prop-types'
import Slugify from 'slugify'
import { saveAs } from 'file-saver'
import {format} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button'
import Modal from './Modal'
import {MdFileDownload} from 'react-icons/md'
import style from '../../libs/style'
import fieldSpecAdditional from '../../libs/field-spec-additional'
function stripAccessTokens(mapStyle) {
const changedMetadata = { ...mapStyle.metadata }
delete changedMetadata['maputnik:mapbox_access_token']
delete changedMetadata['maputnik:openmaptiles_access_token']
return {
...mapStyle,
metadata: changedMetadata
}
}
class ExportModal extends React.Component {
static propTypes = {
mapStyle: PropTypes.object.isRequired,
onStyleChanged: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
}
downloadStyle() {
const tokenStyle = format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle)));
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
let exportName;
if(this.props.mapStyle.name) {
exportName = Slugify(this.props.mapStyle.name, {
replacement: '_',
lower: true
})
} else {
exportName = this.props.mapStyle.id
}
saveAs(blob, exportName + ".json");
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
render() {
return <Modal
data-wd-key="export-modal"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Export Style'}
className="maputnik-export-modal"
>
<div className="maputnik-modal-section">
<h4>Download Style</h4>
<p>
Download a JSON style to your computer.
</p>
<div>
<InputBlock
label={fieldSpecAdditional.maputnik.mapbox_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.mapbox_access_token}
>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
</InputBlock>
<InputBlock
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/>
</InputBlock>
<InputBlock
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
/>
</InputBlock>
</div>
<Button onClick={this.downloadStyle.bind(this)}>
<MdFileDownload />
Download
</Button>
</div>
</Modal>
}
}
export default ExportModal

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

View File

@@ -1,48 +1,74 @@
import React from 'react'
import PropTypes from 'prop-types'
import {MdClose} from 'react-icons/md'
import AriaModal from 'react-aria-modal'
import classnames from 'classnames';
import CloseIcon from 'react-icons/lib/md/close'
import Overlay from './Overlay'
import colors from '../../config/colors'
import { margins, fontSizes } from '../../config/scales'
class Modal extends React.Component {
static propTypes = {
isOpen: React.PropTypes.bool.isRequired,
title: React.PropTypes.string.isRequired,
onOpenToggle: React.PropTypes.func.isRequired,
"data-wd-key": PropTypes.string,
isOpen: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
onOpenToggle: PropTypes.func.isRequired,
children: PropTypes.node,
underlayClickExits: PropTypes.bool,
underlayProps: PropTypes.object,
className: PropTypes.string,
}
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() {
return <Overlay isOpen={this.props.isOpen}>
<div style={{
minWidth: 350,
maxWidth: 600,
backgroundColor: colors.black,
boxShadow: '0px 0px 5px 0px rgba(0,0,0,0.3)',
}}>
<div style={{
backgroundColor: colors.gray,
display: 'flex',
flexDirection: 'row',
padding: margins[2],
fontSize: fontSizes[4],
}}>
{this.props.title}
<span style={{flexGrow: 1}} />
<a
onClick={() => this.props.onOpenToggle(false)}
style={{ cursor: 'pointer' }} >
<CloseIcon />
</a>
if(this.props.isOpen) {
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={classnames("maputnik-modal", this.props.className)}
data-wd-key={this.props["data-wd-key"]}
>
<header className="maputnik-modal-header">
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
<span className="maputnik-modal-header-space"></span>
<button className="maputnik-modal-header-toggle"
onClick={this.onClose}
data-wd-key={this.props["data-wd-key"]+".close-modal"}
>
<MdClose />
</button>
</header>
<div className="maputnik-modal-scroller">
<div className="maputnik-modal-content">{this.props.children}</div>
</div>
</div>
<div style={{
padding: margins[2],
}}>
{this.props.children}
</div>
</div>
</Overlay>
</AriaModal>
}
else {
return false;
}
}
}

View File

@@ -1,63 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
import LoadingModal from './LoadingModal'
import Modal from './Modal'
import Heading from '../Heading'
import Button from '../Button'
import Paragraph from '../Paragraph'
import FileReaderInput from 'react-file-reader-input'
import request from 'request'
import UrlInput from '../inputs/UrlInput'
import FileUploadIcon from 'react-icons/lib/md/file-upload'
import AddIcon from 'react-icons/lib/md/add-circle-outline'
import {MdFileUpload} from 'react-icons/md'
import {MdAddCircleOutline} from 'react-icons/md'
import style from '../../libs/style.js'
import colors from '../../config/colors'
import { margins, fontSizes } from '../../config/scales'
import publicStyles from '../../config/styles.json'
class PublicStyle extends React.Component {
static propTypes = {
url: React.PropTypes.string.isRequired,
thumbnailUrl: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
onSelect: React.PropTypes.func.isRequired,
url: PropTypes.string.isRequired,
thumbnailUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
onSelect: PropTypes.func.isRequired,
}
render() {
return <div style={{
verticalAlign: 'top',
marginTop: margins[2],
marginRight: margins[2],
backgroundColor: colors.gray,
display: 'inline-block',
width: 180,
fontSize: fontSizes[4],
color: colors.lowgray,
}}>
return <div className="maputnik-public-style">
<Button
className="maputnik-public-style-button"
aria-label={this.props.title}
onClick={() => this.props.onSelect(this.props.url)}
style={{
backgroundColor: 'transparent',
padding: margins[2],
display: 'block',
}}
>
<div style={{
display: 'flex',
flexDirection: 'row',
}}>
<span style={{fontWeight: 700}}>{this.props.title}</span>
<span style={{flexGrow: 1}} />
<AddIcon />
</div>
<img
<header className="maputnik-public-style-header">
<h4>{this.props.title}</h4>
<span className="maputnik-space" />
<MdAddCircleOutline />
</header>
<div
className="maputnik-public-style-thumbnail"
style={{
display: 'block',
marginTop: margins[1],
maxWidth: '100%',
backgroundImage: `url(${this.props.thumbnailUrl})`
}}
src={this.props.thumbnailUrl}
alt={this.props.title}
/>
></div>
</Button>
</div>
}
@@ -65,38 +45,127 @@ class PublicStyle extends React.Component {
class OpenModal extends React.Component {
static propTypes = {
isOpen: React.PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired,
onStyleOpen: React.PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
onStyleOpen: PropTypes.func.isRequired,
}
onStyleSelect(styleUrl) {
request({
url: styleUrl,
withCredentials: false,
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
const mapStyle = style.ensureMetadataExists(JSON.parse(body))
console.log('Loaded style ', mapStyle.id)
this.props.onStyleOpen(mapStyle)
} else {
console.warn('Could not open the style URL', styleUrl)
}
constructor(props) {
super(props);
this.state = {
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 = (url) => {
this.onStyleSelect(this.state.styleUrl);
}
onUpload = (_, files) => {
const [e, file] = files[0];
const reader = new FileReader();
this.clearError();
reader.readAsText(file, "UTF-8");
reader.onload = e => {
let mapStyle = JSON.parse(e.target.result)
mapStyle = style.ensureMetadataExists(mapStyle)
let mapStyle;
try {
mapStyle = JSON.parse(e.target.result)
}
catch(err) {
this.setState({
error: err.toString()
});
return;
}
mapStyle = style.ensureStyleValidity(mapStyle)
this.props.onStyleOpen(mapStyle);
this.onOpenToggle();
}
reader.onerror = e => console.log(e.target);
}
onOpenToggle() {
this.setState({
styleUrl: ""
});
this.clearError();
this.props.onOpenToggle();
}
onChangeUrl = (url) => {
this.setState({
styleUrl: url,
});
}
render() {
const styleOptions = publicStyles.map(style => {
return <PublicStyle
@@ -104,32 +173,79 @@ class OpenModal extends React.Component {
url={style.url}
title={style.title}
thumbnailUrl={style.thumbnail}
onSelect={this.onStyleSelect.bind(this)}
onSelect={this.onStyleSelect}
/>
})
return <Modal
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Open Style'}
>
<Heading level={4}>Upload Style</Heading>
<Paragraph>
Upload a JSON style from your computer.
</Paragraph>
<FileReaderInput onChange={this.onUpload.bind(this)}>
<Button>
<FileUploadIcon />
Upload
</Button>
</FileReaderInput>
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>
);
}
<Heading level={4}>Gallery Styles</Heading>
<Paragraph>
Open one of the publicly available styles to start from.
</Paragraph>
{styleOptions}
</Modal>
return (
<div>
<Modal
data-wd-key="open-modal"
isOpen={this.props.isOpen}
onOpenToggle={() => this.onOpenToggle()}
title={'Open Style'}
>
{errorElement}
<section className="maputnik-modal-section">
<h2>Upload Style</h2>
<p>Upload a JSON style from your computer.</p>
<FileReaderInput onChange={this.onUpload} tabIndex="-1">
<Button className="maputnik-upload-button"><MdFileUpload /> Upload</Button>
</FileReaderInput>
</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>
<UrlInput
data-wd-key="open-modal.url.input"
type="text"
className="maputnik-input"
default="Enter URL..."
value={this.state.styleUrl}
onInput={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>
<p>
Open one of the publicly available styles to start from.
</p>
<div className="maputnik-style-gallery-container">
{styleOptions}
</div>
</section>
</Modal>
<LoadingModal
isOpen={!!this.state.activeRequest}
title={'Loading style'}
onCancel={(e) => this.onCancelActiveRequest(e)}
message={"Loading: "+this.state.activeRequestUrl}
/>
</div>
)
}
}

View File

@@ -1,53 +0,0 @@
import React from 'react'
class ViewportOverlay extends React.Component {
static propTypes = {
style: React.PropTypes.object
}
render() {
const overlayStyle = {
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
zIndex: 2,
opacity: 0.875,
backgroundColor: 'rgb(28, 31, 36)',
...this.props.style
}
return <div style={overlayStyle} />
}
}
class Overlay extends React.Component {
static propTypes = {
isOpen: React.PropTypes.bool.isRequired,
children: React.PropTypes.element.isRequired
}
render() {
return <div style={{
top: 0,
right: 0,
bottom: 0,
left: 0,
position: 'fixed',
display: this.props.isOpen ? 'flex' : 'none',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
}}>
<ViewportOverlay />
<div style={{
zIndex: 3,
}}>
{this.props.children}
</div>
</div>
}
}
export default Overlay

View File

@@ -1,83 +1,267 @@
import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import InputBlock from '../inputs/InputBlock'
import ArrayInput from '../inputs/ArrayInput'
import NumberInput from '../inputs/NumberInput'
import StringInput from '../inputs/StringInput'
import UrlInput from '../inputs/UrlInput'
import SelectInput from '../inputs/SelectInput'
import EnumInput from '../inputs/EnumInput'
import ColorField from '../fields/ColorField'
import Modal from './Modal'
import colors from '../../config/colors'
import fieldSpecAdditional from '../../libs/field-spec-additional'
class SettingsModal extends React.Component {
static propTypes = {
mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
isOpen: React.PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired,
mapStyle: PropTypes.object.isRequired,
onStyleChanged: PropTypes.func.isRequired,
onChangeMetadataProperty: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
changeTransitionProperty(property, value) {
const transition = {
...this.props.mapStyle.transition,
}
if (value === undefined) {
delete transition[property];
}
else {
transition[property] = value;
}
this.props.onStyleChanged({
...this.props.mapStyle,
transition,
});
}
onChange(property, e) {
const changedStyle = this.props.mapStyle.set(property, e.target.value)
this.props.onStyleChanged(changedStyle)
changeLightProperty(property, value) {
const light = {
...this.props.mapStyle.light,
}
if (value === undefined) {
delete light[property];
}
else {
light[property] = value;
}
this.props.onStyleChanged({
...this.props.mapStyle,
light,
});
}
onRendererChange(renderer) {
changeStyleProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
'maputnik:renderer': renderer,
}
};
if (value === undefined) {
delete changedStyle[property];
}
this.props.onStyleChanged(changedStyle)
else {
changedStyle[property] = value;
}
this.props.onStyleChanged(changedStyle);
}
render() {
const metadata = this.props.mapStyle.metadata || {}
const {onChangeMetadataProperty, mapStyle} = this.props;
const inputProps = { }
const light = this.props.mapStyle.light || {};
const transition = this.props.mapStyle.transition || {};
return <Modal
data-wd-key="modal-settings"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'StyleSettings'}
title={'Style Settings'}
>
<InputBlock label={"Name"}>
<div className="modal-settings">
<InputBlock label={"Name"} fieldSpec={latest.$root.name}>
<StringInput {...inputProps}
data-wd-key="modal-settings.name"
value={this.props.mapStyle.name}
onChange={this.onChange.bind(this, "name")}
onChange={this.changeStyleProperty.bind(this, "name")}
/>
</InputBlock>
<InputBlock label={"Owner"}>
<InputBlock label={"Owner"} fieldSpec={{doc: "Owner ID of the style. Used by Mapbox or future style APIs."}}>
<StringInput {...inputProps}
data-wd-key="modal-settings.owner"
value={this.props.mapStyle.owner}
onChange={this.onChange.bind(this, "owner")}
onChange={this.changeStyleProperty.bind(this, "owner")}
/>
</InputBlock>
<InputBlock label={"Sprite URL"}>
<StringInput {...inputProps}
<InputBlock label={"Sprite URL"} fieldSpec={latest.$root.sprite}>
<UrlInput {...inputProps}
data-wd-key="modal-settings.sprite"
value={this.props.mapStyle.sprite}
onChange={this.onChange.bind(this, "sprite")}
onChange={this.changeStyleProperty.bind(this, "sprite")}
/>
</InputBlock>
<InputBlock label={"Glyphs URL"}>
<StringInput {...inputProps}
<InputBlock label={"Glyphs URL"} fieldSpec={latest.$root.glyphs}>
<UrlInput {...inputProps}
data-wd-key="modal-settings.glyphs"
value={this.props.mapStyle.glyphs}
onChange={this.onChange.bind(this, "glyphs")}
onChange={this.changeStyleProperty.bind(this, "glyphs")}
/>
</InputBlock>
<InputBlock
label={fieldSpecAdditional.maputnik.mapbox_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.mapbox_access_token}
>
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:mapbox_access_token"
value={metadata['maputnik:mapbox_access_token']}
onChange={onChangeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/>
</InputBlock>
<InputBlock label={"Style Renderer"}>
<InputBlock
label={fieldSpecAdditional.maputnik.maptiler_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.maptiler_access_token}
>
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
value={metadata['maputnik:openmaptiles_access_token']}
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
</InputBlock>
<InputBlock
label={fieldSpecAdditional.maputnik.thunderforest_access_token.label}
fieldSpec={fieldSpecAdditional.maputnik.thunderforest_access_token}
>
<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 label={"Center"} fieldSpec={latest.$root.center}>
<ArrayInput
length={2}
type="number"
value={mapStyle.center}
default={latest.$root.center.default || [0, 0]}
onChange={this.changeStyleProperty.bind(this, "center")}
/>
</InputBlock>
<InputBlock label={"Zoom"} fieldSpec={latest.$root.zoom}>
<NumberInput
{...inputProps}
value={mapStyle.zoom}
default={latest.$root.zoom.default || 0}
onChange={this.changeStyleProperty.bind(this, "zoom")}
/>
</InputBlock>
<InputBlock label={"Bearing"} fieldSpec={latest.$root.bearing}>
<NumberInput
{...inputProps}
value={mapStyle.bearing}
default={latest.$root.bearing.default}
onChange={this.changeStyleProperty.bind(this, "bearing")}
/>
</InputBlock>
<InputBlock label={"Pitch"} fieldSpec={latest.$root.pitch}>
<NumberInput
{...inputProps}
value={mapStyle.pitch}
default={latest.$root.pitch.default}
onChange={this.changeStyleProperty.bind(this, "pitch")}
/>
</InputBlock>
<InputBlock label={"Light anchor"} fieldSpec={latest.light.anchor}>
<EnumInput
{...inputProps}
value={light.anchor}
options={Object.keys(latest.light.anchor.values)}
default={latest.light.anchor.default}
onChange={this.changeLightProperty.bind(this, "anchor")}
/>
</InputBlock>
<InputBlock label={"Light color"} fieldSpec={latest.light.color}>
<ColorField
{...inputProps}
value={light.color}
default={latest.light.color.default}
onChange={this.changeLightProperty.bind(this, "color")}
/>
</InputBlock>
<InputBlock label={"Light intensity"} fieldSpec={latest.light.intensity}>
<NumberInput
{...inputProps}
value={light.intensity}
default={latest.light.intensity.default}
onChange={this.changeLightProperty.bind(this, "intensity")}
/>
</InputBlock>
<InputBlock label={"Light position"} fieldSpec={latest.light.position}>
<ArrayInput
{...inputProps}
type="number"
length={latest.light.position.length}
value={light.position}
default={latest.light.position.default}
onChange={this.changeLightProperty.bind(this, "position")}
/>
</InputBlock>
<InputBlock label={"Transition delay"} fieldSpec={latest.transition.delay}>
<NumberInput
{...inputProps}
value={transition.delay}
default={latest.transition.delay.default}
onChange={this.changeTransitionProperty.bind(this, "delay")}
/>
</InputBlock>
<InputBlock label={"Transition duration"} fieldSpec={latest.transition.duration}>
<NumberInput
{...inputProps}
value={transition.duration}
default={latest.transition.duration.default}
onChange={this.changeTransitionProperty.bind(this, "duration")}
/>
</InputBlock>
<InputBlock
label={fieldSpecAdditional.maputnik.style_renderer.label}
fieldSpec={fieldSpecAdditional.maputnik.style_renderer}
>
<SelectInput {...inputProps}
data-wd-key="modal-settings.maputnik:renderer"
options={[
['mbgljs', 'MapboxGL JS'],
['ol3', 'Open Layers 3']
['ol', 'Open Layers (experimental)'],
]}
value={(this.props.mapStyle.metadata || {})['maputnik:renderer'] || 'mbgljs'}
onChange={this.onRendererChange.bind(this)}
value={metadata['maputnik:renderer'] || 'mbgljs'}
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
/>
</InputBlock>
</div>
</Modal>
}
}

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