Compare commits

..

119 Commits

Author SHA1 Message Date
orangemug
f0e4b5b930 1.5.0-beta 2018-09-01 11:09:19 +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
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
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
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
63 changed files with 1497 additions and 615 deletions

View File

@@ -7,14 +7,14 @@ templates:
name: "Create artifacts directory"
command: mkdir /tmp/artifacts
- restore_cache:
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: mkdir -p /tmp/artifacts/logs
- run: npm run build
@@ -30,14 +30,14 @@ templates:
name: "Create artifacts directory"
command: mkdir /tmp/artifacts
- restore_cache:
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
- run: mkdir -p /tmp/artifacts/logs
- run: npm run build
@@ -60,18 +60,34 @@ jobs:
- image: selenium/standalone-chrome:3.8.1
working_directory: ~/repo-linux-node-v8
steps: *wdio-steps
build-linux-node-v9:
build-linux-node-v10:
docker:
- image: node:9
working_directory: ~/repo-linux-node-v9
- image: node:10
working_directory: ~/repo-linux-node-v10
steps: *build-steps
build-osx-node-v9:
build-osx-node-v6:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@9
working_directory: ~/repo-linux-node-v9
- brew install node@6
working_directory: ~/repo-osx-node-v6
steps: *build-steps
build-osx-node-v8:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@8
working_directory: ~/repo-osx-node-v8
steps: *build-steps
build-osx-node-v10:
macos:
xcode: "9.0"
dependencies:
override:
- brew install node@10
working_directory: ~/repo-osx-node-v10
steps: *build-steps
workflows:
@@ -80,5 +96,8 @@ workflows:
jobs:
- build-linux-node-v6
- build-linux-node-v8
- build-linux-node-v9
- build-linux-node-v10
- build-osx-node-v6
- build-osx-node-v8
- build-osx-node-v10

View File

@@ -22,6 +22,11 @@ targeted at developers and map designers.
Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independance is an OSS map designer.
## 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!

614
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "maputnik",
"version": "1.2.0",
"version": "1.5.0-beta",
"description": "A MapboxGL visual style editor",
"main": "''",
"scripts": {
@@ -21,19 +21,20 @@
"license": "MIT",
"homepage": "https://github.com/maputnik/editor#readme",
"dependencies": {
"@mapbox/mapbox-gl-rtl-text": "^0.1.1",
"@mapbox/mapbox-gl-style-spec": "^11.1.1",
"@mapbox/mapbox-gl-rtl-text": "^0.2.0",
"@mapbox/mapbox-gl-style-spec": "^13.1.0",
"classnames": "^2.2.5",
"codemirror": "^5.36.0",
"codemirror": "^5.37.0",
"color": "^3.0.0",
"file-saver": "^1.3.8",
"github-api": "^3.0.0",
"jsonlint": "github:josdejong/jsonlint#85a19d7",
"lodash.capitalize": "^4.2.1",
"lodash.clamp": "^4.0.3",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.44.2",
"mapbox-gl": "^0.47.0",
"mapbox-gl-inspect": "^1.3.1",
"maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.1",
@@ -42,6 +43,8 @@
"prop-types": "^15.6.0",
"react": "^16.3.2",
"react-addons-pure-render-mixin": "^15.6.2",
"react-aria-menubutton": "^5.1.1",
"react-aria-modal": "^2.12.1",
"react-autocomplete": "^1.7.2",
"react-codemirror2": "^4.2.1",
"react-collapse": "^4.0.3",
@@ -64,7 +67,15 @@
"stylelint": {
"extends": "stylelint-config-recommended-scss",
"rules": {
"no-descending-specificity": null
"no-descending-specificity": null,
"media-feature-name-no-unknown": [
true,
{
"ignoreMediaFeatureNames": [
"prefers-reduced-motion"
]
}
]
}
},
"eslintConfig": {
@@ -91,7 +102,7 @@
}
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "7.1.4",
"babel-plugin-istanbul": "^4.1.6",
@@ -122,8 +133,8 @@
"istanbul-lib-coverage": "^1.2.0",
"json-loader": "^0.5.7",
"mkdirp": "^0.5.1",
"mocha": "^5.1.0",
"node-sass": "^4.8.3",
"mocha": "^5.1.1",
"node-sass": "^4.9.0",
"nsp": "^3.1.0",
"react-hot-loader": "^3.1.1",
"sass-loader": "^7.0.1",

View File

@@ -1,5 +1,9 @@
import React from 'react'
import Mousetrap from 'mousetrap'
import cloneDeep from 'lodash.clonedeep'
import clamp from 'lodash.clamp'
import {arrayMove} from 'react-sortable-hoc'
import url from 'url'
import MapboxGlMap from './map/MapboxGlMap'
import OpenLayers3Map from './map/OpenLayers3Map'
@@ -9,9 +13,16 @@ import Toolbar from './Toolbar'
import AppLayout from './AppLayout'
import MessagePanel from './MessagePanel'
import SettingsModal from './modals/SettingsModal'
import ExportModal from './modals/ExportModal'
import SourcesModal from './modals/SourcesModal'
import OpenModal from './modals/OpenModal'
import ShortcutsModal from './modals/ShortcutsModal'
import SurveyModal from './modals/SurveyModal'
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import style from '../libs/style.js'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import style from '../libs/style'
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
import { undoMessages, redoMessages } from '../libs/diffmessage'
import { loadDefaultStyle, StyleStore } from '../libs/stylestore'
@@ -21,9 +32,10 @@ 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 MapboxGl from 'mapbox-gl'
import mapboxUtil from 'mapbox-gl/src/util/mapbox'
import { normalizeSourceURL } from 'mapbox-gl/src/util/mapbox'
function updateRootSpec(spec, fieldName, newValues) {
@@ -47,6 +59,79 @@ export default class App extends React.Component {
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
})
const keyCodes = {
"esc": 27,
"?": 191,
"o": 79,
"e": 69,
"s": 83,
"d": 68,
"i": 73,
"m": 77,
}
const shortcuts = [
{
keyCode: keyCodes["?"],
handler: () => {
this.toggleModal("shortcuts");
}
},
{
keyCode: keyCodes["o"],
handler: () => {
this.toggleModal("open");
}
},
{
keyCode: keyCodes["e"],
handler: () => {
this.toggleModal("export");
}
},
{
keyCode: keyCodes["d"],
handler: () => {
this.toggleModal("sources");
}
},
{
keyCode: keyCodes["s"],
handler: () => {
this.toggleModal("settings");
}
},
{
keyCode: keyCodes["i"],
handler: () => {
this.changeInspectMode();
}
},
{
keyCode: keyCodes["m"],
handler: () => {
document.querySelector(".mapboxgl-canvas").focus();
}
},
]
document.body.addEventListener("keyup", (e) => {
if(e.keyCode === keyCodes["esc"]) {
e.target.blur();
document.body.focus();
}
else if(document.activeElement === document.body) {
const shortcut = shortcuts.find((shortcut) => {
return (shortcut.keyCode === e.keyCode)
})
if(shortcut) {
shortcut.handler(e);
}
}
})
const styleUrl = initialStyleUrl()
if(styleUrl) {
this.styleStore = new StyleStore()
@@ -71,6 +156,8 @@ export default class App extends React.Component {
Debug.set("maputnik", "styleStore", this.styleStore);
}
const queryObj = url.parse(window.location.href, true).query;
this.state = {
errors: [],
infos: [],
@@ -80,6 +167,19 @@ export default class App extends React.Component {
vectorLayers: {},
inspectModeEnabled: false,
spec: styleSpec.latest,
isOpen: {
settings: false,
sources: false,
open: false,
shortcuts: false,
export: false,
survey: localStorage.hasOwnProperty('survey') ? false : true
},
mapOptions: {
showTileBoundaries: queryUtil.asBool(queryObj, "show-tile-boundaries"),
showCollisionBoxes: queryUtil.asBool(queryObj, "show-collision-boxes")
},
mapFilter: queryObj["color-blindness-emulation"],
}
this.layerWatcher = new LayerWatcher({
@@ -164,6 +264,24 @@ export default class App extends React.Component {
})
}
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,
@@ -172,6 +290,40 @@ export default class App extends React.Component {
this.onStyleChanged(changedStyle)
}
onLayerDestroy(layerId) {
let layers = this.state.mapStyle.layers;
const remainingLayers = layers.slice(0);
const idx = style.indexOfLayer(remainingLayers, layerId)
remainingLayers.splice(idx, 1);
this.onLayersChange(remainingLayers);
}
onLayerCopy(layerId) {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const clonedLayer = cloneDeep(changedLayers[idx])
clonedLayer.id = clonedLayer.id + "-copy"
changedLayers.splice(idx, 0, clonedLayer)
this.onLayersChange(changedLayers)
}
onLayerVisibilityToggle(layerId) {
let layers = this.state.mapStyle.layers;
const changedLayers = layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const layer = { ...changedLayers[idx] }
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
layer.layout = changedLayout
changedLayers[idx] = layer
this.onLayersChange(changedLayers)
}
onLayerIdChange(oldId, newId) {
const changedLayers = this.state.mapStyle.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, oldId)
@@ -214,7 +366,7 @@ export default class App extends React.Component {
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) {
let url = val.url;
try {
url = mapboxUtil.normalizeSourceURL(url, MapboxGl.accessToken);
url = normalizeSourceURL(url, MapboxGl.accessToken);
} catch(err) {
console.warn("Failed to normalizeSourceURL: ", err);
}
@@ -256,7 +408,8 @@ export default class App extends React.Component {
mapRenderer() {
const mapProps = {
mapStyle: style.replaceAccessToken(this.state.mapStyle, {allowFallback: true}),
mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}),
options: this.state.mapOptions,
onDataChange: (e) => {
this.layerWatcher.analyzeMap(e.map)
this.fetchSources();
@@ -266,15 +419,26 @@ export default class App extends React.Component {
const metadata = this.state.mapStyle.metadata || {}
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
let mapElement;
// Check if OL3 code has been loaded?
if(renderer === 'ol3') {
return <OpenLayers3Map {...mapProps} />
mapElement = <OpenLayers3Map {...mapProps} />
} else {
return <MapboxGlMap {...mapProps}
mapElement = <MapboxGlMap {...mapProps}
inspectModeEnabled={this.state.inspectModeEnabled}
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
onLayerSelect={this.onLayerSelect.bind(this)} />
}
const elementStyle = {};
if(this.state.mapFilter) {
elementStyle.filter = `url('#${this.state.mapFilter}')`;
}
return <div style={elementStyle}>
{mapElement}
</div>
}
onLayerSelect(layerId) {
@@ -282,6 +446,19 @@ export default class App extends React.Component {
this.setState({ selectedLayerIndex: idx })
}
toggleModal(modalName) {
this.setState({
isOpen: {
...this.state.isOpen,
[modalName]: !this.state.isOpen[modalName]
}
})
if(modalName === 'survey') {
localStorage.setItem('survey', '');
}
}
render() {
const layers = this.state.mapStyle.layers || []
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
@@ -294,9 +471,14 @@ export default class App extends React.Component {
onStyleChanged={this.onStyleChanged.bind(this)}
onStyleOpen={this.onStyleChanged.bind(this)}
onInspectModeToggle={this.changeInspectMode.bind(this)}
onToggleModal={this.toggleModal.bind(this)}
/>
const layerList = <LayerList
onMoveLayer={this.onMoveLayer.bind(this)}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayersChange={this.onLayersChange.bind(this)}
onLayerSelect={this.onLayerSelect.bind(this)}
selectedLayerIndex={this.state.selectedLayerIndex}
@@ -306,10 +488,17 @@ export default class App extends React.Component {
const layerEditor = selectedLayer ? <LayerEditor
layer={selectedLayer}
layerIndex={this.state.selectedLayerIndex}
isFirstLayer={this.state.selectedLayerIndex < 1}
isLastLayer={this.state.selectedLayerIndex === this.state.mapStyle.layers.length-1}
sources={this.state.sources}
vectorLayers={this.state.vectorLayers}
spec={this.state.spec}
onMoveLayer={this.onMoveLayer.bind(this)}
onLayerChanged={this.onLayerChanged.bind(this)}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayerIdChange={this.onLayerIdChange.bind(this)}
/> : null
@@ -318,12 +507,48 @@ export default class App extends React.Component {
infos={this.state.infos}
/> : null
const modals = <div>
<ShortcutsModal
isOpen={this.state.isOpen.shortcuts}
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
/>
<SettingsModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged.bind(this)}
isOpen={this.state.isOpen.settings}
onOpenToggle={this.toggleModal.bind(this, 'settings')}
/>
<ExportModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged.bind(this)}
isOpen={this.state.isOpen.export}
onOpenToggle={this.toggleModal.bind(this, 'export')}
/>
<OpenModal
isOpen={this.state.isOpen.open}
onStyleOpen={this.onStyleChanged.bind(this)}
onOpenToggle={this.toggleModal.bind(this, 'open')}
/>
<SourcesModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged.bind(this)}
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

@@ -9,6 +9,7 @@ class AppLayout extends React.Component {
layerEditor: PropTypes.element,
map: PropTypes.element.isRequired,
bottom: PropTypes.element,
modals: PropTypes.node,
}
static childContextTypes = {
@@ -39,6 +40,7 @@ class AppLayout extends React.Component {
{this.props.bottom}
</div>
}
{this.props.modals}
</div>
}
}

View File

@@ -5,6 +5,7 @@ import classnames from 'classnames'
class Button extends React.Component {
static propTypes = {
"data-wd-key": PropTypes.string,
"aria-label": PropTypes.string,
onClick: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string,
@@ -12,13 +13,14 @@ class Button extends React.Component {
}
render() {
return <a
return <button
onClick={this.props.onClick}
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

@@ -16,12 +16,9 @@ import MdInsertEmoticon from 'react-icons/lib/md/insert-emoticon'
import MdFontDownload from 'react-icons/lib/md/font-download'
import HelpIcon from 'react-icons/lib/md/help-outline'
import InspectionIcon from 'react-icons/lib/md/find-in-page'
import SurveyIcon from 'react-icons/lib/md/assignment-turned-in'
import logoImage from 'maputnik-design/logos/logo-color.svg'
import SettingsModal from './modals/SettingsModal'
import ExportModal from './modals/ExportModal'
import SourcesModal from './modals/SourcesModal'
import OpenModal from './modals/OpenModal'
import pkgJson from '../../package.json'
import style from '../libs/style'
@@ -41,6 +38,7 @@ class ToolbarLink extends React.Component {
className: PropTypes.string,
children: PropTypes.node,
href: PropTypes.string,
onToggleModal: PropTypes.func,
}
render() {
@@ -55,6 +53,28 @@ class ToolbarLink extends React.Component {
}
}
class ToolbarLinkHighlighted extends React.Component {
static propTypes = {
className: PropTypes.string,
children: PropTypes.node,
href: PropTypes.string,
onToggleModal: PropTypes.func
}
render() {
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 ToolbarAction extends React.Component {
static propTypes = {
children: PropTypes.node,
@@ -63,13 +83,13 @@ class ToolbarAction extends React.Component {
}
render() {
return <a
return <button
className='maputnik-toolbar-action'
data-wd-key={this.props.wdKey}
onClick={this.props.onClick}
>
{this.props.children}
</a>
</button>
}
}
@@ -83,7 +103,8 @@ export default class Toolbar extends React.Component {
// A dict of source id's and the available source layers
sources: PropTypes.object.isRequired,
onInspectModeToggle: PropTypes.func.isRequired,
children: PropTypes.node
children: PropTypes.node,
onToggleModal: PropTypes.func,
}
constructor(props) {
@@ -99,64 +120,41 @@ export default class Toolbar extends React.Component {
}
}
toggleModal(modalName) {
this.setState({
isOpen: {
...this.state.isOpen,
[modalName]: !this.state.isOpen[modalName]
}
})
}
render() {
return <div className='maputnik-toolbar'>
<SettingsModal
mapStyle={this.props.mapStyle}
onStyleChanged={this.props.onStyleChanged}
isOpen={this.state.isOpen.settings}
onOpenToggle={this.toggleModal.bind(this, 'settings')}
/>
<ExportModal
mapStyle={this.props.mapStyle}
onStyleChanged={this.props.onStyleChanged}
isOpen={this.state.isOpen.export}
onOpenToggle={this.toggleModal.bind(this, 'export')}
/>
<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')}
/>
<div className="maputnik-toolbar__inner">
<ToolbarLink
href={"https://github.com/maputnik/editor"}
className="maputnik-toolbar-logo"
<div
className="maputnik-toolbar-logo-container"
>
<img src={logoImage} alt="Maputnik" />
<h1>Maputnik
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
</h1>
</ToolbarLink>
<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"
>
<img src={logoImage} alt="Maputnik" />
<h1>Maputnik
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
</h1>
</a>
</div>
<div className="maputnik-toolbar__actions">
<ToolbarAction wdKey="nav:open" onClick={this.toggleModal.bind(this, 'open')}>
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
<OpenIcon />
<IconText>Open</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:export" onClick={this.toggleModal.bind(this, 'export')}>
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
<MdFileDownload />
<IconText>Export</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:sources" onClick={this.toggleModal.bind(this, 'sources')}>
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
<SourcesIcon />
<IconText>Sources</IconText>
<IconText>Data Sources</IconText>
</ToolbarAction>
<ToolbarAction wdKey="nav:settings" onClick={this.toggleModal.bind(this, 'settings')}>
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
<SettingsIcon />
<IconText>Style Settings</IconText>
</ToolbarAction>
@@ -171,6 +169,10 @@ export default class Toolbar extends React.Component {
<HelpIcon />
<IconText>Help</IconText>
</ToolbarLink>
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
<SurveyIcon />
<IconText>Take the Maputnik Survey</IconText>
</ToolbarLinkHighlighted>
</div>
</div>
</div>

View File

@@ -105,6 +105,7 @@ class ColorField extends React.Component {
{this.state.pickerOpened && picker}
<div className="maputnik-color-swatch" style={swatchStyle}></div>
<input
spellCheck="false"
className="maputnik-color"
ref={(input) => this.colorInput = input}
onClick={this.togglePicker.bind(this)}

View File

@@ -16,7 +16,7 @@ export default class FunctionButtons extends React.Component {
render() {
let makeZoomButton, makeDataButton
if (this.props.fieldSpec['zoom-function']) {
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
makeZoomButton = <Button
className="maputnik-make-zoom-function"
onClick={this.props.onZoomClick}

View File

@@ -37,7 +37,7 @@ export default class ZoomProperty extends React.Component {
}
}
componentWillMount() {
componentDidMount() {
this.setState({
refs: this.setStopRefs(this.props)
})
@@ -66,7 +66,7 @@ export default class ZoomProperty extends React.Component {
return newRefs;
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
const newRefs = this.setStopRefs(nextProps);
if(newRefs) {
this.setState({

View File

@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { combiningFilterOps } from '../../libs/filterops.js'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import DocLabel from '../fields/DocLabel'
import SelectInput from '../inputs/SelectInput'
import SingleFilterEditor from './SingleFilterEditor'

View File

@@ -63,7 +63,8 @@ class AutocompleteInput extends React.Component {
style: null
}}
inputProps={{
className: "maputnik-string"
className: "maputnik-string",
spellCheck: false
}}
value={this.props.value}
items={this.props.options}

View File

@@ -17,7 +17,7 @@ class NumberInput extends React.Component {
}
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.value })
}
@@ -67,6 +67,7 @@ class NumberInput extends React.Component {
render() {
return <input
spellCheck="false"
className="maputnik-number"
placeholder={this.props.default}
value={this.state.value}

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
class SelectInput extends React.Component {
static propTypes = {
value: PropTypes.string.isRequired,
"data-wd-key": PropTypes.string.isRequired,
"data-wd-key": PropTypes.string,
options: PropTypes.array.isRequired,
style: PropTypes.object,
onChange: PropTypes.func.isRequired,

View File

@@ -18,7 +18,7 @@ class StringInput extends React.Component {
}
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.value || '' })
}
@@ -42,6 +42,7 @@ class StringInput extends React.Component {
return React.createElement(tag, {
"data-wd-key": this.props["data-wd-key"],
spellCheck: !(tag === "input"),
className: classes.join(" "),
style: this.props.style,
value: this.state.value,

View File

@@ -0,0 +1,30 @@
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
}
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

@@ -29,7 +29,7 @@ class JSONEditor extends React.Component {
}
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
code: JSON.stringify(nextProps.layer, null, 2)
})

View File

@@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
import JSONEditor from './JSONEditor'
import FilterEditor from '../filter/FilterEditor'
@@ -13,6 +14,8 @@ import CommentBlock from './CommentBlock'
import LayerSourceBlock from './LayerSourceBlock'
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
import MoreVertIcon from 'react-icons/lib/md/more-vert'
import InputBlock from '../inputs/InputBlock'
import MultiButtonInput from '../inputs/MultiButtonInput'
@@ -45,6 +48,13 @@ export default class LayerEditor extends React.Component {
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,
}
static defaultProps = {
@@ -69,7 +79,7 @@ export default class LayerEditor extends React.Component {
this.state = { editorGroups }
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
const additionalGroups = { ...this.state.editorGroups }
layout[nextProps.layer.type].groups.forEach(group => {
@@ -176,6 +186,13 @@ export default class LayerEditor extends React.Component {
}
}
moveLayer(offset) {
this.props.onMoveLayer({
oldIndex: this.props.layerIndex,
newIndex: this.props.layerIndex+offset
})
}
render() {
const layerType = this.props.layer.type
const groups = layoutGroups(layerType).filter(group => {
@@ -192,8 +209,73 @@ export default class LayerEditor extends React.Component {
</LayerEditorGroup>
})
const layout = this.props.layer.layout || {}
const items = {
delete: {
text: "Delete",
handler: () => this.props.onLayerDestroy(this.props.layer.id)
},
duplicate: {
text: "Duplicate",
handler: () => this.props.onLayerCopy(this.props.layer.id)
},
hide: {
text: (layout.visibility === "none") ? "Show" : "Hide",
handler: () => this.props.onLayerVisibilityToggle(this.props.layer.id)
},
moveLayerUp: {
text: "Move layer up",
// Not actually used...
disabled: this.props.isFirstLayer,
handler: () => this.moveLayer(-1)
},
moveLayerDown: {
text: "Move layer down",
// Not actually used...
disabled: this.props.isLastLayer,
handler: () => this.moveLayer(+1)
}
}
function handleSelection(id, event) {
event.stopPropagation;
items[id].handler();
}
return <div className="maputnik-layer-editor"
>
<header>
<div className="layer-header">
<h2 className="layer-header__title">
Layer: {this.props.layer.id}
</h2>
<div className="layer-header__info">
<Wrapper
className='more-menu'
onSelection={handleSelection}
closeOnSelection={false}
>
<Button className='more-menu__button'>
<MoreVertIcon className="more-menu__button__svg" />
</Button>
<Menu>
<ul className="more-menu__menu">
{Object.keys(items).map((id, idx) => {
const item = items[id];
return <li key={id}>
<MenuItem value={id} className='more-menu__menu__item'>
{item.text}
</MenuItem>
</li>
})}
</ul>
</Menu>
</Wrapper>
</div>
</div>
</header>
{groups}
</div>
}

View File

@@ -1,7 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import Collapse from 'react-collapse'
import Collapser from './Collapser'
import Collapse from './Collapse'
export default class LayerEditorGroup extends React.Component {
static propTypes = {
@@ -22,7 +23,7 @@ export default class LayerEditorGroup extends React.Component {
<span style={{flexGrow: 1}} />
<Collapser isCollapsed={this.props.isActive} />
</div>
<Collapse isOpened={this.props.isActive}>
<Collapse isActive={this.props.isActive}>
<div className="react-collapse-container">
{this.props.children}
</div>

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'

View File

@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import cloneDeep from 'lodash.clonedeep'
import Button from '../Button'
import LayerListGroup from './LayerListGroup'
@@ -10,7 +9,7 @@ import AddIcon from 'react-icons/lib/md/add-circle-outline'
import AddModal from '../modals/AddModal'
import style from '../../libs/style.js'
import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
import {SortableContainer, SortableHandle} from 'react-sortable-hoc';
const layerListPropTypes = {
layers: PropTypes.array.isRequired,
@@ -57,36 +56,6 @@ class LayerListContainer extends React.Component {
}
}
onLayerDestroy(layerId) {
const remainingLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(remainingLayers, layerId)
remainingLayers.splice(idx, 1);
this.props.onLayersChange(remainingLayers)
}
onLayerCopy(layerId) {
const changedLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const clonedLayer = cloneDeep(changedLayers[idx])
clonedLayer.id = clonedLayer.id + "-copy"
changedLayers.splice(idx, 0, clonedLayer)
this.props.onLayersChange(changedLayers)
}
onLayerVisibilityToggle(layerId) {
const changedLayers = this.props.layers.slice(0)
const idx = style.indexOfLayer(changedLayers, layerId)
const layer = { ...changedLayers[idx] }
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
layer.layout = changedLayout
changedLayers[idx] = layer
this.props.onLayersChange(changedLayers)
}
toggleModal(modalName) {
this.setState({
isOpen: {
@@ -186,9 +155,9 @@ class LayerListContainer extends React.Component {
visibility={(layer.layout || {}).visibility}
isSelected={idx === this.props.selectedLayerIndex}
onLayerSelect={this.props.onLayerSelect}
onLayerDestroy={this.onLayerDestroy.bind(this)}
onLayerCopy={this.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
onLayerDestroy={this.props.onLayerDestroy.bind(this)}
onLayerCopy={this.props.onLayerCopy.bind(this)}
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
/>
listItems.push(listItem)
idx += 1
@@ -208,21 +177,22 @@ class LayerListContainer extends React.Component {
<span className="maputnik-space" />
<div className="maputnik-default-property">
<div className="maputnik-multibutton">
<a
<button
id="skip-menu"
onClick={this.toggleLayers.bind(this)}
className="maputnik-button">
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
</a>
</button>
</div>
</div>
<div className="maputnik-default-property">
<div className="maputnik-multibutton">
<a
<button
onClick={this.toggleModal.bind(this, 'add')}
data-wd-key="layer-list:add-layer"
className="maputnik-button maputnik-button-selected">
Add Layer
</a>
</button>
</div>
</div>
</header>
@@ -236,18 +206,10 @@ class LayerListContainer extends React.Component {
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
{...this.props}
onSortEnd={this.onSortEnd.bind(this)}
onSortEnd={this.props.onMoveLayer.bind(this)}
useDragHandle={true}
/>
}

View File

@@ -38,7 +38,7 @@ class IconAction extends React.Component {
renderIcon() {
switch(this.props.action) {
case 'copy': return <CopyIcon />
case 'duplicate': return <CopyIcon />
case 'show': return <VisibilityIcon />
case 'hide': return <VisibilityOffIcon />
case 'delete': return <DeleteIcon />
@@ -46,13 +46,15 @@ class IconAction extends React.Component {
}
render() {
return <a
return <button
tabIndex="-1"
title={this.props.action}
className="maputnik-layer-list-icon-action"
data-wd-key={this.props.wdKey}
onClick={this.props.onClick}
>
{this.renderIcon()}
</a>
</button>
}
}
@@ -109,7 +111,7 @@ class LayerListItem extends React.Component {
/>
<IconAction
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
action={'copy'}
action={'duplicate'}
onClick={e => this.props.onLayerCopy(this.props.layerId)}
/>
<IconAction

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import AutocompleteInput from '../inputs/AutocompleteInput'

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import AutocompleteInput from '../inputs/AutocompleteInput'

View File

@@ -1,14 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import SelectInput from '../inputs/SelectInput'
class LayerTypeBlock extends React.Component {
static propTypes = {
value: PropTypes.string.isRequired,
wdKey: PropTypes.string.isRequired,
wdKey: PropTypes.string,
onChange: PropTypes.func.isRequired,
}

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput'

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput'

View File

@@ -58,6 +58,7 @@ export default class MapboxGlMap extends React.Component {
mapStyle: PropTypes.object.isRequired,
inspectModeEnabled: PropTypes.bool.isRequired,
highlightedLayer: PropTypes.object,
options: PropTypes.object,
}
static defaultProps = {
@@ -65,6 +66,7 @@ export default class MapboxGlMap extends React.Component {
onDataChange: () => {},
onLayerSelect: () => {},
mapboxAccessToken: tokens.mapbox,
options: {},
}
constructor(props) {
@@ -79,7 +81,7 @@ export default class MapboxGlMap extends React.Component {
}
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
if(!this.state.map) return
const metadata = nextProps.mapStyle.metadata || {}
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
@@ -92,20 +94,31 @@ export default class MapboxGlMap extends React.Component {
}
componentDidUpdate(prevProps) {
const map = this.state.map;
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
this.state.inspect.toggleInspector()
}
if(this.props.inspectModeEnabled) {
this.state.inspect.render()
}
map.showTileBoundaries = this.props.options.showTileBoundaries;
map.showCollisionBoxes = this.props.options.showCollisionBoxes;
}
componentDidMount() {
const map = new MapboxGl.Map({
const mapOpts = {
...this.props.options,
container: this.container,
style: this.props.mapStyle,
hash: true,
})
}
const map = new MapboxGl.Map(mapOpts);
map.showTileBoundaries = mapOpts.showTileBoundaries;
map.showCollisionBoxes = mapOpts.showCollisionBoxes;
const zoom = new ZoomControl;
map.addControl(zoom, 'top-right');

View File

@@ -29,7 +29,7 @@ class OpenLayers3Map extends React.Component {
const styleFunc = olms.apply(this.map, newMapStyle)
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
require.ensure(["ol", "ol-mapbox-style"], () => {
if(!this.map) return
this.updateStyle(nextProps.mapStyle)

View File

@@ -55,7 +55,7 @@ class AddModal extends React.Component {
}
}
componentWillUpdate(nextProps, nextState) {
UNSAFE_componentWillUpdate(nextProps, nextState) {
// Check if source is valid for new type
const oldType = this.state.type;
const newType = nextState.type;

View File

@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { saveAs } from 'file-saver'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import CheckboxInput from '../inputs/CheckboxInput'
@@ -10,7 +10,7 @@ import Button from '../Button'
import Modal from './Modal'
import MdFileDownload from 'react-icons/lib/md/file-download'
import TiClipboard from 'react-icons/lib/ti/clipboard'
import style from '../../libs/style.js'
import style from '../../libs/style'
import GitHub from 'github-api'
import { CopyToClipboard } from 'react-copy-to-clipboard'
@@ -31,7 +31,7 @@ class Gist extends React.Component {
}
}
componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
...this.state,
preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
@@ -49,7 +49,7 @@ class Gist extends React.Component {
const mapboxToken = (this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token'];
const mapStyleStr = preview ?
styleSpec.format(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
styleSpec.format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle))) :
styleSpec.format(stripAccessTokens(this.props.mapStyle));
const styleTitle = this.props.mapStyle.name || 'Style';
const htmlStr = `
@@ -235,10 +235,24 @@ class ExportModal extends React.Component {
}
downloadStyle() {
const blob = new Blob([styleSpec.format(stripAccessTokens(this.props.mapStyle))], {type: "application/json;charset=utf-8"});
const tokenStyle = styleSpec.format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle)));
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
saveAs(blob, this.props.mapStyle.id + ".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"
@@ -252,6 +266,28 @@ class ExportModal extends React.Component {
<p>
Download a JSON style to your computer.
</p>
<p>
<InputBlock label={"OpenMapTiles Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
</InputBlock>
<InputBlock label={"Mapbox Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/>
</InputBlock>
<InputBlock label={"Thunderforest Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
/>
</InputBlock>
</p>
<Button onClick={this.downloadStyle.bind(this)}>
<MdFileDownload />
Download

View File

@@ -0,0 +1,49 @@
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,
}
constructor(props) {
super(props);
}
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,7 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import CloseIcon from 'react-icons/lib/md/close'
import Overlay from './Overlay'
import AriaModal from 'react-aria-modal'
class Modal extends React.Component {
static propTypes = {
@@ -10,28 +11,51 @@ class Modal extends React.Component {
title: PropTypes.string.isRequired,
onOpenToggle: PropTypes.func.isRequired,
children: PropTypes.node,
underlayClickExits: PropTypes.bool,
underlayProps: PropTypes.object,
}
static defaultProps = {
underlayClickExits: true
}
getApplicationNode() {
return document.getElementById('app');
}
render() {
return <Overlay isOpen={this.props.isOpen}>
<div className="maputnik-modal"
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.props.onOpenToggle(false)}
>
<header className="maputnik-modal-header">
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
<span className="maputnik-modal-header-space"></span>
<a className="maputnik-modal-header-toggle"
onClick={() => this.props.onOpenToggle(false)}
data-wd-key={this.props["data-wd-key"]+".close-modal"}
>
<CloseIcon />
</a>
</header>
<div className="maputnik-modal-scroller">
<div className="maputnik-modal-content">{this.props.children}</div>
<div className="maputnik-modal"
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.props.onOpenToggle(false)}
data-wd-key={this.props["data-wd-key"]+".close-modal"}
>
<CloseIcon />
</button>
</header>
<div className="maputnik-modal-scroller">
<div className="maputnik-modal-content">{this.props.children}</div>
</div>
</div>
</div>
</Overlay>
</AriaModal>
}
else {
return false;
}
}
}

View File

@@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import LoadingModal from './LoadingModal'
import Modal from './Modal'
import Button from '../Button'
import FileReaderInput from 'react-file-reader-input'
@@ -23,6 +24,7 @@ class PublicStyle extends React.Component {
return <div className="maputnik-public-style">
<Button
className="maputnik-public-style-button"
aria-label={this.props.title}
onClick={() => this.props.onSelect(this.props.url)}
>
<header className="maputnik-public-style-header">
@@ -30,11 +32,12 @@ class PublicStyle extends React.Component {
<span className="maputnik-space" />
<AddIcon />
</header>
<img
<div
className="maputnik-public-style-thumbnail"
src={this.props.thumbnailUrl}
alt={this.props.title}
/>
style={{
backgroundImage: `url(${this.props.thumbnailUrl})`
}}
></div>
</Button>
</div>
}
@@ -58,13 +61,33 @@ class OpenModal extends React.Component {
})
}
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();
request({
const reqOpts = {
url: styleUrl,
withCredentials: false,
}, (error, response, body) => {
}
const activeRequest = request(reqOpts, (error, response, body) => {
this.setState({
activeRequest: null,
activeRequestUrl: null
});
if (!error && response.statusCode == 200) {
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
console.log('Loaded style ', mapStyle.id)
@@ -74,6 +97,11 @@ class OpenModal extends React.Component {
console.warn('Could not open the style URL', styleUrl)
}
})
this.setState({
activeRequest: activeRequest,
activeRequestUrl: reqOpts.url
})
}
onOpenUrl() {
@@ -142,7 +170,7 @@ class OpenModal extends React.Component {
<section className="maputnik-modal-section">
<h2>Upload Style</h2>
<p>Upload a JSON style from your computer.</p>
<FileReaderInput onChange={this.onUpload.bind(this)}>
<FileReaderInput onChange={this.onUpload.bind(this)} tabIndex="-1">
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
</FileReaderInput>
</section>
@@ -167,6 +195,13 @@ class OpenModal extends React.Component {
{styleOptions}
</div>
</section>
<LoadingModal
isOpen={!!this.state.activeRequest}
title={'Loading style'}
onCancel={(e) => this.onCancelActiveRequest(e)}
message={"Loading: "+this.state.activeRequestUrl}
/>
</Modal>
}
}

View File

@@ -1,24 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
class Overlay extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
children: PropTypes.element.isRequired
}
render() {
let overlayStyle = {}
if(!this.props.isOpen) {
overlayStyle['display'] = 'none';
}
return <div className={"maputnik-overlay"} style={overlayStyle}>
<div className={"maputnik-overlay-viewport"} />
{this.props.children}
</div>
}
}
export default Overlay

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput'
@@ -94,6 +94,14 @@ class SettingsModal extends React.Component {
/>
</InputBlock>
<InputBlock label={"Thunderforest Access Token"} doc={"Public access token for Thunderforest services."}>
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:thunderforest_access_token"
value={metadata['maputnik:thunderforest_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
/>
</InputBlock>
<InputBlock label={"Style Renderer"} doc={"Choose the default Maputnik renderer for this style."}>
<SelectInput {...inputProps}
data-wd-key="modal-settings.maputnik:renderer"

View File

@@ -0,0 +1,73 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
class ShortcutsModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
}
render() {
const help = [
{
key: "?",
text: "Shortcuts menu"
},
{
key: "o",
text: "Open modal"
},
{
key: "e",
text: "Export modal"
},
{
key: "d",
text: "Data Sources modal"
},
{
key: "s",
text: "Style Settings modal"
},
{
key: "i",
text: "Toggle inspect"
},
{
key: "m",
text: "Focus map"
},
]
return <Modal
data-wd-key="shortcuts-modal"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title={'Shortcuts'}
>
<div className="maputnik-modal-section maputnik-modal-shortcuts">
<p>
Press <code>ESC</code> to lose focus of any active elements, then press one of:
</p>
<ul>
{help.map((item) => {
return <li key={item.key}>
<code>{item.key}</code> {item.text}
</li>
})}
</ul>
</div>
</Modal>
}
}
export default ShortcutsModal

View File

@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import Modal from './Modal'
import Button from '../Button'
import InputBlock from '../inputs/InputBlock'
@@ -236,9 +236,12 @@ class SourcesModal extends React.Component {
<p>
Add one of the publicly available sources to your style.
</p>
<div style={{maxwidth: 500}}>
<div className="maputnik-public-sources" style={{maxwidth: 500}}>
{tilesetOptions}
</div>
<p>
<strong>Note:</strong> Some of the tilesets are not optimised for online use, and as a result the file sizes of the tiles can be quite large (heavy) for online vector rendering. Please review any tilesets before use.
</p>
</div>
<div className="maputnik-modal-section">

View File

@@ -0,0 +1,41 @@
import React from 'react'
import PropTypes from 'prop-types'
import Button from '../Button'
import Modal from './Modal'
import logoImage from 'maputnik-design/logos/logo-color.svg'
class SurveyModal extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
constructor(props) { super(props); }
onClick = () => {
window.open('https://gregorywolanski.typeform.com/to/cPgaSY', '_blank');
this.props.onOpenToggle();
}
render() {
return <Modal
data-wd-key="modal-survey"
isOpen={this.props.isOpen}
onOpenToggle={this.props.onOpenToggle}
title="Maputnik Survey"
>
<div className="maputnik-modal-survey">
<img className="maputnik-modal-survey__logo" src={logoImage} alt="" width="128" />
<h1>You + Maputnik = Maputnik better for you</h1>
<p className="maputnik-modal-survey__description">We dont track you, so we dont know how you use Maputnik. Help us make Maputnik better for you by completing a 7minute survey carried out by our contributing designer.</p>
<Button onClick={this.onClick} className="maputnik-big-button maputnik-white-button maputnik-wide-button">Take the Maputnik Survey</Button>
<p className="maputnik-modal-survey__footnote">It takes 7 minutes, tops! Every question is optional.</p>
</div>
</Modal>
}
}
export default SurveyModal

View File

@@ -1,26 +1,32 @@
import React from 'react'
import PropTypes from 'prop-types'
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput'
import NumberInput from '../inputs/NumberInput'
import SelectInput from '../inputs/SelectInput'
class TileJSONSourceEditor extends React.Component {
static propTypes = {
source: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
children: PropTypes.node,
}
render() {
return <InputBlock label={"TileJSON URL"} doc={styleSpec.latest.source_vector.url.doc}>
<StringInput
value={this.props.source.url}
onChange={url => this.props.onChange({
...this.props.source,
url: url
})}
/>
</InputBlock>
return <div>
<InputBlock label={"TileJSON URL"} doc={styleSpec.latest.source_vector.url.doc}>
<StringInput
value={this.props.source.url}
onChange={url => this.props.onChange({
...this.props.source,
url: url
})}
/>
</InputBlock>
{this.props.children}
</div>
}
}
@@ -28,6 +34,7 @@ class TileURLSourceEditor extends React.Component {
static propTypes = {
source: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
children: PropTypes.node,
}
changeTileUrl(idx, value) {
@@ -73,6 +80,7 @@ class TileURLSourceEditor extends React.Component {
})}
/>
</InputBlock>
{this.props.children}
</div>
}
@@ -116,7 +124,18 @@ class SourceTypeEditor extends React.Component {
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps} />
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
<InputBlock label={"Encoding"} doc={styleSpec.latest.source_raster_dem.encoding.doc}>
<SelectInput
options={Object.keys(styleSpec.latest.source_raster_dem.encoding.values)}
onChange={encoding => this.props.onChange({
...this.props.source,
encoding: encoding
})}
value={this.props.source.encoding || styleSpec.latest.source_raster_dem.encoding.default}
/>
</InputBlock>
</TileURLSourceEditor>
default: return null
}
}

View File

@@ -193,7 +193,8 @@
"raster-brightness-max",
"raster-saturation",
"raster-contrast",
"raster-fade-duration"
"raster-fade-duration",
"raster-resampling"
]
}
]

View File

@@ -26,8 +26,8 @@
{
"id": "osm-liberty",
"title": "OSM Liberty",
"url": "https://rawgit.com/lukasmartinelli/osm-liberty/gh-pages/style.json",
"thumbnail": "https://cdn.rawgit.com/lukasmartinelli/osm-liberty/gh-pages/thumbnail.png"
"url": "https://rawgit.com/maputnik/osm-liberty/gh-pages/style.json",
"thumbnail": "https://cdn.rawgit.com/maputnik/osm-liberty/gh-pages/thumbnail.png"
},
{
"id": "empty-style",

View File

@@ -8,5 +8,15 @@
"type": "vector",
"url": "https://free.tilehosting.com/data/v3.json?key={key}",
"title": "OpenMapTiles"
},
"thunderforest_transport": {
"type": "vector",
"url": "https://tile.thunderforest.com/thunderforest.transport-v1.json?apikey={key}",
"title": "Thunderforest Transport (heavy)"
},
"thunderforest_outdoors": {
"type": "vector",
"url": "https://tile.thunderforest.com/thunderforest.outdoors-v1.json?apikey={key}",
"title": "Thunderforest Outdoors (heavy)"
}
}

View File

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

12
src/libs/accessibility.js Normal file
View File

@@ -0,0 +1,12 @@
import lodash from 'lodash'
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
const reducedMotionEnabled = lodash.throttle(() => {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
}, 3000);
export default {
reducedMotionEnabled
}

View File

@@ -1,4 +1,4 @@
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
export function diffMessages(beforeStyle, afterStyle) {
const changes = styleSpec.diff(beforeStyle, afterStyle)

View File

@@ -1,4 +1,4 @@
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
export const combiningFilterOps = ['all', 'any', 'none']
export const setFilterOps = ['in', '!in']
export const otherFilterOps = Object

View File

@@ -1,4 +1,4 @@
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
export function changeType(layer, newType) {
const changedPaintProps = { ...layer.paint }

17
src/libs/query-util.js Normal file
View File

@@ -0,0 +1,17 @@
function asBool(queryObj, key) {
if(queryObj.hasOwnProperty(key)) {
if(queryObj[key].match(/^false|0$/)) {
return false;
}
else {
return true;
}
}
else {
return false;
}
}
module.exports = {
asBool
}

View File

@@ -54,18 +54,28 @@ function indexOfLayer(layers, layerId) {
return null
}
function replaceAccessToken(mapStyle, opts={}) {
const omtSource = mapStyle.sources.openmaptiles
if(!omtSource) return mapStyle
if(!omtSource.hasOwnProperty("url")) return mapStyle
function getAccessToken(key, mapStyle, opts) {
if(key === "thunderforest_transport" || key === "thunderforest_outdoors") {
key = "thunderforest";
}
const metadata = mapStyle.metadata || {}
let accessToken = metadata['maputnik:openmaptiles_access_token'];
let accessToken = metadata['maputnik:'+key+'_access_token'];
if(opts.allowFallback && !accessToken) {
accessToken = tokens.openmaptiles;
accessToken = tokens[key];
}
return accessToken;
}
function replaceSourceAccessToken(mapStyle, key, opts={}) {
const source = mapStyle.sources[key]
if(!source) return mapStyle
if(!source.hasOwnProperty("url")) return mapStyle
const accessToken = getAccessToken(key, mapStyle, opts)
if(!accessToken) {
// Early exit.
return mapStyle;
@@ -73,24 +83,40 @@ function replaceAccessToken(mapStyle, opts={}) {
const changedSources = {
...mapStyle.sources,
openmaptiles: {
...omtSource,
url: omtSource.url.replace('{key}', accessToken)
[key]: {
...source,
url: source.url.replace('{key}', accessToken)
}
}
const changedStyle = {
...mapStyle,
glyphs: mapStyle.glyphs ? mapStyle.glyphs.replace('{key}', accessToken) : mapStyle.glyphs,
sources: changedSources
}
return changedStyle
}
function replaceAccessTokens(mapStyle, opts={}) {
let changedStyle = mapStyle;
Object.keys(mapStyle.sources).forEach((tokenKey) => {
changedStyle = replaceSourceAccessToken(changedStyle, tokenKey, opts);
})
if(mapStyle.glyphs && mapStyle.glyphs.match(/\.tileserver\.org/)) {
changedStyle = {
...changedStyle,
glyphs: mapStyle.glyphs ? mapStyle.glyphs.replace('{key}', getAccessToken("openmaptiles", mapStyle, opts)) : mapStyle.glyphs
}
}
return changedStyle
}
export default {
ensureStyleValidity,
emptyStyle,
indexOfLayer,
generateId,
replaceAccessToken,
replaceAccessTokens,
}

View File

@@ -18,6 +18,11 @@ html {
box-sizing: border-box;
}
body {
// The UI is 100% height so prevent bounce scroll on OSX
overflow: hidden;
}
*,
*::before,
*::after {

View File

@@ -5,7 +5,11 @@
right: 0;
bottom: 0;
height: calc(100% - #{$toolbar-height + $toolbar-offset});
width: 75%;
width: calc(
100%
- 200px /* layer list */
- 350px /* layer editor */
);
}
// DOC LABEL
@@ -53,8 +57,10 @@
font-size: $font-size-6;
padding: $margin-2;
user-select: none;
border-width: 0;
border-radius: 2px;
box-sizing: border-box;
text-decoration: none;
&:hover {
background-color: lighten($color-midgray, 12);
@@ -69,6 +75,20 @@
font-size: $font-size-5;
}
.maputnik-wide-button {
padding: $margin-2 $margin-3;
}
.maputnik-green-button {
background-color: $color-green;
color: $color-black;
}
.maputnik-white-button {
background-color: $color-white;
color: $color-black;
}
.maputnik-icon-button {
background-color: transparent;

View File

@@ -41,7 +41,7 @@
.maputnik-color-swatch {
height: 26px;
width: 3px;
width: 14px;
flex-shrink: 0;
flex-grow: 0;
}
@@ -91,7 +91,6 @@
.maputnik-button-selected {
background-color: lighten($color-midgray, 12);
outline: 1px $color-white;
color: white;
}
@@ -126,6 +125,10 @@
border-width: 2px;
border-color: $color-gray;
transition: background-color 0.1s ease-out;
@media screen and (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
&-icon {

View File

@@ -43,17 +43,32 @@
-webkit-transition: opacity 600ms, visibility 600ms;
transition: opacity 600ms, visibility 600ms;
@media screen and (prefers-reduced-motion: reduce) {
transition-duration: 0;
}
@include flex-row;
}
&-icon-action svg {
fill: $color-black;
&-icon-action {
display: none;
svg {
fill: $color-black;
}
}
.maputnik-layer-list-item:hover,
.maputnik-layer-list-item-selected {
background-color: lighten($color-black, 2);
.maputnik-layer-list-icon-action {
display: block;
background: initial;
border: none;
padding: 0 2px;
}
.maputnik-layer-list-icon-action svg {
fill: darken($color-lowgray, 0.5);
@@ -122,6 +137,7 @@
user-select: none;
padding: $margin-2;
line-height: 20px;
border-top: solid 1px #36383e;
@include flex-row;
@@ -164,3 +180,41 @@
color: $color-lowgray;
}
}
.more-menu {
position: relative;
&__menu {
position: absolute;
z-index: 9999;
background: $color-black;
border: solid 1px $color-midgray;
right: 0;
min-width: 120px;
}
&__button__svg {
width: 24px;
height: 24px;
}
&__menu__item {
padding: 4px;
}
}
.layer-header {
display: flex;
padding: 6px;
background: $color-black;
&__title {
flex: 1;
margin: 0;
line-height: 24px;
}
&__info {
min-width: 28px;
}
}

View File

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

View File

@@ -7,6 +7,7 @@
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
z-index: 3;
position: relative;
font-family: $font-family;
}
.maputnik-modal-section {
@@ -42,7 +43,10 @@
}
.maputnik-modal-header-toggle {
cursor: pointer;
border: none;
background: initial;
color: white;
padding: 0;
}
.maputnik-modal-scroller {
@@ -60,31 +64,6 @@
@extend .maputnik-space;
}
//OVERLAY
.maputnik-overlay-viewport {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
opacity: 0.875;
background-color: rgb(28, 31, 36);
}
.maputnik-overlay {
top: 0;
right: 0;
bottom: 0;
left: 0;
position: fixed;
align-items: center;
justify-content: center;
z-index: 9;
@include flex-row;
}
//OPEN MODAL
.maputnik-upload-button {
@extend .maputnik-big-button;
@@ -109,6 +88,7 @@
background-color: $color-gray;
padding: $margin-3;
display: block;
width: 100%;
&:hover {
background-color: $color-midgray;
@@ -123,6 +103,9 @@
display: block;
margin-top: $margin-2;
width: 100%;
padding-top: calc(400 / 600 * 100%);
background-size: cover;
background-color: $color-midgray;
}
.maputnik-add-layer {
@@ -142,6 +125,10 @@
}
//SOURCE MODAL
.maputnik-public-sources {
margin-bottom: 1.5%;
}
.maputnik-public-source {
vertical-align: top;
margin-top: 1.5%;
@@ -156,6 +143,7 @@
font-size: $font-size-5;
color: $color-lowgray;
background-color: transparent;
width: 100%;
@include flex-row;
}
@@ -166,6 +154,7 @@
.maputnik-public-source-id {
font-weight: 400;
text-align: left;
}
.maputnik-active-source-type-editor {
@@ -239,3 +228,38 @@
text-decoration: none;
color: #ef5350;
}
.maputnik-modal-shortcuts {
code {
color: white;
background: #3c3c3c;
padding: 2px 6px;
display: inline-block;
text-align: center;
border-radius: 2px;
margin-right: 4px;
font-family: monospace;
}
li {
margin-bottom: 4px;
}
}
.maputnik-modal-survey {
width: 372px;
}
.maputnik-modal-survey__logo {
display: block;
margin: 0 auto;
}
.maputnik-modal-survey__description {
line-height: 1.5;
}
.maputnik-modal-survey__footnote {
color: $color-green;
margin-top: 16px;
}

View File

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

View File

@@ -1,5 +1,9 @@
// See <https://github.com/nkbt/react-collapse/commit/4f4fbce7c6c07b082dc62062338c9294c656f9df>
.react-collapse-container {
position: relative;
overflow: hidden;
display: flex;
max-width: 100%;
> * {
flex: 1;
}
}

View File

@@ -9,16 +9,25 @@
background-color: $color-black;
}
.maputnik-toolbar-logo-container {
position: relative;
}
.maputnik-toolbar-logo {
text-decoration: none;
display: block;
flex: 0 0 180px;
width: 180px;
text-align: left;
background-color: $color-black;
padding: $margin-2;
height: $toolbar-height;
position: relative;
overflow: hidden;
h1 {
display: inline;
line-height: 26px;
}
img {
@@ -48,15 +57,38 @@
}
}
.maputnik-toolbar-link--highlighted {
line-height: 1;
padding: $margin-2 $margin-3;
.maputnik-toolbar-link-wrapper {
background-color: $color-white;
border-radius: 2px;
padding: $margin-2;
margin-top: $margin-1;
color: $color-black;
display: block;
}
&:hover {
background-color: $color-black;
}
&:hover .maputnik-toolbar-link-wrapper {
background-color: lighten($color-midgray, 12);
color: $color-white;
}
}
.maputnik-toolbar-version {
position: absolute;
font-size: 10px;
bottom: -2px;
margin-left: 4px;
white-space: nowrap;
}
.maputnik-toolbar-action {
background: inherit;
border-width: 0;
@extend .maputnik-toolbar-link;
}
@@ -78,3 +110,23 @@
flex: 1;
overflow-y: auto;
}
.maputnik-toolbar-skip {
position: absolute;
overflow: hidden;
width: 0px;
height: 100%;
text-align: center;
display: block;
background-color: $color-black;
z-index: 999;
line-height: 40px;
left: 0;
top: 0;
&:active,
&:focus {
width: 100%;
}
}

View File

@@ -4,6 +4,7 @@ $color-midgray: #36383e;
$color-lowgray: #8e8e8e;
$color-white: #f0f0f0;
$color-red: #cf4a4a;
$color-green: #53b972;
$margin-1: 3px;
$margin-2: 5px;
$margin-3: 10px;
@@ -37,6 +38,7 @@ $toolbar-offset: 0;
@import 'popup';
@import 'map';
@import 'react-collapse';
@import 'react-codemirror';
/**
* Hacks for webdriverio isVisibleWithinViewport

View File

@@ -69,6 +69,87 @@
</style>
</head>
<body>
<!-- TODO: Import dynamically -->
<!-- From <https://github.com/hail2u/color-blindness-emulation> -->
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1">
<defs>
<filter id="protanopia">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.567, 0.433, 0, 0, 0
0.558, 0.442, 0, 0, 0
0, 0.242, 0.758, 0, 0
0, 0, 0, 1, 0"/>
</filter>
<filter id="protanomaly">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.817, 0.183, 0, 0, 0
0.333, 0.667, 0, 0, 0
0, 0.125, 0.875, 0, 0
0, 0, 0, 1, 0"/>
</filter>
<filter id="deuteranopia">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.625, 0.375, 0, 0, 0
0.7, 0.3, 0, 0, 0
0, 0.3, 0.7, 0, 0
0, 0, 0, 1, 0"/>
</filter>
<filter id="deuteranomaly">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.8, 0.2, 0, 0, 0
0.258, 0.742, 0, 0, 0
0, 0.142, 0.858, 0, 0
0, 0, 0, 1, 0"/>
</filter>
<filter id="tritanopia">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.95, 0.05, 0, 0, 0
0, 0.433, 0.567, 0, 0
0, 0.475, 0.525, 0, 0
0, 0, 0, 1, 0"/>
</filter>
<filter id="tritanomaly">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.967, 0.033, 0, 0, 0
0, 0.733, 0.267, 0, 0
0, 0.183, 0.817, 0, 0
0, 0, 0, 1, 0"/>
</filter>
<filter id="achromatopsia">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.299, 0.587, 0.114, 0, 0
0.299, 0.587, 0.114, 0, 0
0.299, 0.587, 0.114, 0, 0
0, 0, 0, 1, 0"/>
</filter>
<filter id="achromatomaly">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0.618, 0.320, 0.062, 0, 0
0.163, 0.775, 0.062, 0, 0
0.163, 0.320, 0.516, 0, 0
0, 0, 0, 1, 0"/>
</filter>
</defs>
</svg>
<div id="app">
<div id="loader">Loading...</div>
</div>

View File

@@ -13,6 +13,9 @@ describe('maputnik', function() {
"geojson:example",
"raster:raster"
]));
browser.execute(function() {
localStorage.setItem("survey", true);
});
browser.waitForExist(".maputnik-toolbar-link");
browser.flushReactUpdates();
});