mirror of
https://github.com/maputnik/editor.git
synced 2025-12-06 14:20:02 +00:00
Compare commits
109 Commits
v1.2.0-bet
...
v1.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc51774259 | ||
|
|
5a19245ee0 | ||
|
|
45f45b7547 | ||
|
|
530bfaf3b3 | ||
|
|
6ea70ab9cf | ||
|
|
a0e2d68dae | ||
|
|
1447e8bfb5 | ||
|
|
c0480a50ea | ||
|
|
09ba2be416 | ||
|
|
115ce3305d | ||
|
|
960b2022ed | ||
|
|
252b442ca9 | ||
|
|
03b9ddda9c | ||
|
|
968d7d7fda | ||
|
|
b211f1cd12 | ||
|
|
870d4349f4 | ||
|
|
d88bc59720 | ||
|
|
7c00775515 | ||
|
|
4b5536b282 | ||
|
|
fb84cfee1c | ||
|
|
9132262106 | ||
|
|
5de9e708e9 | ||
|
|
4df63c7287 | ||
|
|
a88ca031d0 | ||
|
|
452706f35c | ||
|
|
8b0aa194cf | ||
|
|
b9aa7e9206 | ||
|
|
e35f106482 | ||
|
|
b7a97cf8ee | ||
|
|
9208115981 | ||
|
|
afbdaecd0a | ||
|
|
558f3d649d | ||
|
|
417511d577 | ||
|
|
df350534ce | ||
|
|
7167235146 | ||
|
|
7a7f2eb7de | ||
|
|
cd28a53f6a | ||
|
|
1fe31ac0ec | ||
|
|
ffce8e3ba5 | ||
|
|
a28a417ebc | ||
|
|
6cdb56d13f | ||
|
|
0516e587b4 | ||
|
|
5b4063105b | ||
|
|
d9a5548762 | ||
|
|
cae6cffb7b | ||
|
|
ede782abed | ||
|
|
00afbad7ac | ||
|
|
edd09ef585 | ||
|
|
1e09066779 | ||
|
|
32edb48e16 | ||
|
|
b116eef147 | ||
|
|
74d1cd2d01 | ||
|
|
fd48d82e42 | ||
|
|
480d54c2d8 | ||
|
|
ab9c39b862 | ||
|
|
dd122d1bac | ||
|
|
f9f5e8f925 | ||
|
|
aa2f4a091c | ||
|
|
13fc699d4a | ||
|
|
f5e8d473ad | ||
|
|
35353d75f5 | ||
|
|
0f103c3c00 | ||
|
|
019428a241 | ||
|
|
6200edea25 | ||
|
|
fc7395df96 | ||
|
|
272f662a34 | ||
|
|
d59d9cde95 | ||
|
|
c71fbcf436 | ||
|
|
54c79445db | ||
|
|
a82ba26f86 | ||
|
|
28af87391d | ||
|
|
0aabd33538 | ||
|
|
bd9076c4ff | ||
|
|
1aed761893 | ||
|
|
a2a6f6dcab | ||
|
|
db5dd0f6ee | ||
|
|
42c3dcf258 | ||
|
|
51a115d65a | ||
|
|
fc0fbd6a37 | ||
|
|
d80d76724c | ||
|
|
77da0a6d30 | ||
|
|
79b251d8b9 | ||
|
|
4f19f6a08c | ||
|
|
d2a6eab1e6 | ||
|
|
c7cf051502 | ||
|
|
6e21503e6b | ||
|
|
78d71a4e7e | ||
|
|
b8f32d46cf | ||
|
|
443782decf | ||
|
|
54e79e5eb8 | ||
|
|
221cd4ffd2 | ||
|
|
354b2fb3cb | ||
|
|
7cb2c36ac9 | ||
|
|
11d73595fc | ||
|
|
c241a6e280 | ||
|
|
198ff143f6 | ||
|
|
7b8b797f9c | ||
|
|
a41b25eea7 | ||
|
|
06eac68f9d | ||
|
|
8abf84ebc0 | ||
|
|
e9aa1f6dd6 | ||
|
|
8e7b838bf7 | ||
|
|
32db3c3c9b | ||
|
|
502586e5d5 | ||
|
|
d92d599d8a | ||
|
|
3487056c7d | ||
|
|
dbcfb08c15 | ||
|
|
e96141090e | ||
|
|
5bd25fc2ed |
@@ -7,14 +7,14 @@ templates:
|
|||||||
name: "Create artifacts directory"
|
name: "Create artifacts directory"
|
||||||
command: mkdir /tmp/artifacts
|
command: mkdir /tmp/artifacts
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: v1-dependencies-{{ checksum "package.json" }}
|
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||||
|
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
key: v1-dependencies-{{ checksum "package.json" }}
|
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||||
|
|
||||||
- run: mkdir -p /tmp/artifacts/logs
|
- run: mkdir -p /tmp/artifacts/logs
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
@@ -30,14 +30,14 @@ templates:
|
|||||||
name: "Create artifacts directory"
|
name: "Create artifacts directory"
|
||||||
command: mkdir /tmp/artifacts
|
command: mkdir /tmp/artifacts
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: v1-dependencies-{{ checksum "package.json" }}
|
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||||
|
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
key: v1-dependencies-{{ checksum "package.json" }}
|
key: v1-dependencies-{{ arch }}-{{ checksum "package.json" }}
|
||||||
|
|
||||||
- run: mkdir -p /tmp/artifacts/logs
|
- run: mkdir -p /tmp/artifacts/logs
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
@@ -60,18 +60,34 @@ jobs:
|
|||||||
- image: selenium/standalone-chrome:3.8.1
|
- image: selenium/standalone-chrome:3.8.1
|
||||||
working_directory: ~/repo-linux-node-v8
|
working_directory: ~/repo-linux-node-v8
|
||||||
steps: *wdio-steps
|
steps: *wdio-steps
|
||||||
build-linux-node-v9:
|
build-linux-node-v10:
|
||||||
docker:
|
docker:
|
||||||
- image: node:9
|
- image: node:10
|
||||||
working_directory: ~/repo-linux-node-v9
|
working_directory: ~/repo-linux-node-v10
|
||||||
steps: *build-steps
|
steps: *build-steps
|
||||||
build-osx-node-v9:
|
build-osx-node-v6:
|
||||||
macos:
|
macos:
|
||||||
xcode: "9.0"
|
xcode: "9.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
override:
|
override:
|
||||||
- brew install node@9
|
- brew install node@6
|
||||||
working_directory: ~/repo-linux-node-v9
|
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
|
steps: *build-steps
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
@@ -80,5 +96,8 @@ workflows:
|
|||||||
jobs:
|
jobs:
|
||||||
- build-linux-node-v6
|
- build-linux-node-v6
|
||||||
- build-linux-node-v8
|
- build-linux-node-v8
|
||||||
- build-linux-node-v9
|
- build-linux-node-v10
|
||||||
|
- build-osx-node-v6
|
||||||
|
- build-osx-node-v8
|
||||||
|
- build-osx-node-v10
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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
|
## Documentation
|
||||||
|
|
||||||
The documentation can be found in the [Wiki](https://github.com/maputnik/editor/wiki). You are welcome to collaborate!
|
The documentation can be found in the [Wiki](https://github.com/maputnik/editor/wiki). You are welcome to collaborate!
|
||||||
|
|||||||
6804
package-lock.json
generated
6804
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "maputnik",
|
"name": "maputnik",
|
||||||
"version": "1.2.0-beta",
|
"version": "1.4.0",
|
||||||
"description": "A MapboxGL visual style editor",
|
"description": "A MapboxGL visual style editor",
|
||||||
"main": "''",
|
"main": "''",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -21,19 +21,20 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/maputnik/editor#readme",
|
"homepage": "https://github.com/maputnik/editor#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.1.1",
|
"@mapbox/mapbox-gl-rtl-text": "^0.1.2",
|
||||||
"@mapbox/mapbox-gl-style-spec": "^11.1.1",
|
"@mapbox/mapbox-gl-style-spec": "^12.0.0",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"codemirror": "^5.36.0",
|
"codemirror": "^5.37.0",
|
||||||
"color": "^3.0.0",
|
"color": "^3.0.0",
|
||||||
"file-saver": "^1.3.8",
|
"file-saver": "^1.3.8",
|
||||||
"github-api": "^3.0.0",
|
"github-api": "^3.0.0",
|
||||||
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
"jsonlint": "github:josdejong/jsonlint#85a19d7",
|
||||||
"lodash.capitalize": "^4.2.1",
|
"lodash.capitalize": "^4.2.1",
|
||||||
|
"lodash.clamp": "^4.0.3",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"mapbox-gl": "^0.44.2",
|
"mapbox-gl": "^0.45.0",
|
||||||
"mapbox-gl-inspect": "^1.3.1",
|
"mapbox-gl-inspect": "^1.3.1",
|
||||||
"maputnik-design": "github:maputnik/design",
|
"maputnik-design": "github:maputnik/design",
|
||||||
"mousetrap": "^1.6.1",
|
"mousetrap": "^1.6.1",
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
"react": "^16.3.2",
|
"react": "^16.3.2",
|
||||||
"react-addons-pure-render-mixin": "^15.6.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-autocomplete": "^1.7.2",
|
||||||
"react-codemirror2": "^4.2.1",
|
"react-codemirror2": "^4.2.1",
|
||||||
"react-collapse": "^4.0.3",
|
"react-collapse": "^4.0.3",
|
||||||
@@ -64,7 +67,15 @@
|
|||||||
"stylelint": {
|
"stylelint": {
|
||||||
"extends": "stylelint-config-recommended-scss",
|
"extends": "stylelint-config-recommended-scss",
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-descending-specificity": null
|
"no-descending-specificity": null,
|
||||||
|
"media-feature-name-no-unknown": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"ignoreMediaFeatureNames": [
|
||||||
|
"prefers-reduced-motion"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
@@ -91,7 +102,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.3",
|
||||||
"babel-eslint": "^8.2.3",
|
"babel-eslint": "^8.2.3",
|
||||||
"babel-loader": "7.1.4",
|
"babel-loader": "7.1.4",
|
||||||
"babel-plugin-istanbul": "^4.1.6",
|
"babel-plugin-istanbul": "^4.1.6",
|
||||||
@@ -122,8 +133,8 @@
|
|||||||
"istanbul-lib-coverage": "^1.2.0",
|
"istanbul-lib-coverage": "^1.2.0",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mocha": "^5.1.0",
|
"mocha": "^5.1.1",
|
||||||
"node-sass": "^4.8.3",
|
"node-sass": "^4.9.0",
|
||||||
"nsp": "^3.1.0",
|
"nsp": "^3.1.0",
|
||||||
"react-hot-loader": "^3.1.1",
|
"react-hot-loader": "^3.1.1",
|
||||||
"sass-loader": "^7.0.1",
|
"sass-loader": "^7.0.1",
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Mousetrap from 'mousetrap'
|
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 MapboxGlMap from './map/MapboxGlMap'
|
||||||
import OpenLayers3Map from './map/OpenLayers3Map'
|
import OpenLayers3Map from './map/OpenLayers3Map'
|
||||||
@@ -9,8 +13,15 @@ import Toolbar from './Toolbar'
|
|||||||
import AppLayout from './AppLayout'
|
import AppLayout from './AppLayout'
|
||||||
import MessagePanel from './MessagePanel'
|
import MessagePanel from './MessagePanel'
|
||||||
|
|
||||||
|
import SettingsModal from './modals/SettingsModal'
|
||||||
|
import ExportModal from './modals/ExportModal'
|
||||||
|
import SourcesModal from './modals/SourcesModal'
|
||||||
|
import OpenModal from './modals/OpenModal'
|
||||||
|
import ShortcutsModal from './modals/ShortcutsModal'
|
||||||
|
import SurveyModal from './modals/SurveyModal'
|
||||||
|
|
||||||
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
import { downloadGlyphsMetadata, downloadSpriteMetadata } from '../libs/metadata'
|
||||||
import styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
import * as styleSpec from '@mapbox/mapbox-gl-style-spec/style-spec'
|
||||||
import style from '../libs/style.js'
|
import style from '../libs/style.js'
|
||||||
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
|
import { initialStyleUrl, loadStyleUrl } from '../libs/urlopen'
|
||||||
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
||||||
@@ -21,9 +32,10 @@ import LayerWatcher from '../libs/layerwatcher'
|
|||||||
import tokens from '../config/tokens.json'
|
import tokens from '../config/tokens.json'
|
||||||
import isEqual from 'lodash.isequal'
|
import isEqual from 'lodash.isequal'
|
||||||
import Debug from '../libs/debug'
|
import Debug from '../libs/debug'
|
||||||
|
import queryUtil from '../libs/query-util'
|
||||||
|
|
||||||
import MapboxGl from 'mapbox-gl'
|
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) {
|
function updateRootSpec(spec, fieldName, newValues) {
|
||||||
@@ -47,6 +59,79 @@ export default class App extends React.Component {
|
|||||||
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
|
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()
|
const styleUrl = initialStyleUrl()
|
||||||
if(styleUrl) {
|
if(styleUrl) {
|
||||||
this.styleStore = new StyleStore()
|
this.styleStore = new StyleStore()
|
||||||
@@ -71,6 +156,8 @@ export default class App extends React.Component {
|
|||||||
Debug.set("maputnik", "styleStore", this.styleStore);
|
Debug.set("maputnik", "styleStore", this.styleStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const queryObj = url.parse(window.location.href, true).query;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
errors: [],
|
errors: [],
|
||||||
infos: [],
|
infos: [],
|
||||||
@@ -80,6 +167,18 @@ export default class App extends React.Component {
|
|||||||
vectorLayers: {},
|
vectorLayers: {},
|
||||||
inspectModeEnabled: false,
|
inspectModeEnabled: false,
|
||||||
spec: styleSpec.latest,
|
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")
|
||||||
|
},
|
||||||
|
mapFilter: queryObj["color-blindness-emulation"],
|
||||||
}
|
}
|
||||||
|
|
||||||
this.layerWatcher = new LayerWatcher({
|
this.layerWatcher = new LayerWatcher({
|
||||||
@@ -164,6 +263,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) {
|
onLayersChange(changedLayers) {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.state.mapStyle,
|
...this.state.mapStyle,
|
||||||
@@ -172,6 +289,40 @@ export default class App extends React.Component {
|
|||||||
this.onStyleChanged(changedStyle)
|
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) {
|
onLayerIdChange(oldId, newId) {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0)
|
||||||
const idx = style.indexOfLayer(changedLayers, oldId)
|
const idx = style.indexOfLayer(changedLayers, oldId)
|
||||||
@@ -214,7 +365,7 @@ export default class App extends React.Component {
|
|||||||
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) {
|
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) {
|
||||||
let url = val.url;
|
let url = val.url;
|
||||||
try {
|
try {
|
||||||
url = mapboxUtil.normalizeSourceURL(url, MapboxGl.accessToken);
|
url = normalizeSourceURL(url, MapboxGl.accessToken);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn("Failed to normalizeSourceURL: ", err);
|
console.warn("Failed to normalizeSourceURL: ", err);
|
||||||
}
|
}
|
||||||
@@ -257,6 +408,7 @@ export default class App extends React.Component {
|
|||||||
mapRenderer() {
|
mapRenderer() {
|
||||||
const mapProps = {
|
const mapProps = {
|
||||||
mapStyle: style.replaceAccessToken(this.state.mapStyle, {allowFallback: true}),
|
mapStyle: style.replaceAccessToken(this.state.mapStyle, {allowFallback: true}),
|
||||||
|
options: this.state.mapOptions,
|
||||||
onDataChange: (e) => {
|
onDataChange: (e) => {
|
||||||
this.layerWatcher.analyzeMap(e.map)
|
this.layerWatcher.analyzeMap(e.map)
|
||||||
this.fetchSources();
|
this.fetchSources();
|
||||||
@@ -266,15 +418,26 @@ export default class App extends React.Component {
|
|||||||
const metadata = this.state.mapStyle.metadata || {}
|
const metadata = this.state.mapStyle.metadata || {}
|
||||||
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
|
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
|
||||||
|
|
||||||
|
let mapElement;
|
||||||
|
|
||||||
// Check if OL3 code has been loaded?
|
// Check if OL3 code has been loaded?
|
||||||
if(renderer === 'ol3') {
|
if(renderer === 'ol3') {
|
||||||
return <OpenLayers3Map {...mapProps} />
|
mapElement = <OpenLayers3Map {...mapProps} />
|
||||||
} else {
|
} else {
|
||||||
return <MapboxGlMap {...mapProps}
|
mapElement = <MapboxGlMap {...mapProps}
|
||||||
inspectModeEnabled={this.state.inspectModeEnabled}
|
inspectModeEnabled={this.state.inspectModeEnabled}
|
||||||
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
||||||
onLayerSelect={this.onLayerSelect.bind(this)} />
|
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) {
|
onLayerSelect(layerId) {
|
||||||
@@ -282,6 +445,19 @@ export default class App extends React.Component {
|
|||||||
this.setState({ selectedLayerIndex: idx })
|
this.setState({ selectedLayerIndex: idx })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleModal(modalName) {
|
||||||
|
this.setState({
|
||||||
|
isOpen: {
|
||||||
|
...this.state.isOpen,
|
||||||
|
[modalName]: !this.state.isOpen[modalName]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(modalName === 'survey') {
|
||||||
|
localStorage.setItem('survey', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const layers = this.state.mapStyle.layers || []
|
const layers = this.state.mapStyle.layers || []
|
||||||
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
|
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
|
||||||
@@ -294,9 +470,14 @@ export default class App extends React.Component {
|
|||||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
onStyleChanged={this.onStyleChanged.bind(this)}
|
||||||
onStyleOpen={this.onStyleChanged.bind(this)}
|
onStyleOpen={this.onStyleChanged.bind(this)}
|
||||||
onInspectModeToggle={this.changeInspectMode.bind(this)}
|
onInspectModeToggle={this.changeInspectMode.bind(this)}
|
||||||
|
onToggleModal={this.toggleModal.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
const layerList = <LayerList
|
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)}
|
onLayersChange={this.onLayersChange.bind(this)}
|
||||||
onLayerSelect={this.onLayerSelect.bind(this)}
|
onLayerSelect={this.onLayerSelect.bind(this)}
|
||||||
selectedLayerIndex={this.state.selectedLayerIndex}
|
selectedLayerIndex={this.state.selectedLayerIndex}
|
||||||
@@ -306,10 +487,17 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
const layerEditor = selectedLayer ? <LayerEditor
|
const layerEditor = selectedLayer ? <LayerEditor
|
||||||
layer={selectedLayer}
|
layer={selectedLayer}
|
||||||
|
layerIndex={this.state.selectedLayerIndex}
|
||||||
|
isFirstLayer={this.state.selectedLayerIndex < 1}
|
||||||
|
isLastLayer={this.state.selectedLayerIndex === this.state.mapStyle.layers.length-1}
|
||||||
sources={this.state.sources}
|
sources={this.state.sources}
|
||||||
vectorLayers={this.state.vectorLayers}
|
vectorLayers={this.state.vectorLayers}
|
||||||
spec={this.state.spec}
|
spec={this.state.spec}
|
||||||
|
onMoveLayer={this.onMoveLayer.bind(this)}
|
||||||
onLayerChanged={this.onLayerChanged.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)}
|
onLayerIdChange={this.onLayerIdChange.bind(this)}
|
||||||
/> : null
|
/> : null
|
||||||
|
|
||||||
@@ -318,12 +506,48 @@ export default class App extends React.Component {
|
|||||||
infos={this.state.infos}
|
infos={this.state.infos}
|
||||||
/> : null
|
/> : 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
|
return <AppLayout
|
||||||
toolbar={toolbar}
|
toolbar={toolbar}
|
||||||
layerList={layerList}
|
layerList={layerList}
|
||||||
layerEditor={layerEditor}
|
layerEditor={layerEditor}
|
||||||
map={this.mapRenderer()}
|
map={this.mapRenderer()}
|
||||||
bottom={bottomPanel}
|
bottom={bottomPanel}
|
||||||
|
modals={modals}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class AppLayout extends React.Component {
|
|||||||
layerEditor: PropTypes.element,
|
layerEditor: PropTypes.element,
|
||||||
map: PropTypes.element.isRequired,
|
map: PropTypes.element.isRequired,
|
||||||
bottom: PropTypes.element,
|
bottom: PropTypes.element,
|
||||||
|
modals: PropTypes.node,
|
||||||
}
|
}
|
||||||
|
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
@@ -39,6 +40,7 @@ class AppLayout extends React.Component {
|
|||||||
{this.props.bottom}
|
{this.props.bottom}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{this.props.modals}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import classnames from 'classnames'
|
|||||||
class Button extends React.Component {
|
class Button extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
"data-wd-key": PropTypes.string,
|
"data-wd-key": PropTypes.string,
|
||||||
|
"aria-label": PropTypes.string,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
@@ -12,13 +13,14 @@ class Button extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <a
|
return <button
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
|
aria-label={this.props["aria-label"]}
|
||||||
className={classnames("maputnik-button", this.props.className)}
|
className={classnames("maputnik-button", this.props.className)}
|
||||||
data-wd-key={this.props["data-wd-key"]}
|
data-wd-key={this.props["data-wd-key"]}
|
||||||
style={this.props.style}>
|
style={this.props.style}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</a>
|
</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,9 @@ import MdInsertEmoticon from 'react-icons/lib/md/insert-emoticon'
|
|||||||
import MdFontDownload from 'react-icons/lib/md/font-download'
|
import MdFontDownload from 'react-icons/lib/md/font-download'
|
||||||
import HelpIcon from 'react-icons/lib/md/help-outline'
|
import HelpIcon from 'react-icons/lib/md/help-outline'
|
||||||
import InspectionIcon from 'react-icons/lib/md/find-in-page'
|
import InspectionIcon from 'react-icons/lib/md/find-in-page'
|
||||||
|
import SurveyIcon from 'react-icons/lib/md/assignment-turned-in'
|
||||||
|
|
||||||
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
import logoImage from 'maputnik-design/logos/logo-color.svg'
|
||||||
import 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 pkgJson from '../../package.json'
|
||||||
|
|
||||||
import style from '../libs/style'
|
import style from '../libs/style'
|
||||||
@@ -41,6 +38,7 @@ class ToolbarLink extends React.Component {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
href: PropTypes.string,
|
href: PropTypes.string,
|
||||||
|
onToggleModal: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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 {
|
class ToolbarAction extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
@@ -63,13 +83,13 @@ class ToolbarAction extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <a
|
return <button
|
||||||
className='maputnik-toolbar-action'
|
className='maputnik-toolbar-action'
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{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
|
// A dict of source id's and the available source layers
|
||||||
sources: PropTypes.object.isRequired,
|
sources: PropTypes.object.isRequired,
|
||||||
onInspectModeToggle: PropTypes.func.isRequired,
|
onInspectModeToggle: PropTypes.func.isRequired,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
onToggleModal: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
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() {
|
render() {
|
||||||
return <div className='maputnik-toolbar'>
|
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">
|
<div className="maputnik-toolbar__inner">
|
||||||
<ToolbarLink
|
<div
|
||||||
href={"https://github.com/maputnik/editor"}
|
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"
|
className="maputnik-toolbar-logo"
|
||||||
>
|
>
|
||||||
<img src={logoImage} alt="Maputnik" />
|
<img src={logoImage} alt="Maputnik" />
|
||||||
<h1>Maputnik
|
<h1>Maputnik
|
||||||
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
|
||||||
</h1>
|
</h1>
|
||||||
</ToolbarLink>
|
</a>
|
||||||
|
</div>
|
||||||
<div className="maputnik-toolbar__actions">
|
<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 />
|
<OpenIcon />
|
||||||
<IconText>Open</IconText>
|
<IconText>Open</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:export" onClick={this.toggleModal.bind(this, 'export')}>
|
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
<IconText>Export</IconText>
|
<IconText>Export</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:sources" onClick={this.toggleModal.bind(this, 'sources')}>
|
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
||||||
<SourcesIcon />
|
<SourcesIcon />
|
||||||
<IconText>Sources</IconText>
|
<IconText>Data Sources</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:settings" onClick={this.toggleModal.bind(this, 'settings')}>
|
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
<IconText>Style Settings</IconText>
|
<IconText>Style Settings</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
@@ -171,6 +169,10 @@ export default class Toolbar extends React.Component {
|
|||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
<IconText>Help</IconText>
|
<IconText>Help</IconText>
|
||||||
</ToolbarLink>
|
</ToolbarLink>
|
||||||
|
<ToolbarLinkHighlighted href={"https://gregorywolanski.typeform.com/to/cPgaSY"}>
|
||||||
|
<SurveyIcon />
|
||||||
|
<IconText>Take the Maputnik Survey</IconText>
|
||||||
|
</ToolbarLinkHighlighted>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ class ColorField extends React.Component {
|
|||||||
{this.state.pickerOpened && picker}
|
{this.state.pickerOpened && picker}
|
||||||
<div className="maputnik-color-swatch" style={swatchStyle}></div>
|
<div className="maputnik-color-swatch" style={swatchStyle}></div>
|
||||||
<input
|
<input
|
||||||
|
spellCheck="false"
|
||||||
className="maputnik-color"
|
className="maputnik-color"
|
||||||
ref={(input) => this.colorInput = input}
|
ref={(input) => this.colorInput = input}
|
||||||
onClick={this.togglePicker.bind(this)}
|
onClick={this.togglePicker.bind(this)}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default class ZoomProperty extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentDidMount() {
|
||||||
this.setState({
|
this.setState({
|
||||||
refs: this.setStopRefs(this.props)
|
refs: this.setStopRefs(this.props)
|
||||||
})
|
})
|
||||||
@@ -66,7 +66,7 @@ export default class ZoomProperty extends React.Component {
|
|||||||
return newRefs;
|
return newRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
const newRefs = this.setStopRefs(nextProps);
|
const newRefs = this.setStopRefs(nextProps);
|
||||||
if(newRefs) {
|
if(newRefs) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { combiningFilterOps } from '../../libs/filterops.js'
|
import { combiningFilterOps } from '../../libs/filterops.js'
|
||||||
|
|
||||||
import 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 DocLabel from '../fields/DocLabel'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
import SingleFilterEditor from './SingleFilterEditor'
|
import SingleFilterEditor from './SingleFilterEditor'
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ class AutocompleteInput extends React.Component {
|
|||||||
style: null
|
style: null
|
||||||
}}
|
}}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
className: "maputnik-string"
|
className: "maputnik-string",
|
||||||
|
spellCheck: false
|
||||||
}}
|
}}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
items={this.props.options}
|
items={this.props.options}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class NumberInput extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
this.setState({ value: nextProps.value })
|
this.setState({ value: nextProps.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ class NumberInput extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <input
|
return <input
|
||||||
|
spellCheck="false"
|
||||||
className="maputnik-number"
|
className="maputnik-number"
|
||||||
placeholder={this.props.default}
|
placeholder={this.props.default}
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
|
|||||||
class SelectInput extends React.Component {
|
class SelectInput extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
"data-wd-key": PropTypes.string.isRequired,
|
"data-wd-key": PropTypes.string,
|
||||||
options: PropTypes.array.isRequired,
|
options: PropTypes.array.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class StringInput extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
this.setState({ value: nextProps.value || '' })
|
this.setState({ value: nextProps.value || '' })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ class StringInput extends React.Component {
|
|||||||
|
|
||||||
return React.createElement(tag, {
|
return React.createElement(tag, {
|
||||||
"data-wd-key": this.props["data-wd-key"],
|
"data-wd-key": this.props["data-wd-key"],
|
||||||
|
spellCheck: !(tag === "input"),
|
||||||
className: classes.join(" "),
|
className: classes.join(" "),
|
||||||
style: this.props.style,
|
style: this.props.style,
|
||||||
value: this.state.value,
|
value: this.state.value,
|
||||||
|
|||||||
30
src/components/layers/Collapse.jsx
Normal file
30
src/components/layers/Collapse.jsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class JSONEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
this.setState({
|
this.setState({
|
||||||
code: JSON.stringify(nextProps.layer, null, 2)
|
code: JSON.stringify(nextProps.layer, null, 2)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
|
||||||
|
|
||||||
import JSONEditor from './JSONEditor'
|
import JSONEditor from './JSONEditor'
|
||||||
import FilterEditor from '../filter/FilterEditor'
|
import FilterEditor from '../filter/FilterEditor'
|
||||||
@@ -13,6 +14,8 @@ import CommentBlock from './CommentBlock'
|
|||||||
import LayerSourceBlock from './LayerSourceBlock'
|
import LayerSourceBlock from './LayerSourceBlock'
|
||||||
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
|
import LayerSourceLayerBlock from './LayerSourceLayerBlock'
|
||||||
|
|
||||||
|
import MoreVertIcon from 'react-icons/lib/md/more-vert'
|
||||||
|
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
import MultiButtonInput from '../inputs/MultiButtonInput'
|
import MultiButtonInput from '../inputs/MultiButtonInput'
|
||||||
|
|
||||||
@@ -45,6 +48,13 @@ export default class LayerEditor extends React.Component {
|
|||||||
spec: PropTypes.object.isRequired,
|
spec: PropTypes.object.isRequired,
|
||||||
onLayerChanged: PropTypes.func,
|
onLayerChanged: PropTypes.func,
|
||||||
onLayerIdChange: PropTypes.func,
|
onLayerIdChange: PropTypes.func,
|
||||||
|
onMoveLayer: PropTypes.func,
|
||||||
|
onLayerDestroy: PropTypes.func,
|
||||||
|
onLayerCopy: PropTypes.func,
|
||||||
|
onLayerVisibilityToggle: PropTypes.func,
|
||||||
|
isFirstLayer: PropTypes.bool,
|
||||||
|
isLastLayer: PropTypes.bool,
|
||||||
|
layerIndex: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -69,7 +79,7 @@ export default class LayerEditor extends React.Component {
|
|||||||
this.state = { editorGroups }
|
this.state = { editorGroups }
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
const additionalGroups = { ...this.state.editorGroups }
|
const additionalGroups = { ...this.state.editorGroups }
|
||||||
|
|
||||||
layout[nextProps.layer.type].groups.forEach(group => {
|
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() {
|
render() {
|
||||||
const layerType = this.props.layer.type
|
const layerType = this.props.layer.type
|
||||||
const groups = layoutGroups(layerType).filter(group => {
|
const groups = layoutGroups(layerType).filter(group => {
|
||||||
@@ -192,8 +209,73 @@ export default class LayerEditor extends React.Component {
|
|||||||
</LayerEditorGroup>
|
</LayerEditorGroup>
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const layout = this.props.layer.layout || {}
|
||||||
|
|
||||||
|
const items = {
|
||||||
|
delete: {
|
||||||
|
text: "Delete",
|
||||||
|
handler: () => this.props.onLayerDestroy(this.props.layer.id)
|
||||||
|
},
|
||||||
|
duplicate: {
|
||||||
|
text: "Duplicate",
|
||||||
|
handler: () => this.props.onLayerCopy(this.props.layer.id)
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
text: (layout.visibility === "none") ? "Show" : "Hide",
|
||||||
|
handler: () => this.props.onLayerVisibilityToggle(this.props.layer.id)
|
||||||
|
},
|
||||||
|
moveLayerUp: {
|
||||||
|
text: "Move layer up",
|
||||||
|
// Not actually used...
|
||||||
|
disabled: this.props.isFirstLayer,
|
||||||
|
handler: () => this.moveLayer(-1)
|
||||||
|
},
|
||||||
|
moveLayerDown: {
|
||||||
|
text: "Move layer down",
|
||||||
|
// Not actually used...
|
||||||
|
disabled: this.props.isLastLayer,
|
||||||
|
handler: () => this.moveLayer(+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelection(id, event) {
|
||||||
|
event.stopPropagation;
|
||||||
|
items[id].handler();
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="maputnik-layer-editor"
|
return <div className="maputnik-layer-editor"
|
||||||
>
|
>
|
||||||
|
<header>
|
||||||
|
<div className="layer-header">
|
||||||
|
<h2 className="layer-header__title">
|
||||||
|
Layer: {this.props.layer.id}
|
||||||
|
</h2>
|
||||||
|
<div className="layer-header__info">
|
||||||
|
<Wrapper
|
||||||
|
className='more-menu'
|
||||||
|
onSelection={handleSelection}
|
||||||
|
closeOnSelection={false}
|
||||||
|
>
|
||||||
|
<Button className='more-menu__button'>
|
||||||
|
<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}
|
{groups}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Collapse from 'react-collapse'
|
|
||||||
import Collapser from './Collapser'
|
import Collapser from './Collapser'
|
||||||
|
import Collapse from './Collapse'
|
||||||
|
|
||||||
|
|
||||||
export default class LayerEditorGroup extends React.Component {
|
export default class LayerEditorGroup extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -22,7 +23,7 @@ export default class LayerEditorGroup extends React.Component {
|
|||||||
<span style={{flexGrow: 1}} />
|
<span style={{flexGrow: 1}} />
|
||||||
<Collapser isCollapsed={this.props.isActive} />
|
<Collapser isCollapsed={this.props.isActive} />
|
||||||
</div>
|
</div>
|
||||||
<Collapse isOpened={this.props.isActive}>
|
<Collapse isActive={this.props.isActive}>
|
||||||
<div className="react-collapse-container">
|
<div className="react-collapse-container">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import 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 InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
|
||||||
|
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import LayerListGroup from './LayerListGroup'
|
import LayerListGroup from './LayerListGroup'
|
||||||
@@ -10,7 +9,7 @@ import AddIcon from 'react-icons/lib/md/add-circle-outline'
|
|||||||
import AddModal from '../modals/AddModal'
|
import AddModal from '../modals/AddModal'
|
||||||
|
|
||||||
import style from '../../libs/style.js'
|
import style from '../../libs/style.js'
|
||||||
import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
|
import {SortableContainer, SortableHandle} from 'react-sortable-hoc';
|
||||||
|
|
||||||
const layerListPropTypes = {
|
const layerListPropTypes = {
|
||||||
layers: PropTypes.array.isRequired,
|
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) {
|
toggleModal(modalName) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpen: {
|
isOpen: {
|
||||||
@@ -186,9 +155,9 @@ class LayerListContainer extends React.Component {
|
|||||||
visibility={(layer.layout || {}).visibility}
|
visibility={(layer.layout || {}).visibility}
|
||||||
isSelected={idx === this.props.selectedLayerIndex}
|
isSelected={idx === this.props.selectedLayerIndex}
|
||||||
onLayerSelect={this.props.onLayerSelect}
|
onLayerSelect={this.props.onLayerSelect}
|
||||||
onLayerDestroy={this.onLayerDestroy.bind(this)}
|
onLayerDestroy={this.props.onLayerDestroy.bind(this)}
|
||||||
onLayerCopy={this.onLayerCopy.bind(this)}
|
onLayerCopy={this.props.onLayerCopy.bind(this)}
|
||||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle.bind(this)}
|
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
|
||||||
/>
|
/>
|
||||||
listItems.push(listItem)
|
listItems.push(listItem)
|
||||||
idx += 1
|
idx += 1
|
||||||
@@ -208,21 +177,22 @@ class LayerListContainer extends React.Component {
|
|||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<div className="maputnik-default-property">
|
<div className="maputnik-default-property">
|
||||||
<div className="maputnik-multibutton">
|
<div className="maputnik-multibutton">
|
||||||
<a
|
<button
|
||||||
|
id="skip-menu"
|
||||||
onClick={this.toggleLayers.bind(this)}
|
onClick={this.toggleLayers.bind(this)}
|
||||||
className="maputnik-button">
|
className="maputnik-button">
|
||||||
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
{this.state.areAllGroupsExpanded === true ? "Collapse" : "Expand"}
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-default-property">
|
<div className="maputnik-default-property">
|
||||||
<div className="maputnik-multibutton">
|
<div className="maputnik-multibutton">
|
||||||
<a
|
<button
|
||||||
onClick={this.toggleModal.bind(this, 'add')}
|
onClick={this.toggleModal.bind(this, 'add')}
|
||||||
data-wd-key="layer-list:add-layer"
|
data-wd-key="layer-list:add-layer"
|
||||||
className="maputnik-button maputnik-button-selected">
|
className="maputnik-button maputnik-button-selected">
|
||||||
Add Layer
|
Add Layer
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -236,18 +206,10 @@ class LayerListContainer extends React.Component {
|
|||||||
export default class LayerList extends React.Component {
|
export default class LayerList extends React.Component {
|
||||||
static propTypes = {...layerListPropTypes}
|
static propTypes = {...layerListPropTypes}
|
||||||
|
|
||||||
onSortEnd(move) {
|
|
||||||
const { oldIndex, newIndex } = move
|
|
||||||
if(oldIndex === newIndex) return
|
|
||||||
let layers = this.props.layers.slice(0)
|
|
||||||
layers = arrayMove(layers, oldIndex, newIndex)
|
|
||||||
this.props.onLayersChange(layers)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <LayerListContainer
|
return <LayerListContainer
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onSortEnd={this.onSortEnd.bind(this)}
|
onSortEnd={this.props.onMoveLayer.bind(this)}
|
||||||
useDragHandle={true}
|
useDragHandle={true}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class IconAction extends React.Component {
|
|||||||
|
|
||||||
renderIcon() {
|
renderIcon() {
|
||||||
switch(this.props.action) {
|
switch(this.props.action) {
|
||||||
case 'copy': return <CopyIcon />
|
case 'duplicate': return <CopyIcon />
|
||||||
case 'show': return <VisibilityIcon />
|
case 'show': return <VisibilityIcon />
|
||||||
case 'hide': return <VisibilityOffIcon />
|
case 'hide': return <VisibilityOffIcon />
|
||||||
case 'delete': return <DeleteIcon />
|
case 'delete': return <DeleteIcon />
|
||||||
@@ -46,13 +46,15 @@ class IconAction extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <a
|
return <button
|
||||||
|
tabIndex="-1"
|
||||||
|
title={this.props.action}
|
||||||
className="maputnik-layer-list-icon-action"
|
className="maputnik-layer-list-icon-action"
|
||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
{this.renderIcon()}
|
{this.renderIcon()}
|
||||||
</a>
|
</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ class LayerListItem extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
|
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
|
||||||
action={'copy'}
|
action={'duplicate'}
|
||||||
onClick={e => this.props.onLayerCopy(this.props.layerId)}
|
onClick={e => this.props.onLayerCopy(this.props.layerId)}
|
||||||
/>
|
/>
|
||||||
<IconAction
|
<IconAction
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import AutocompleteInput from '../inputs/AutocompleteInput'
|
import AutocompleteInput from '../inputs/AutocompleteInput'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import AutocompleteInput from '../inputs/AutocompleteInput'
|
import AutocompleteInput from '../inputs/AutocompleteInput'
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
|
|
||||||
class LayerTypeBlock extends React.Component {
|
class LayerTypeBlock extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
wdKey: PropTypes.string.isRequired,
|
wdKey: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
mapStyle: PropTypes.object.isRequired,
|
mapStyle: PropTypes.object.isRequired,
|
||||||
inspectModeEnabled: PropTypes.bool.isRequired,
|
inspectModeEnabled: PropTypes.bool.isRequired,
|
||||||
highlightedLayer: PropTypes.object,
|
highlightedLayer: PropTypes.object,
|
||||||
|
options: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -65,6 +66,7 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
onDataChange: () => {},
|
onDataChange: () => {},
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
mapboxAccessToken: tokens.mapbox,
|
mapboxAccessToken: tokens.mapbox,
|
||||||
|
options: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -79,7 +81,7 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
if(!this.state.map) return
|
if(!this.state.map) return
|
||||||
const metadata = nextProps.mapStyle.metadata || {}
|
const metadata = nextProps.mapStyle.metadata || {}
|
||||||
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
|
MapboxGl.accessToken = metadata['maputnik:mapbox_access_token'] || tokens.mapbox
|
||||||
@@ -92,20 +94,29 @@ export default class MapboxGlMap extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
const map = this.state.map;
|
||||||
|
|
||||||
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) {
|
||||||
this.state.inspect.toggleInspector()
|
this.state.inspect.toggleInspector()
|
||||||
}
|
}
|
||||||
if(this.props.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled) {
|
||||||
this.state.inspect.render()
|
this.state.inspect.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
map.showTileBoundaries = this.props.options.showTileBoundaries;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const map = new MapboxGl.Map({
|
const mapOpts = {
|
||||||
|
...this.props.options,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
style: this.props.mapStyle,
|
style: this.props.mapStyle,
|
||||||
hash: true,
|
hash: true,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const map = new MapboxGl.Map(mapOpts);
|
||||||
|
|
||||||
|
map.showTileBoundaries = mapOpts.showTileBoundaries;
|
||||||
|
|
||||||
const zoom = new ZoomControl;
|
const zoom = new ZoomControl;
|
||||||
map.addControl(zoom, 'top-right');
|
map.addControl(zoom, 'top-right');
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class OpenLayers3Map extends React.Component {
|
|||||||
const styleFunc = olms.apply(this.map, newMapStyle)
|
const styleFunc = olms.apply(this.map, newMapStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
require.ensure(["ol", "ol-mapbox-style"], () => {
|
require.ensure(["ol", "ol-mapbox-style"], () => {
|
||||||
if(!this.map) return
|
if(!this.map) return
|
||||||
this.updateStyle(nextProps.mapStyle)
|
this.updateStyle(nextProps.mapStyle)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class AddModal extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
UNSAFE_componentWillUpdate(nextProps, nextState) {
|
||||||
// Check if source is valid for new type
|
// Check if source is valid for new type
|
||||||
const oldType = this.state.type;
|
const oldType = this.state.type;
|
||||||
const newType = nextState.type;
|
const newType = nextState.type;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { saveAs } from 'file-saver'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import CheckboxInput from '../inputs/CheckboxInput'
|
import CheckboxInput from '../inputs/CheckboxInput'
|
||||||
@@ -31,7 +31,7 @@ class Gist extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
|
preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
|
||||||
@@ -235,10 +235,24 @@ class ExportModal extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadStyle() {
|
downloadStyle() {
|
||||||
const blob = new Blob([styleSpec.format(stripAccessTokens(this.props.mapStyle))], {type: "application/json;charset=utf-8"});
|
const tokenStyle = styleSpec.format(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle)));
|
||||||
|
|
||||||
|
const blob = new Blob([tokenStyle], {type: "application/json;charset=utf-8"});
|
||||||
saveAs(blob, this.props.mapStyle.id + ".json");
|
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() {
|
render() {
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="export-modal"
|
data-wd-key="export-modal"
|
||||||
@@ -252,13 +266,29 @@ class ExportModal extends React.Component {
|
|||||||
<p>
|
<p>
|
||||||
Download a JSON style to your computer.
|
Download a JSON style to your computer.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<InputBlock label={"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>
|
||||||
|
</p>
|
||||||
|
|
||||||
<Button onClick={this.downloadStyle.bind(this)}>
|
<Button onClick={this.downloadStyle.bind(this)}>
|
||||||
<MdFileDownload />
|
<MdFileDownload />
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="maputnik-modal-section">
|
<div className="maputnik-modal-section hide">
|
||||||
<h4>Save style</h4>
|
<h4>Save style</h4>
|
||||||
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
|
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
49
src/components/modals/LoadingModal.jsx
Normal file
49
src/components/modals/LoadingModal.jsx
Normal 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
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import CloseIcon from 'react-icons/lib/md/close'
|
import CloseIcon from 'react-icons/lib/md/close'
|
||||||
import Overlay from './Overlay'
|
import AriaModal from 'react-aria-modal'
|
||||||
|
|
||||||
|
|
||||||
class Modal extends React.Component {
|
class Modal extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -10,28 +11,51 @@ class Modal extends React.Component {
|
|||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
onOpenToggle: PropTypes.func.isRequired,
|
onOpenToggle: PropTypes.func.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
underlayClickExits: PropTypes.bool,
|
||||||
|
underlayProps: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
underlayClickExits: true
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplicationNode() {
|
||||||
|
return document.getElementById('app');
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Overlay isOpen={this.props.isOpen}>
|
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)}
|
||||||
|
>
|
||||||
<div className="maputnik-modal"
|
<div className="maputnik-modal"
|
||||||
data-wd-key={this.props["data-wd-key"]}
|
data-wd-key={this.props["data-wd-key"]}
|
||||||
>
|
>
|
||||||
<header className="maputnik-modal-header">
|
<header className="maputnik-modal-header">
|
||||||
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
<h1 className="maputnik-modal-header-title">{this.props.title}</h1>
|
||||||
<span className="maputnik-modal-header-space"></span>
|
<span className="maputnik-modal-header-space"></span>
|
||||||
<a className="maputnik-modal-header-toggle"
|
<button className="maputnik-modal-header-toggle"
|
||||||
onClick={() => this.props.onOpenToggle(false)}
|
onClick={() => this.props.onOpenToggle(false)}
|
||||||
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
data-wd-key={this.props["data-wd-key"]+".close-modal"}
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</a>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<div className="maputnik-modal-scroller">
|
<div className="maputnik-modal-scroller">
|
||||||
<div className="maputnik-modal-content">{this.props.children}</div>
|
<div className="maputnik-modal-content">{this.props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Overlay>
|
</AriaModal>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import LoadingModal from './LoadingModal'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import FileReaderInput from 'react-file-reader-input'
|
import FileReaderInput from 'react-file-reader-input'
|
||||||
@@ -23,6 +24,7 @@ class PublicStyle extends React.Component {
|
|||||||
return <div className="maputnik-public-style">
|
return <div className="maputnik-public-style">
|
||||||
<Button
|
<Button
|
||||||
className="maputnik-public-style-button"
|
className="maputnik-public-style-button"
|
||||||
|
aria-label={this.props.title}
|
||||||
onClick={() => this.props.onSelect(this.props.url)}
|
onClick={() => this.props.onSelect(this.props.url)}
|
||||||
>
|
>
|
||||||
<header className="maputnik-public-style-header">
|
<header className="maputnik-public-style-header">
|
||||||
@@ -30,11 +32,12 @@ class PublicStyle extends React.Component {
|
|||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</header>
|
</header>
|
||||||
<img
|
<div
|
||||||
className="maputnik-public-style-thumbnail"
|
className="maputnik-public-style-thumbnail"
|
||||||
src={this.props.thumbnailUrl}
|
style={{
|
||||||
alt={this.props.title}
|
backgroundImage: `url(${this.props.thumbnailUrl})`
|
||||||
/>
|
}}
|
||||||
|
></div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -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) {
|
onStyleSelect(styleUrl) {
|
||||||
this.clearError();
|
this.clearError();
|
||||||
|
|
||||||
request({
|
const reqOpts = {
|
||||||
url: styleUrl,
|
url: styleUrl,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
}, (error, response, body) => {
|
}
|
||||||
|
|
||||||
|
const activeRequest = request(reqOpts, (error, response, body) => {
|
||||||
|
this.setState({
|
||||||
|
activeRequest: null,
|
||||||
|
activeRequestUrl: null
|
||||||
|
});
|
||||||
|
|
||||||
if (!error && response.statusCode == 200) {
|
if (!error && response.statusCode == 200) {
|
||||||
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
|
const mapStyle = style.ensureStyleValidity(JSON.parse(body))
|
||||||
console.log('Loaded style ', mapStyle.id)
|
console.log('Loaded style ', mapStyle.id)
|
||||||
@@ -74,6 +97,11 @@ class OpenModal extends React.Component {
|
|||||||
console.warn('Could not open the style URL', styleUrl)
|
console.warn('Could not open the style URL', styleUrl)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
activeRequest: activeRequest,
|
||||||
|
activeRequestUrl: reqOpts.url
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenUrl() {
|
onOpenUrl() {
|
||||||
@@ -142,7 +170,7 @@ class OpenModal extends React.Component {
|
|||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h2>Upload Style</h2>
|
<h2>Upload Style</h2>
|
||||||
<p>Upload a JSON style from your computer.</p>
|
<p>Upload a JSON style from your computer.</p>
|
||||||
<FileReaderInput onChange={this.onUpload.bind(this)}>
|
<FileReaderInput onChange={this.onUpload.bind(this)} tabIndex="-1">
|
||||||
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
|
<Button className="maputnik-upload-button"><FileUploadIcon /> Upload</Button>
|
||||||
</FileReaderInput>
|
</FileReaderInput>
|
||||||
</section>
|
</section>
|
||||||
@@ -167,6 +195,13 @@ class OpenModal extends React.Component {
|
|||||||
{styleOptions}
|
{styleOptions}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<LoadingModal
|
||||||
|
isOpen={!!this.state.activeRequest}
|
||||||
|
title={'Loading style'}
|
||||||
|
onCancel={(e) => this.onCancelActiveRequest(e)}
|
||||||
|
message={"Loading: "+this.state.activeRequestUrl}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import SelectInput from '../inputs/SelectInput'
|
import SelectInput from '../inputs/SelectInput'
|
||||||
|
|||||||
73
src/components/modals/ShortcutsModal.jsx
Normal file
73
src/components/modals/ShortcutsModal.jsx
Normal 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 Modal from './Modal'
|
||||||
import Button from '../Button'
|
import Button from '../Button'
|
||||||
import InputBlock from '../inputs/InputBlock'
|
import InputBlock from '../inputs/InputBlock'
|
||||||
|
|||||||
41
src/components/modals/SurveyModal.jsx
Normal file
41
src/components/modals/SurveyModal.jsx
Normal 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 don’t track you, so we don’t know how you use Maputnik. Help us make Maputnik better for you by completing a 7–minute 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
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
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 InputBlock from '../inputs/InputBlock'
|
||||||
import StringInput from '../inputs/StringInput'
|
import StringInput from '../inputs/StringInput'
|
||||||
import NumberInput from '../inputs/NumberInput'
|
import NumberInput from '../inputs/NumberInput'
|
||||||
|
import SelectInput from '../inputs/SelectInput'
|
||||||
|
|
||||||
|
|
||||||
class TileJSONSourceEditor extends React.Component {
|
class TileJSONSourceEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
source: PropTypes.object.isRequired,
|
source: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
children: PropTypes.node,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <InputBlock label={"TileJSON URL"} doc={styleSpec.latest.source_vector.url.doc}>
|
return <div>
|
||||||
|
<InputBlock label={"TileJSON URL"} doc={styleSpec.latest.source_vector.url.doc}>
|
||||||
<StringInput
|
<StringInput
|
||||||
value={this.props.source.url}
|
value={this.props.source.url}
|
||||||
onChange={url => this.props.onChange({
|
onChange={url => this.props.onChange({
|
||||||
@@ -21,6 +25,8 @@ class TileJSONSourceEditor extends React.Component {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +34,7 @@ class TileURLSourceEditor extends React.Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
source: PropTypes.object.isRequired,
|
source: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
children: PropTypes.node,
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTileUrl(idx, value) {
|
changeTileUrl(idx, value) {
|
||||||
@@ -73,6 +80,7 @@ class TileURLSourceEditor extends React.Component {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</InputBlock>
|
</InputBlock>
|
||||||
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -116,7 +124,18 @@ class SourceTypeEditor extends React.Component {
|
|||||||
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
|
||||||
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
|
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
|
||||||
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
|
case '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
|
default: return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
{
|
{
|
||||||
"id": "osm-liberty",
|
"id": "osm-liberty",
|
||||||
"title": "OSM Liberty",
|
"title": "OSM Liberty",
|
||||||
"url": "https://rawgit.com/lukasmartinelli/osm-liberty/gh-pages/style.json",
|
"url": "https://rawgit.com/maputnik/osm-liberty/gh-pages/style.json",
|
||||||
"thumbnail": "https://cdn.rawgit.com/lukasmartinelli/osm-liberty/gh-pages/thumbnail.png"
|
"thumbnail": "https://cdn.rawgit.com/maputnik/osm-liberty/gh-pages/thumbnail.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "empty-style",
|
"id": "empty-style",
|
||||||
|
|||||||
12
src/libs/accessibility.js
Normal file
12
src/libs/accessibility.js
Normal 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
|
||||||
|
}
|
||||||
@@ -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) {
|
export function diffMessages(beforeStyle, afterStyle) {
|
||||||
const changes = styleSpec.diff(beforeStyle, afterStyle)
|
const changes = styleSpec.diff(beforeStyle, afterStyle)
|
||||||
|
|||||||
@@ -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 combiningFilterOps = ['all', 'any', 'none']
|
||||||
export const setFilterOps = ['in', '!in']
|
export const setFilterOps = ['in', '!in']
|
||||||
export const otherFilterOps = Object
|
export const otherFilterOps = Object
|
||||||
|
|||||||
@@ -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) {
|
export function changeType(layer, newType) {
|
||||||
const changedPaintProps = { ...layer.paint }
|
const changedPaintProps = { ...layer.paint }
|
||||||
|
|||||||
17
src/libs/query-util.js
Normal file
17
src/libs/query-util.js
Normal 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
|
||||||
|
}
|
||||||
@@ -18,6 +18,11 @@ html {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
// The UI is 100% height so prevent bounce scroll on OSX
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
@@ -76,3 +81,7 @@ label:hover {
|
|||||||
a {
|
a {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
height: calc(100% - #{$toolbar-height + $toolbar-offset});
|
||||||
width: 75%;
|
width: calc(
|
||||||
|
100%
|
||||||
|
- 200px /* layer list */
|
||||||
|
- 350px /* layer editor */
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOC LABEL
|
// DOC LABEL
|
||||||
@@ -53,8 +57,10 @@
|
|||||||
font-size: $font-size-6;
|
font-size: $font-size-6;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
border-width: 0;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: lighten($color-midgray, 12);
|
background-color: lighten($color-midgray, 12);
|
||||||
@@ -69,6 +75,20 @@
|
|||||||
font-size: $font-size-5;
|
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 {
|
.maputnik-icon-button {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
.maputnik-color-swatch {
|
.maputnik-color-swatch {
|
||||||
height: 26px;
|
height: 26px;
|
||||||
width: 3px;
|
width: 14px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,6 @@
|
|||||||
|
|
||||||
.maputnik-button-selected {
|
.maputnik-button-selected {
|
||||||
background-color: lighten($color-midgray, 12);
|
background-color: lighten($color-midgray, 12);
|
||||||
outline: 1px $color-white;
|
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +125,10 @@
|
|||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
border-color: $color-gray;
|
border-color: $color-gray;
|
||||||
transition: background-color 0.1s ease-out;
|
transition: background-color 0.1s ease-out;
|
||||||
|
|
||||||
|
@media screen and (prefers-reduced-motion: reduce) {
|
||||||
|
transition-duration: 0ms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-icon {
|
&-icon {
|
||||||
|
|||||||
@@ -43,17 +43,32 @@
|
|||||||
-webkit-transition: opacity 600ms, visibility 600ms;
|
-webkit-transition: opacity 600ms, visibility 600ms;
|
||||||
transition: opacity 600ms, visibility 600ms;
|
transition: opacity 600ms, visibility 600ms;
|
||||||
|
|
||||||
|
@media screen and (prefers-reduced-motion: reduce) {
|
||||||
|
transition-duration: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@include flex-row;
|
@include flex-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-icon-action svg {
|
&-icon-action {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
svg {
|
||||||
fill: $color-black;
|
fill: $color-black;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-layer-list-item:hover,
|
.maputnik-layer-list-item:hover,
|
||||||
.maputnik-layer-list-item-selected {
|
.maputnik-layer-list-item-selected {
|
||||||
background-color: lighten($color-black, 2);
|
background-color: lighten($color-black, 2);
|
||||||
|
|
||||||
|
.maputnik-layer-list-icon-action {
|
||||||
|
display: block;
|
||||||
|
background: initial;
|
||||||
|
border: none;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-layer-list-icon-action svg {
|
.maputnik-layer-list-icon-action svg {
|
||||||
fill: darken($color-lowgray, 0.5);
|
fill: darken($color-lowgray, 0.5);
|
||||||
|
|
||||||
@@ -122,6 +137,7 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
border-top: solid 1px #36383e;
|
||||||
|
|
||||||
@include flex-row;
|
@include flex-row;
|
||||||
|
|
||||||
@@ -164,3 +180,41 @@
|
|||||||
color: $color-lowgray;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//SCROLLING
|
//SCROLLING
|
||||||
.maputnik-scroll-container {
|
.maputnik-scroll-container {
|
||||||
overflow-x: visible;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
font-family: $font-family;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-section {
|
.maputnik-modal-section {
|
||||||
@@ -42,7 +43,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-header-toggle {
|
.maputnik-modal-header-toggle {
|
||||||
cursor: pointer;
|
border: none;
|
||||||
|
background: initial;
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-scroller {
|
.maputnik-modal-scroller {
|
||||||
@@ -60,31 +64,6 @@
|
|||||||
@extend .maputnik-space;
|
@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
|
//OPEN MODAL
|
||||||
.maputnik-upload-button {
|
.maputnik-upload-button {
|
||||||
@extend .maputnik-big-button;
|
@extend .maputnik-big-button;
|
||||||
@@ -109,6 +88,7 @@
|
|||||||
background-color: $color-gray;
|
background-color: $color-gray;
|
||||||
padding: $margin-3;
|
padding: $margin-3;
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $color-midgray;
|
background-color: $color-midgray;
|
||||||
@@ -123,6 +103,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
margin-top: $margin-2;
|
margin-top: $margin-2;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-top: calc(400 / 600 * 100%);
|
||||||
|
background-size: cover;
|
||||||
|
background-color: $color-midgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-layer {
|
.maputnik-add-layer {
|
||||||
@@ -156,6 +139,7 @@
|
|||||||
font-size: $font-size-5;
|
font-size: $font-size-5;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
@include flex-row;
|
@include flex-row;
|
||||||
}
|
}
|
||||||
@@ -239,3 +223,38 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #ef5350;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
3
src/styles/_react-codemirror.scss
Normal file
3
src/styles/_react-codemirror.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.react-codemirror2 {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
// See <https://github.com/nkbt/react-collapse/commit/4f4fbce7c6c07b082dc62062338c9294c656f9df>
|
// See <https://github.com/nkbt/react-collapse/commit/4f4fbce7c6c07b082dc62062338c9294c656f9df>
|
||||||
.react-collapse-container {
|
.react-collapse-container {
|
||||||
position: relative;
|
display: flex;
|
||||||
overflow: hidden;
|
max-width: 100%;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,25 @@
|
|||||||
background-color: $color-black;
|
background-color: $color-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-toolbar-logo-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-toolbar-logo {
|
.maputnik-toolbar-logo {
|
||||||
flex: 0 0 170px;
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 180px;
|
||||||
width: 180px;
|
width: 180px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-color: $color-black;
|
background-color: $color-black;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
height: $toolbar-height;
|
height: $toolbar-height;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
line-height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -48,14 +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 {
|
.maputnik-toolbar-version {
|
||||||
position: absolute;
|
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
bottom: -2px;
|
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-toolbar-action {
|
.maputnik-toolbar-action {
|
||||||
|
background: inherit;
|
||||||
|
border-width: 0;
|
||||||
@extend .maputnik-toolbar-link;
|
@extend .maputnik-toolbar-link;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,3 +110,23 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ $color-midgray: #36383e;
|
|||||||
$color-lowgray: #8e8e8e;
|
$color-lowgray: #8e8e8e;
|
||||||
$color-white: #f0f0f0;
|
$color-white: #f0f0f0;
|
||||||
$color-red: #cf4a4a;
|
$color-red: #cf4a4a;
|
||||||
|
$color-green: #53b972;
|
||||||
$margin-1: 3px;
|
$margin-1: 3px;
|
||||||
$margin-2: 5px;
|
$margin-2: 5px;
|
||||||
$margin-3: 10px;
|
$margin-3: 10px;
|
||||||
@@ -37,6 +38,7 @@ $toolbar-offset: 0;
|
|||||||
@import 'popup';
|
@import 'popup';
|
||||||
@import 'map';
|
@import 'map';
|
||||||
@import 'react-collapse';
|
@import 'react-collapse';
|
||||||
|
@import 'react-codemirror';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hacks for webdriverio isVisibleWithinViewport
|
* Hacks for webdriverio isVisibleWithinViewport
|
||||||
|
|||||||
@@ -69,6 +69,87 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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="app">
|
||||||
<div id="loader">Loading...</div>
|
<div id="loader">Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user