From d6f31ec82e6e27946b81ebbd85b78b741c0fa3d6 Mon Sep 17 00:00:00 2001
From: orangemug
Date: Wed, 3 Jun 2020 17:11:47 +0100
Subject: [PATCH 01/25] Block* -> Field*
---
.../{BlockComment.jsx => FieldComment.jsx} | 0
src/components/{BlockId.jsx => FieldId.jsx} | 0
.../{BlockMaxZoom.jsx => FieldMaxZoom.jsx} | 0
.../{BlockMinZoom.jsx => FieldMinZoom.jsx} | 0
.../{BlockSource.jsx => FieldSource.jsx} | 0
...ckSourceLayer.jsx => FieldSourceLayer.jsx} | 0
.../{BlockType.jsx => FieldType.jsx} | 0
stories/FieldComment.stories.js | 26 +++++++++++++++++++
stories/FieldId.stories.js | 26 +++++++++++++++++++
stories/FieldMaxZoom.stories.js | 26 +++++++++++++++++++
stories/FieldMinZoom.stories.js | 26 +++++++++++++++++++
stories/FieldSource.stories.js | 26 +++++++++++++++++++
stories/FieldSourceLayer.stories.js | 26 +++++++++++++++++++
stories/FieldType.stories.js | 26 +++++++++++++++++++
stories/ui.js | 2 +-
15 files changed, 183 insertions(+), 1 deletion(-)
rename src/components/{BlockComment.jsx => FieldComment.jsx} (100%)
rename src/components/{BlockId.jsx => FieldId.jsx} (100%)
rename src/components/{BlockMaxZoom.jsx => FieldMaxZoom.jsx} (100%)
rename src/components/{BlockMinZoom.jsx => FieldMinZoom.jsx} (100%)
rename src/components/{BlockSource.jsx => FieldSource.jsx} (100%)
rename src/components/{BlockSourceLayer.jsx => FieldSourceLayer.jsx} (100%)
rename src/components/{BlockType.jsx => FieldType.jsx} (100%)
create mode 100644 stories/FieldComment.stories.js
create mode 100644 stories/FieldId.stories.js
create mode 100644 stories/FieldMaxZoom.stories.js
create mode 100644 stories/FieldMinZoom.stories.js
create mode 100644 stories/FieldSource.stories.js
create mode 100644 stories/FieldSourceLayer.stories.js
create mode 100644 stories/FieldType.stories.js
diff --git a/src/components/BlockComment.jsx b/src/components/FieldComment.jsx
similarity index 100%
rename from src/components/BlockComment.jsx
rename to src/components/FieldComment.jsx
diff --git a/src/components/BlockId.jsx b/src/components/FieldId.jsx
similarity index 100%
rename from src/components/BlockId.jsx
rename to src/components/FieldId.jsx
diff --git a/src/components/BlockMaxZoom.jsx b/src/components/FieldMaxZoom.jsx
similarity index 100%
rename from src/components/BlockMaxZoom.jsx
rename to src/components/FieldMaxZoom.jsx
diff --git a/src/components/BlockMinZoom.jsx b/src/components/FieldMinZoom.jsx
similarity index 100%
rename from src/components/BlockMinZoom.jsx
rename to src/components/FieldMinZoom.jsx
diff --git a/src/components/BlockSource.jsx b/src/components/FieldSource.jsx
similarity index 100%
rename from src/components/BlockSource.jsx
rename to src/components/FieldSource.jsx
diff --git a/src/components/BlockSourceLayer.jsx b/src/components/FieldSourceLayer.jsx
similarity index 100%
rename from src/components/BlockSourceLayer.jsx
rename to src/components/FieldSourceLayer.jsx
diff --git a/src/components/BlockType.jsx b/src/components/FieldType.jsx
similarity index 100%
rename from src/components/BlockType.jsx
rename to src/components/FieldType.jsx
diff --git a/stories/FieldComment.stories.js b/stories/FieldComment.stories.js
new file mode 100644
index 0000000..6c815d9
--- /dev/null
+++ b/stories/FieldComment.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {useActionState} from './helper';
+import FieldComment from '../src/components/FieldComment';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'FieldComment',
+ component: FieldComment,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", "Hello\nworld");
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/FieldId.stories.js b/stories/FieldId.stories.js
new file mode 100644
index 0000000..b2b97ae
--- /dev/null
+++ b/stories/FieldId.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {useActionState} from './helper';
+import FieldId from '../src/components/FieldId';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'FieldId',
+ component: FieldId,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", "water");
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/FieldMaxZoom.stories.js b/stories/FieldMaxZoom.stories.js
new file mode 100644
index 0000000..729f889
--- /dev/null
+++ b/stories/FieldMaxZoom.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {useActionState} from './helper';
+import FieldMaxZoom from '../src/components/FieldMaxZoom';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'FieldMaxZoom',
+ component: FieldMaxZoom,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", 12);
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/FieldMinZoom.stories.js b/stories/FieldMinZoom.stories.js
new file mode 100644
index 0000000..f88eaee
--- /dev/null
+++ b/stories/FieldMinZoom.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {useActionState} from './helper';
+import FieldMinZoom from '../src/components/FieldMinZoom';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'FieldMinZoom',
+ component: FieldMinZoom,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", 2);
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/FieldSource.stories.js b/stories/FieldSource.stories.js
new file mode 100644
index 0000000..25d9fde
--- /dev/null
+++ b/stories/FieldSource.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {useActionState} from './helper';
+import FieldSource from '../src/components/FieldSource';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'FieldSource',
+ component: FieldSource,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", "openmaptiles");
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/FieldSourceLayer.stories.js b/stories/FieldSourceLayer.stories.js
new file mode 100644
index 0000000..5d8b0ec
--- /dev/null
+++ b/stories/FieldSourceLayer.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {useActionState} from './helper';
+import FieldSourceLayer from '../src/components/FieldSourceLayer';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'FieldSourceLayer',
+ component: FieldSourceLayer,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", "water");
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/FieldType.stories.js b/stories/FieldType.stories.js
new file mode 100644
index 0000000..2312bca
--- /dev/null
+++ b/stories/FieldType.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import {useActionState} from './helper';
+import FieldType from '../src/components/FieldType';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'FieldType',
+ component: FieldType,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", "background");
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/ui.js b/stories/ui.js
index 41cfb7f..eb6333c 100644
--- a/stories/ui.js
+++ b/stories/ui.js
@@ -11,7 +11,7 @@ export function Describe ({children}) {
export function Wrapper ({children}) {
return (
-
+
{children}
);
From 2cc179acc14b628d197471c7b79936ce1591adfe Mon Sep 17 00:00:00 2001
From: orangemug
Date: Tue, 9 Jun 2020 19:11:07 +0100
Subject: [PATCH 02/25] Fixed more input accessibility issues, also
- Added searchParams based router for easier testing
- Added more stories to the storybook
---
.storybook/main.js | 1 -
package-lock.json | 5 +
package.json | 1 +
src/components/App.jsx | 92 ++++++-
src/components/AppToolbar.jsx | 25 +-
src/components/Block.jsx | 45 +---
src/components/FieldArray.jsx | 105 +-------
src/components/FieldAutocomplete.jsx | 93 +------
src/components/FieldCheckbox.jsx | 32 +--
src/components/FieldColor.jsx | 126 +---------
src/components/FieldComment.jsx | 7 +-
src/components/FieldDynamicArray.jsx | 133 +---------
src/components/FieldEnum.jsx | 41 +--
src/components/FieldFunction.jsx | 1 +
src/components/FieldId.jsx | 7 +-
src/components/FieldJson.jsx | 16 ++
src/components/FieldMaxZoom.jsx | 7 +-
src/components/FieldMinZoom.jsx | 7 +-
src/components/FieldMultiInput.jsx | 38 +--
src/components/FieldNumber.jsx | 227 +----------------
src/components/FieldSelect.jsx | 29 +--
src/components/FieldSource.jsx | 7 +-
src/components/FieldSourceLayer.jsx | 7 +-
src/components/FieldString.jsx | 88 +------
src/components/FieldType.jsx | 11 +-
src/components/FieldUrl.jsx | 95 +------
src/components/Fieldset.jsx | 23 ++
src/components/FilterEditor.jsx | 18 +-
src/components/FilterEditorBlock.jsx | 6 +-
src/components/InputArray.jsx | 113 +++++++++
src/components/InputAutocomplete.jsx | 100 ++++++++
.../{Button.jsx => InputButton.jsx} | 3 +-
src/components/InputCheckbox.jsx | 34 +++
src/components/InputColor.jsx | 135 ++++++++++
src/components/InputDynamicArray.jsx | 138 ++++++++++
src/components/InputEnum.jsx | 49 ++++
src/components/InputFont.jsx | 61 +++++
.../{FieldJsonEditor.jsx => InputJson.jsx} | 3 +-
src/components/InputMultiInput.jsx | 42 ++++
src/components/InputNumber.jsx | 235 ++++++++++++++++++
src/components/InputSelect.jsx | 36 +++
src/components/InputSpec.jsx | 139 +++++++++++
src/components/InputString.jsx | 95 +++++++
src/components/InputUrl.jsx | 103 ++++++++
src/components/LayerEditor.jsx | 64 ++---
src/components/MapMapboxGl.jsx | 1 +
src/components/ModalAdd.jsx | 25 +-
src/components/ModalExport.jsx | 40 ++-
src/components/ModalLoading.jsx | 6 +-
src/components/ModalOpen.jsx | 16 +-
src/components/ModalSettings.jsx | 87 +++----
src/components/ModalSources.jsx | 62 ++---
src/components/ModalSourcesTypeEditor.jsx | 181 +++++++-------
src/components/ModalSurvey.jsx | 6 +-
src/components/SingleFilterEditor.jsx | 12 +-
src/components/SpecField.jsx | 145 ++---------
src/components/_DataProperty.jsx | 166 ++++++++-----
src/components/_DeleteStopButton.jsx | 6 +-
src/components/_ExpressionProperty.jsx | 14 +-
src/components/_FieldComment.jsx | 32 +++
.../{FieldFont.jsx => _FieldFont.jsx} | 22 +-
src/components/_FieldId.jsx | 28 +++
src/components/_FieldMaxZoom.jsx | 31 +++
src/components/_FieldMinZoom.jsx | 31 +++
src/components/_FieldSource.jsx | 37 +++
src/components/_FieldSourceLayer.jsx | 35 +++
.../{FieldSymbol.jsx => _FieldSymbol.jsx} | 0
src/components/_FieldType.jsx | 53 ++++
src/components/_FunctionButtons.jsx | 28 +--
src/components/_SpecProperty.jsx | 7 +-
src/components/_ZoomProperty.jsx | 132 ++++++----
src/styles/_components.scss | 1 +
src/styles/_input.scss | 4 +-
src/styles/_modal.scss | 5 +-
src/styles/_zoomproperty.scss | 4 -
src/styles/index.scss | 132 ++++++++++
stories/FieldArray.stories.js | 2 +
stories/FieldAutocomplete.stories.js | 1 +
stories/FieldCheckbox.stories.js | 2 +
stories/FieldColor.stories.js | 2 +-
stories/FieldComment.stories.js | 26 --
stories/FieldDynamicArray.stories.js | 3 +
stories/FieldEnum.stories.js | 6 +-
stories/FieldFont.stories.js | 28 ---
stories/FieldFunction.stories.js | 43 ++++
stories/FieldId.stories.js | 26 --
stories/FieldMaxZoom.stories.js | 26 --
stories/FieldMinZoom.stories.js | 26 --
stories/FieldMultiInput.stories.js | 1 +
stories/FieldNumber.stories.js | 4 +-
stories/FieldSelect.stories.js | 1 +
stories/FieldSource.stories.js | 26 --
stories/FieldSourceLayer.stories.js | 26 --
stories/FieldString.stories.js | 44 +---
stories/FieldSymbol.stories.js | 29 ---
stories/FieldType.stories.js | 26 --
stories/FieldUrl.stories.js | 2 +
stories/IconLayer.stories.js | 56 +++++
stories/InputArray.stories.js | 44 ++++
stories/InputAutocomplete.stories.js | 29 +++
stories/InputButton.stories.js | 21 ++
stories/InputCheckbox.stories.js | 41 +++
stories/InputColor.stories.js | 27 ++
stories/InputDynamicArray.stories.js | 55 ++++
stories/InputEnum.stories.js | 77 ++++++
stories/InputJson.stories.js | 27 ++
stories/InputMultiInput.stories.js | 29 +++
stories/InputNumber.stories.js | 44 ++++
stories/InputSelect.stories.js | 30 +++
stories/InputString.stories.js | 69 +++++
stories/InputUrl.stories.js | 42 ++++
stories/LayerEditor.stories.js | 202 +++++++++++++++
stories/LayerListItem.stories.js | 58 +++++
stories/MapMapboxGl.stories.js | 109 ++++++++
stories/MapOpenLayers.stories.js | 112 +++++++++
stories/Modal.stories.js | 26 ++
stories/ModalAdd.stories.js | 25 ++
stories/ModalDebug.stories.js | 28 +++
stories/ModalExport.stories.js | 26 ++
stories/ModalLoading.stories.js | 25 ++
stories/ModalOpen.stories.js | 31 +++
stories/ModalSettings.stories.js | 29 +++
stories/ModalShortcuts.stories.js | 0
stories/ModalSources.stories.js | 32 +++
...tton.stories.js => ModalSurvey.stories.js} | 20 +-
stories/ScrollContainer.stories.js | 26 ++
stories/ui.js | 8 +
127 files changed, 3858 insertions(+), 1832 deletions(-)
create mode 100644 src/components/FieldJson.jsx
create mode 100644 src/components/Fieldset.jsx
create mode 100644 src/components/InputArray.jsx
create mode 100644 src/components/InputAutocomplete.jsx
rename src/components/{Button.jsx => InputButton.jsx} (93%)
create mode 100644 src/components/InputCheckbox.jsx
create mode 100644 src/components/InputColor.jsx
create mode 100644 src/components/InputDynamicArray.jsx
create mode 100644 src/components/InputEnum.jsx
create mode 100644 src/components/InputFont.jsx
rename src/components/{FieldJsonEditor.jsx => InputJson.jsx} (98%)
create mode 100644 src/components/InputMultiInput.jsx
create mode 100644 src/components/InputNumber.jsx
create mode 100644 src/components/InputSelect.jsx
create mode 100644 src/components/InputSpec.jsx
create mode 100644 src/components/InputString.jsx
create mode 100644 src/components/InputUrl.jsx
create mode 100644 src/components/_FieldComment.jsx
rename src/components/{FieldFont.jsx => _FieldFont.jsx} (74%)
create mode 100644 src/components/_FieldId.jsx
create mode 100644 src/components/_FieldMaxZoom.jsx
create mode 100644 src/components/_FieldMinZoom.jsx
create mode 100644 src/components/_FieldSource.jsx
create mode 100644 src/components/_FieldSourceLayer.jsx
rename src/components/{FieldSymbol.jsx => _FieldSymbol.jsx} (100%)
create mode 100644 src/components/_FieldType.jsx
delete mode 100644 stories/FieldComment.stories.js
delete mode 100644 stories/FieldFont.stories.js
create mode 100644 stories/FieldFunction.stories.js
delete mode 100644 stories/FieldId.stories.js
delete mode 100644 stories/FieldMaxZoom.stories.js
delete mode 100644 stories/FieldMinZoom.stories.js
delete mode 100644 stories/FieldSource.stories.js
delete mode 100644 stories/FieldSourceLayer.stories.js
delete mode 100644 stories/FieldSymbol.stories.js
delete mode 100644 stories/FieldType.stories.js
create mode 100644 stories/IconLayer.stories.js
create mode 100644 stories/InputArray.stories.js
create mode 100644 stories/InputAutocomplete.stories.js
create mode 100644 stories/InputButton.stories.js
create mode 100644 stories/InputCheckbox.stories.js
create mode 100644 stories/InputColor.stories.js
create mode 100644 stories/InputDynamicArray.stories.js
create mode 100644 stories/InputEnum.stories.js
create mode 100644 stories/InputJson.stories.js
create mode 100644 stories/InputMultiInput.stories.js
create mode 100644 stories/InputNumber.stories.js
create mode 100644 stories/InputSelect.stories.js
create mode 100644 stories/InputString.stories.js
create mode 100644 stories/InputUrl.stories.js
create mode 100644 stories/LayerEditor.stories.js
create mode 100644 stories/LayerListItem.stories.js
create mode 100644 stories/MapMapboxGl.stories.js
create mode 100644 stories/MapOpenLayers.stories.js
create mode 100644 stories/Modal.stories.js
create mode 100644 stories/ModalAdd.stories.js
create mode 100644 stories/ModalDebug.stories.js
create mode 100644 stories/ModalExport.stories.js
create mode 100644 stories/ModalLoading.stories.js
create mode 100644 stories/ModalOpen.stories.js
create mode 100644 stories/ModalSettings.stories.js
create mode 100644 stories/ModalShortcuts.stories.js
create mode 100644 stories/ModalSources.stories.js
rename stories/{Button.stories.js => ModalSurvey.stories.js} (55%)
create mode 100644 stories/ScrollContainer.stories.js
diff --git a/.storybook/main.js b/.storybook/main.js
index 81ab3a1..55c1c31 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -16,7 +16,6 @@ module.exports = {
...config,
module: {
rules: [
- ...config.module.rules,
...rules,
]
}
diff --git a/package-lock.json b/package-lock.json
index 4c73eae..ed128af 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17124,6 +17124,11 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true
},
+ "string-hash": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
+ "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs="
+ },
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
diff --git a/package.json b/package.json
index 047d86e..77c9c20 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
"react-sortable-hoc": "^1.11.0",
"reconnecting-websocket": "^4.4.0",
"slugify": "^1.3.6",
+ "string-hash": "^1.1.3",
"url": "^0.11.0"
},
"jshintConfig": {
diff --git a/src/components/App.jsx b/src/components/App.jsx
index bd91f0f..a88cd56 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -6,6 +6,7 @@ import get from 'lodash.get'
import {unset} from 'lodash'
import arrayMove from 'array-move'
import url from 'url'
+import hash from "string-hash";
import MapMapboxGl from './MapMapboxGl'
import MapOpenLayers from './MapOpenLayers'
@@ -189,7 +190,7 @@ export default class App extends React.Component {
console.log('Falling back to local storage for storing styles')
this.styleStore = new StyleStore()
}
- this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle))
+ this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle, {initialLoad: true}))
if(Debug.enabled()) {
Debug.set("maputnik", "styleStore", this.styleStore);
@@ -322,9 +323,14 @@ export default class App extends React.Component {
opts = {
save: true,
addRevision: true,
+ initialLoad: false,
...opts,
};
+ if (opts.initialLoad) {
+ this.getInitialStateFromUrl(newStyle);
+ }
+
const errors = validate(newStyle, latest) || [];
// The validate function doesn't give us errors for duplicate error with
@@ -442,6 +448,7 @@ export default class App extends React.Component {
errors: mappedErrors,
}, () => {
this.fetchSources();
+ this.setStateInUrl();
})
}
@@ -542,7 +549,7 @@ export default class App extends React.Component {
setMapState = (newState) => {
this.setState({
mapState: newState
- })
+ }, this.setStateInUrl);
}
setDefaultValues = (styleObj) => {
@@ -697,8 +704,85 @@ export default class App extends React.Component {
}
+ setStateInUrl = () => {
+ const {mapState, mapStyle, isOpen} = this.state;
+ const {selectedLayerIndex} = this.state;
+ const url = new URL(location.href);
+ const hashVal = hash(JSON.stringify(mapStyle));
+ url.searchParams.set("layer", `${hashVal}~${selectedLayerIndex}`);
+
+ const openModals = Object.entries(isOpen)
+ .map(([key, val]) => (val === true ? key : null))
+ .filter(val => val !== null);
+
+ if (openModals.length > 0) {
+ url.searchParams.set("modal", openModals.join(","));
+ }
+ else {
+ url.searchParams.delete("modal");
+ }
+
+ if (mapState === "map") {
+ url.searchParams.delete("view");
+ }
+ else if (mapState === "inspect") {
+ url.searchParams.set("view", "inspect");
+ }
+
+ history.replaceState({selectedLayerIndex}, "Maputnik", url.href);
+ }
+
+ getInitialStateFromUrl = (mapStyle) => {
+ const url = new URL(location.href);
+ const modalParam = url.searchParams.get("modal");
+ if (modalParam && modalParam !== "") {
+ const modals = modalParam.split(",");
+ const modalObj = {};
+ modals.forEach(modalName => {
+ modalObj[modalName] = true;
+ });
+
+ this.setState({
+ isOpen: {
+ ...this.state.isOpen,
+ ...modalObj,
+ }
+ });
+ }
+
+ const view = url.searchParams.get("view");
+ if (view && view !== "") {
+ this.setMapState(view);
+ }
+
+ const path = url.searchParams.get("layer");
+ if (path) {
+ try {
+ const parts = path.split("~");
+ const [hashVal, selectedLayerIndex] = [
+ parts[0],
+ parseInt(parts[1], 10),
+ ];
+
+ let invalid = false;
+ if (hashVal !== "-") {
+ const currentHashVal = hash(JSON.stringify(mapStyle));
+ if (currentHashVal !== parseInt(hashVal, 10)) {
+ invalid = true;
+ }
+ }
+ if (!invalid) {
+ this.setState({selectedLayerIndex});
+ }
+ }
+ catch (err) {
+ console.warn(err);
+ }
+ }
+ }
+
onLayerSelect = (index) => {
- this.setState({ selectedLayerIndex: index })
+ this.setState({ selectedLayerIndex: index }, this.setStateInUrl);
}
setModal(modalName, value) {
@@ -711,7 +795,7 @@ export default class App extends React.Component {
...this.state.isOpen,
[modalName]: value
}
- })
+ }, this.setStateInUrl)
}
toggleModal(modalName) {
diff --git a/src/components/AppToolbar.jsx b/src/components/AppToolbar.jsx
index 758a097..052e1ee 100644
--- a/src/components/AppToolbar.jsx
+++ b/src/components/AppToolbar.jsx
@@ -145,31 +145,37 @@ export default class AppToolbar extends React.Component {
const views = [
{
id: "map",
+ group: "general",
title: "Map",
},
{
id: "inspect",
+ group: "general",
title: "Inspect",
disabled: this.props.renderer !== 'mbgljs',
},
{
id: "filter-deuteranopia",
- title: "Deuteranopia color filter",
+ group: "color-accessibility",
+ title: "Deuteranopia filter",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-protanopia",
- title: "Protanopia color filter",
+ group: "color-accessibility",
+ title: "Protanopia filter",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-tritanopia",
- title: "Tritanopia color filter",
+ group: "color-accessibility",
+ title: "Tritanopia filter",
disabled: !colorAccessibilityFiltersEnabled,
},
{
id: "filter-achromatopsia",
- title: "Achromatopsia color filter",
+ group: "color-accessibility",
+ title: "Achromatopsia filter",
disabled: !colorAccessibilityFiltersEnabled,
},
];
@@ -242,13 +248,22 @@ export default class AppToolbar extends React.Component {
onChange={(e) => this.handleSelection(e.target.value)}
value={currentView.id}
>
- {views.map((item) => {
+ {views.filter(v => v.group === "general").map((item) => {
return (
{item.title}
);
})}
+
+ {views.filter(v => v.group === "color-accessibility").map((item) => {
+ return (
+
+ {item.title}
+
+ );
+ })}
+
diff --git a/src/components/Block.jsx b/src/components/Block.jsx
index c462203..4f798d0 100644
--- a/src/components/Block.jsx
+++ b/src/components/Block.jsx
@@ -50,44 +50,15 @@ export default class Block extends React.Component {
"maputnik-input-block--wide": this.props.wideMode,
"maputnik-action-block": this.props.action
})}
- >
- {this.props.fieldSpec &&
-
-
-
- }
- {!this.props.fieldSpec &&
-
- {this.props.label}
-
- }
- {this.props.action &&
-
- {this.props.action}
-
- }
-
- {this.props.children}
-
- {errors.length > 0 &&
-
- {[].concat(this.props.error).map((error, idx) => {
- return
{error.message}
- })}
+ >
+
+
+ {this.props.label}
- }
- {this.props.fieldSpec &&
-
-
-
- }
+
+ {this.props.children}
+
+
}
}
diff --git a/src/components/FieldArray.jsx b/src/components/FieldArray.jsx
index 7c78961..00103fc 100644
--- a/src/components/FieldArray.jsx
+++ b/src/components/FieldArray.jsx
@@ -1,108 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
-import FieldString from './FieldString'
-import FieldNumber from './FieldNumber'
+import Block from './Block'
+import InputArray from './InputArray'
+import Fieldset from './Fieldset'
export default class FieldArray extends React.Component {
static propTypes = {
- value: PropTypes.array,
- type: PropTypes.string,
- length: PropTypes.number,
- default: PropTypes.array,
- onChange: PropTypes.func,
- }
-
- static defaultProps = {
- value: [],
- default: [],
- }
-
- constructor (props) {
- super(props);
- this.state = {
- value: this.props.value.slice(0),
- // This is so we can compare changes in getDerivedStateFromProps
- initialPropsValue: this.props.value.slice(0),
- };
- }
-
- static getDerivedStateFromProps(props, state) {
- const value = [];
- const initialPropsValue = state.initialPropsValue.slice(0);
-
- Array(props.length).fill(null).map((_, i) => {
- if (props.value[i] === state.initialPropsValue[i]) {
- value[i] = state.value[i];
- }
- else {
- value[i] = state.value[i];
- initialPropsValue[i] = state.value[i];
- }
- })
-
- return {
- value,
- initialPropsValue,
- };
- }
-
- isComplete (value) {
- return Array(this.props.length).fill(null).every((_, i) => {
- const val = value[i]
- return !(val === undefined || val === "");
- });
- }
-
- changeValue(idx, newValue) {
- const value = this.state.value.slice(0);
- value[idx] = newValue;
-
- this.setState({
- value,
- }, () => {
- if (this.isComplete(value)) {
- this.props.onChange(value);
- }
- else {
- // Unset until complete
- this.props.onChange(undefined);
- }
- });
+ ...InputArray.propTypes,
+ name: PropTypes.string,
}
render() {
- const {value} = this.state;
+ const {props} = this;
- const containsValues = (
- value.length > 0 &&
- !value.every(val => {
- return (val === "" || val === undefined)
- })
- );
-
- const inputs = Array(this.props.length).fill(null).map((_, i) => {
- if(this.props.type === 'number') {
- return
- } else {
- return
- }
- })
-
- return
- {inputs}
-
+ return
+
+
}
}
diff --git a/src/components/FieldAutocomplete.jsx b/src/components/FieldAutocomplete.jsx
index 47c4864..da63105 100644
--- a/src/components/FieldAutocomplete.jsx
+++ b/src/components/FieldAutocomplete.jsx
@@ -1,97 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import Autocomplete from 'react-autocomplete'
+import Block from './Block'
+import InputAutocomplete from './InputAutocomplete'
-const MAX_HEIGHT = 140;
-
export default class FieldAutocomplete extends React.Component {
static propTypes = {
- value: PropTypes.string,
- options: PropTypes.array,
- onChange: PropTypes.func,
- keepMenuWithinWindowBounds: PropTypes.bool
- }
-
- state = {
- maxHeight: MAX_HEIGHT
- }
-
- static defaultProps = {
- onChange: () => {},
- options: [],
- }
-
- calcMaxHeight() {
- if(this.props.keepMenuWithinWindowBounds) {
- const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
- const limitedMaxHeight = Math.min(maxHeight, MAX_HEIGHT);
-
- if(limitedMaxHeight != this.state.maxHeight) {
- this.setState({
- maxHeight: limitedMaxHeight
- })
- }
- }
- }
-
- componentDidMount() {
- this.calcMaxHeight();
- }
-
- componentDidUpdate() {
- this.calcMaxHeight();
- }
-
- onChange (v) {
- this.props.onChange(v === "" ? undefined : v);
+ ...InputAutocomplete.propTypes,
}
render() {
- return {
- this.autocompleteMenuEl = el;
- }}
- >
-
item[0]}
- onSelect={v => this.onChange(v)}
- onChange={(e, v) => this.onChange(v)}
- shouldItemRender={(item, value="") => {
- if (typeof(value) === "string") {
- return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
- }
- }}
- renderItem={(item, isHighlighted) => (
-
- {item[1]}
-
- )}
- />
-
+ const {props} = this;
+
+ return
+
+
}
}
diff --git a/src/components/FieldCheckbox.jsx b/src/components/FieldCheckbox.jsx
index 066f3c8..7cdc08b 100644
--- a/src/components/FieldCheckbox.jsx
+++ b/src/components/FieldCheckbox.jsx
@@ -1,34 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
+import Block from './Block'
+import InputCheckbox from './InputCheckbox'
+
export default class FieldCheckbox extends React.Component {
static propTypes = {
- value: PropTypes.bool,
- style: PropTypes.object,
- onChange: PropTypes.func,
- }
-
- static defaultProps = {
- value: false,
+ ...InputCheckbox.propTypes,
}
render() {
- return
- this.props.onChange(!this.props.value)}
- checked={this.props.value}
- />
-
-
+ const {props} = this;
+
+ return
+
+
}
}
diff --git a/src/components/FieldColor.jsx b/src/components/FieldColor.jsx
index 1f73b4e..2e9067b 100644
--- a/src/components/FieldColor.jsx
+++ b/src/components/FieldColor.jsx
@@ -1,132 +1,20 @@
import React from 'react'
-import Color from 'color'
-import ChromePicker from 'react-color/lib/components/chrome/Chrome'
import PropTypes from 'prop-types'
-import lodash from 'lodash';
+import Block from './Block'
+import InputColor from './InputColor'
-function formatColor(color) {
- const rgb = color.rgb
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`
-}
-/*** Number fields with support for min, max and units and documentation*/
export default class FieldColor extends React.Component {
static propTypes = {
- onChange: PropTypes.func.isRequired,
- name: PropTypes.string,
- value: PropTypes.string,
- doc: PropTypes.string,
- style: PropTypes.object,
- default: PropTypes.string,
- }
-
- state = {
- pickerOpened: false
- }
-
- constructor () {
- super();
- this.onChangeNoCheck = lodash.throttle(this.onChangeNoCheck, 1000/30);
- }
-
- onChangeNoCheck (v) {
- this.props.onChange(v);
- }
-
- //TODO: I much rather would do this with absolute positioning
- //but I am too stupid to get it to work together with fixed position
- //and scrollbars so I have to fallback to JavaScript
- calcPickerOffset = () => {
- const elem = this.colorInput
- if(elem) {
- const pos = elem.getBoundingClientRect()
- return {
- top: pos.top,
- left: pos.left + 196,
- }
- } else {
- return {
- top: 160,
- left: 555,
- }
- }
- }
-
- togglePicker = () => {
- this.setState({ pickerOpened: !this.state.pickerOpened })
- }
-
- get color() {
- // Catch invalid color.
- try {
- return Color(this.props.value).rgb()
- }
- catch(err) {
- console.warn("Error parsing color: ", err);
- return Color("rgb(255,255,255)");
- }
- }
-
- onChange (v) {
- this.props.onChange(v === "" ? undefined : v);
+ ...InputColor.propTypes,
}
render() {
- const offset = this.calcPickerOffset()
- var currentColor = this.color.object()
- currentColor = {
- r: currentColor.r,
- g: currentColor.g,
- b: currentColor.b,
- // Rename alpha -> a for ChromePicker
- a: currentColor.alpha
- }
+ const {props} = this;
- const picker =
-
this.onChangeNoCheck(formatColor(c))}
- />
-
-
-
- var swatchStyle = {
- backgroundColor: this.props.value
- };
-
- return
- {this.state.pickerOpened && picker}
-
-
this.colorInput = input}
- onClick={this.togglePicker}
- style={this.props.style}
- name={this.props.name}
- placeholder={this.props.default}
- value={this.props.value ? this.props.value : ""}
- onChange={(e) => this.onChange(e.target.value)}
- />
-
+ return
+
+
}
}
diff --git a/src/components/FieldComment.jsx b/src/components/FieldComment.jsx
index d64ca2e..9c6125e 100644
--- a/src/components/FieldComment.jsx
+++ b/src/components/FieldComment.jsx
@@ -2,9 +2,9 @@ import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
-import FieldString from './FieldString'
+import InputString from './InputString'
-export default class BlockComment extends React.Component {
+export default class FieldComment extends React.Component {
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
@@ -20,7 +20,7 @@ export default class BlockComment extends React.Component {
fieldSpec={fieldSpec}
data-wd-key="layer-comment"
>
-
}
}
-
diff --git a/src/components/FieldDynamicArray.jsx b/src/components/FieldDynamicArray.jsx
index c8f3b71..47bbbaf 100644
--- a/src/components/FieldDynamicArray.jsx
+++ b/src/components/FieldDynamicArray.jsx
@@ -1,136 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
-import FieldString from './FieldString'
-import FieldNumber from './FieldNumber'
-import Button from './Button'
-import {MdDelete} from 'react-icons/md'
-import FieldDocLabel from './FieldDocLabel'
-import FieldEnum from './FieldEnum'
-import capitalize from 'lodash.capitalize'
-import FieldUrl from './FieldUrl'
-
+import Block from './Block'
+import InputDynamicArray from './InputDynamicArray'
+import Fieldset from './Fieldset'
export default class FieldDynamicArray extends React.Component {
static propTypes = {
- value: PropTypes.array,
- type: PropTypes.string,
- default: PropTypes.array,
- onChange: PropTypes.func,
- style: PropTypes.object,
- fieldSpec: PropTypes.object,
- }
-
- changeValue(idx, newValue) {
- console.log(idx, newValue)
- const values = this.values.slice(0)
- values[idx] = newValue
- this.props.onChange(values)
- }
-
- get values() {
- return this.props.value || this.props.default || []
- }
-
- addValue = () => {
- const values = this.values.slice(0)
- if (this.props.type === 'number') {
- values.push(0)
- }
- else if (this.props.type === 'url') {
- values.push("");
- }
- else if (this.props.type === 'enum') {
- const {fieldSpec} = this.props;
- const defaultValue = Object.keys(fieldSpec.values)[0];
- values.push(defaultValue);
- } else {
- values.push("")
- }
-
- this.props.onChange(values)
- }
-
- deleteValue(valueIdx) {
- const values = this.values.slice(0)
- values.splice(valueIdx, 1)
-
- this.props.onChange(values)
+ ...InputDynamicArray.propTypes,
+ name: PropTypes.string,
}
render() {
- const inputs = this.values.map((v, i) => {
- const deleteValueBtn=
- let input;
- if(this.props.type === 'url') {
- input =
- }
- else if (this.props.type === 'number') {
- input =
- }
- else if (this.props.type === 'enum') {
- const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)]);
+ const {props} = this;
- input =
- }
- else {
- input =
- }
-
- return
-
- {deleteValueBtn}
-
-
- {input}
-
-
- })
-
- return
- {inputs}
-
- Add value
-
-
- }
-}
-
-class DeleteValueButton extends React.Component {
- static propTypes = {
- onClick: PropTypes.func,
- }
-
- render() {
- return
- }
- doc={"Remove array item."}
- />
-
+ return
+
+
}
}
diff --git a/src/components/FieldEnum.jsx b/src/components/FieldEnum.jsx
index a9e89a0..f4268ec 100644
--- a/src/components/FieldEnum.jsx
+++ b/src/components/FieldEnum.jsx
@@ -1,45 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
-import FieldSelect from './FieldSelect'
-import FieldMultiInput from './FieldMultiInput'
-
-
-function optionsLabelLength(options) {
- let sum = 0;
- options.forEach(([_, label]) => {
- sum += label.length
- })
- return sum
-}
+import InputEnum from './InputEnum'
+import Block from './Block';
+import Fieldset from './Fieldset';
export default class FieldEnum extends React.Component {
static propTypes = {
- "data-wd-key": PropTypes.string,
- value: PropTypes.string,
- style: PropTypes.object,
- default: PropTypes.string,
- name: PropTypes.string,
- onChange: PropTypes.func,
- options: PropTypes.array,
+ ...InputEnum.propTypes,
}
render() {
- const {options, value, onChange, name} = this.props;
+ const {props} = this;
- if(options.length <= 3 && optionsLabelLength(options) <= 20) {
- return
- } else {
- return
- }
+ return
+
+
}
}
diff --git a/src/components/FieldFunction.jsx b/src/components/FieldFunction.jsx
index 3cb25a2..ac5887b 100644
--- a/src/components/FieldFunction.jsx
+++ b/src/components/FieldFunction.jsx
@@ -315,6 +315,7 @@ export default class FieldFunction extends React.Component {
)
}
else if (dataType === "data_function") {
+ // TODO: Rename to FieldFunction **this file** shouldn't be called that
specField = (
-
}
}
-
diff --git a/src/components/FieldJson.jsx b/src/components/FieldJson.jsx
new file mode 100644
index 0000000..1f6ccea
--- /dev/null
+++ b/src/components/FieldJson.jsx
@@ -0,0 +1,16 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import InputJson from './InputJson'
+
+
+export default class FieldJson extends React.Component {
+ static propTypes = {
+ ...InputJson.propTypes,
+ }
+
+ render() {
+ const {props} = this;
+ return
+ }
+}
+
diff --git a/src/components/FieldMaxZoom.jsx b/src/components/FieldMaxZoom.jsx
index a032915..2d38ad3 100644
--- a/src/components/FieldMaxZoom.jsx
+++ b/src/components/FieldMaxZoom.jsx
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Block from './Block'
-import FieldNumber from './FieldNumber'
+import InputNumber from './InputNumber'
-export default class BlockMaxZoom extends React.Component {
+export default class FieldMaxZoom extends React.Component {
static propTypes = {
value: PropTypes.number,
onChange: PropTypes.func.isRequired,
@@ -17,7 +17,7 @@ export default class BlockMaxZoom extends React.Component {
error={this.props.error}
data-wd-key="max-zoom"
>
-
}
}
-
diff --git a/src/components/FieldMinZoom.jsx b/src/components/FieldMinZoom.jsx
index 99e2953..f0252d7 100644
--- a/src/components/FieldMinZoom.jsx
+++ b/src/components/FieldMinZoom.jsx
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Block from './Block'
-import FieldNumber from './FieldNumber'
+import InputNumber from './InputNumber'
-export default class BlockMinZoom extends React.Component {
+export default class FieldMinZoom extends React.Component {
static propTypes = {
value: PropTypes.number,
onChange: PropTypes.func.isRequired,
@@ -17,7 +17,7 @@ export default class BlockMinZoom extends React.Component {
error={this.props.error}
data-wd-key="min-zoom"
>
-
}
}
-
diff --git a/src/components/FieldMultiInput.jsx b/src/components/FieldMultiInput.jsx
index 94d7ba9..ae5daf6 100644
--- a/src/components/FieldMultiInput.jsx
+++ b/src/components/FieldMultiInput.jsx
@@ -1,41 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import Button from './Button'
+import Block from './Block'
+import InputMultiInput from './InputMultiInput'
+import Fieldset from './Fieldset'
+
export default class FieldMultiInput extends React.Component {
static propTypes = {
- name: PropTypes.string.isRequired,
- value: PropTypes.string.isRequired,
- options: PropTypes.array.isRequired,
- onChange: PropTypes.func.isRequired,
+ ...InputMultiInput.propTypes,
}
render() {
- let options = this.props.options
- if(options.length > 0 && !Array.isArray(options[0])) {
- options = options.map(v => [v, v])
- }
+ const {props} = this;
- const selectedValue = this.props.value || options[0][0]
- const radios = options.map(([val, label])=> {
- return
- this.props.onChange(val)}
- value={val}
- checked={val === selectedValue}
- />
- {label}
-
- })
-
- return
- {radios}
-
+ return
+
+
}
}
diff --git a/src/components/FieldNumber.jsx b/src/components/FieldNumber.jsx
index a74bb08..0d42ddf 100644
--- a/src/components/FieldNumber.jsx
+++ b/src/components/FieldNumber.jsx
@@ -1,232 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
+import InputNumber from './InputNumber'
+import Block from './Block'
-let IDX = 0;
export default class FieldNumber extends React.Component {
static propTypes = {
- value: PropTypes.number,
- default: PropTypes.number,
- min: PropTypes.number,
- max: PropTypes.number,
- onChange: PropTypes.func,
- allowRange: PropTypes.bool,
- rangeStep: PropTypes.number,
- wdKey: PropTypes.string,
- required: PropTypes.bool,
- }
-
- static defaultProps = {
- rangeStep: 1
- }
-
- constructor(props) {
- super(props)
- this.state = {
- uuid: IDX++,
- editing: false,
- value: props.value,
- dirtyValue: props.value,
- }
- }
-
- static getDerivedStateFromProps(props, state) {
- if (!state.editing && props.value !== state.value) {
- return {
- value: props.value,
- dirtyValue: props.value,
- };
- }
- return null;
- }
-
- changeValue(newValue) {
- const value = (newValue === "" || newValue === undefined) ?
- undefined :
- parseFloat(newValue);
-
- const hasChanged = this.props.value !== value;
- if(this.isValid(value) && hasChanged) {
- this.props.onChange(value)
- this.setState({
- value: newValue,
- });
- }
- else if (!this.isValid(value) && hasChanged) {
- this.setState({
- value: undefined,
- });
- }
-
- this.setState({
- dirtyValue: newValue === "" ? undefined : newValue,
- })
- }
-
- isValid(v) {
- if (v === undefined) {
- return true;
- }
-
- const value = parseFloat(v)
- if(isNaN(value)) {
- return false
- }
-
- if(!isNaN(this.props.min) && value < this.props.min) {
- return false
- }
-
- if(!isNaN(this.props.max) && value > this.props.max) {
- return false
- }
-
- return true
- }
-
- resetValue = () => {
- this.setState({editing: false});
- // Reset explicitly to default value if value has been cleared
- if(this.state.value === "") {
- return;
- }
-
- // If set value is invalid fall back to the last valid value from props or at last resort the default value
- if (!this.isValid(this.state.value)) {
- if(this.isValid(this.props.value)) {
- this.changeValue(this.props.value)
- this.setState({dirtyValue: this.props.value});
- } else {
- this.changeValue(undefined);
- this.setState({dirtyValue: undefined});
- }
- }
- }
-
- onChangeRange = (e) => {
- let value = parseFloat(e.target.value, 10);
- const step = this.props.rangeStep;
- let dirtyValue = value;
-
- if(step) {
- // Can't do this with the range step attribute else we won't be able to set a high precision value via the text input.
- const snap = value % step;
-
- // Round up/down to step
- if (this._keyboardEvent) {
- // If it's keyboard event we might get a low positive/negative value,
- // for example we might go from 13 to 13.23, however because we know
- // that came from a keyboard event we always want to increase by a
- // single step value.
- if (value < this.state.dirtyValue) {
- value = this.state.value - step;
- }
- else {
- value = this.state.value + step
- }
- dirtyValue = value;
- }
- else {
- if (snap < step/2) {
- value = value - snap;
- }
- else {
- value = value + (step - snap);
- };
- }
- }
-
- this._keyboardEvent = false;
-
- // Clamp between min/max
- value = Math.max(this.props.min, Math.min(this.props.max, value));
-
- this.setState({value, dirtyValue});
- this.props.onChange(value);
+ ...InputNumber.propTypes,
}
render() {
- if(
- this.props.hasOwnProperty("min") && this.props.hasOwnProperty("max") &&
- this.props.min !== undefined && this.props.max !== undefined &&
- this.props.allowRange
- ) {
- const value = this.state.editing ? this.state.dirtyValue : this.state.value;
- const defaultValue = this.props.default === undefined ? "" : this.props.default;
- let inputValue;
- if (this.state.editingRange) {
- inputValue = this.state.value;
- }
- else {
- inputValue = value;
- }
-
- return
- {
- this._keyboardEvent = true;
- }}
- onPointerDown={() => {
- this.setState({editing: true, editingRange: true});
- }}
- onPointerUp={() => {
- // Safari doesn't get onBlur event
- this.setState({editing: false, editingRange: false});
- }}
- onBlur={() => {
- this.setState({
- editing: false,
- editingRange: false,
- dirtyValue: this.state.value,
- });
- }}
- />
- {
- this.setState({editing: true});
- }}
- onChange={e => {
- this.changeValue(e.target.value);
- }}
- onBlur={e => {
- this.setState({editing: false});
- this.resetValue()
- }}
- />
-
- }
- else {
- const value = this.state.editing ? this.state.dirtyValue : this.state.value;
-
- return this.changeValue(e.target.value)}
- onFocus={() => {
- this.setState({editing: true});
- }}
- onBlur={this.resetValue}
- required={this.props.required}
- />
- }
+ const {props} = this;
+ return
+
+
}
}
diff --git a/src/components/FieldSelect.jsx b/src/components/FieldSelect.jsx
index fc54eee..1d279c6 100644
--- a/src/components/FieldSelect.jsx
+++ b/src/components/FieldSelect.jsx
@@ -1,33 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
+import Block from './Block'
+import InputSelect from './InputSelect'
+
export default class FieldSelect extends React.Component {
static propTypes = {
- value: PropTypes.string.isRequired,
- "data-wd-key": PropTypes.string,
- options: PropTypes.array.isRequired,
- style: PropTypes.object,
- onChange: PropTypes.func.isRequired,
- title: PropTypes.string,
+ ...InputSelect.propTypes,
}
-
render() {
- let options = this.props.options
- if(options.length > 0 && !Array.isArray(options[0])) {
- options = options.map(v => [v, v])
- }
+ const {props} = this;
- return this.props.onChange(e.target.value)}
- >
- { options.map(([val, label]) => {label} ) }
-
+ return
+
+
}
}
diff --git a/src/components/FieldSource.jsx b/src/components/FieldSource.jsx
index bcee37f..7dcc96d 100644
--- a/src/components/FieldSource.jsx
+++ b/src/components/FieldSource.jsx
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Block from './Block'
-import FieldAutocomplete from './FieldAutocomplete'
+import InputAutocomplete from './InputAutocomplete'
-export default class BlockSource extends React.Component {
+export default class FieldSource extends React.Component {
static propTypes = {
value: PropTypes.string,
wdKey: PropTypes.string,
@@ -26,7 +26,7 @@ export default class BlockSource extends React.Component {
error={this.props.error}
data-wd-key={this.props.wdKey}
>
- [src, src])}
@@ -34,4 +34,3 @@ export default class BlockSource extends React.Component {
}
}
-
diff --git a/src/components/FieldSourceLayer.jsx b/src/components/FieldSourceLayer.jsx
index ff617f4..895d43d 100644
--- a/src/components/FieldSourceLayer.jsx
+++ b/src/components/FieldSourceLayer.jsx
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Block from './Block'
-import FieldAutocomplete from './FieldAutocomplete'
+import InputAutocomplete from './InputAutocomplete'
-export default class BlockSourceLayer extends React.Component {
+export default class FieldSourceLayer extends React.Component {
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func,
@@ -23,7 +23,7 @@ export default class BlockSourceLayer extends React.Component {
return
-
}
}
-
diff --git a/src/components/FieldString.jsx b/src/components/FieldString.jsx
index d061d81..89d9327 100644
--- a/src/components/FieldString.jsx
+++ b/src/components/FieldString.jsx
@@ -1,92 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
+import Block from './Block'
+import InputString from './InputString'
export default class FieldString extends React.Component {
static propTypes = {
- "data-wd-key": PropTypes.string,
- value: PropTypes.string,
- style: PropTypes.object,
- default: PropTypes.string,
- onChange: PropTypes.func,
- onInput: PropTypes.func,
- multi: PropTypes.bool,
- required: PropTypes.bool,
- disabled: PropTypes.bool,
- spellCheck: PropTypes.bool,
- }
-
- static defaultProps = {
- onInput: () => {},
- }
-
- constructor(props) {
- super(props)
- this.state = {
- editing: false,
- value: props.value || ''
- }
- }
-
- static getDerivedStateFromProps(props, state) {
- if (!state.editing) {
- return {
- value: props.value
- };
- }
- return {};
+ ...InputString.propTypes,
+ name: PropTypes.string,
}
render() {
- let tag;
- let classes;
+ const {props} = this;
- if(!!this.props.multi) {
- tag = "textarea"
- classes = [
- "maputnik-string",
- "maputnik-string--multi"
- ]
- }
- else {
- tag = "input"
- classes = [
- "maputnik-string"
- ]
- }
-
- if(!!this.props.disabled) {
- classes.push("maputnik-string--disabled");
- }
-
- return React.createElement(tag, {
- "data-wd-key": this.props["data-wd-key"],
- spellCheck: this.props.hasOwnProperty("spellCheck") ? this.props.spellCheck : !(tag === "input"),
- disabled: this.props.disabled,
- className: classes.join(" "),
- style: this.props.style,
- value: this.state.value === undefined ? "" : this.state.value,
- placeholder: this.props.default,
- onChange: e => {
- this.setState({
- editing: true,
- value: e.target.value
- }, () => {
- this.props.onInput(this.state.value);
- });
- },
- onBlur: () => {
- if(this.state.value!==this.props.value) {
- this.setState({editing: false});
- this.props.onChange(this.state.value);
- }
- },
- onKeyDown: (e) => {
- if (e.keyCode === 13) {
- this.props.onChange(this.state.value);
- }
- },
- required: this.props.required,
- });
+ return
+
+
}
}
diff --git a/src/components/FieldType.jsx b/src/components/FieldType.jsx
index cc44523..d95c1c2 100644
--- a/src/components/FieldType.jsx
+++ b/src/components/FieldType.jsx
@@ -3,10 +3,10 @@ import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Block from './Block'
-import FieldSelect from './FieldSelect'
-import FieldString from './FieldString'
+import InputSelect from './InputSelect'
+import InputString from './InputString'
-export default class BlockType extends React.Component {
+export default class FieldType extends React.Component {
static propTypes = {
value: PropTypes.string.isRequired,
wdKey: PropTypes.string,
@@ -25,13 +25,13 @@ export default class BlockType extends React.Component {
error={this.props.error}
>
{this.props.disabled &&
-
}
{!this.props.disabled &&
-
}
}
-
diff --git a/src/components/FieldUrl.jsx b/src/components/FieldUrl.jsx
index 8132eb8..3c53b19 100644
--- a/src/components/FieldUrl.jsx
+++ b/src/components/FieldUrl.jsx
@@ -1,100 +1,21 @@
import React, {Fragment} from 'react'
import PropTypes from 'prop-types'
-import FieldString from './FieldString'
-import SmallError from './SmallError'
+import InputUrl from './InputUrl'
+import Block from './Block'
-function validate (url) {
- if (url === "") {
- return;
- }
-
- let error;
- const getProtocol = (url) => {
- try {
- const urlObj = new URL(url);
- return urlObj.protocol;
- }
- catch (err) {
- return undefined;
- }
- };
- const protocol = getProtocol(url);
- const isSsl = window.location.protocol === "https:";
-
- if (!protocol) {
- error = (
-
- Must provide protocol {
- isSsl
- ? https://
- : <>http://
or https://
>
- }
-
- );
- }
- else if (
- protocol &&
- protocol === "http:" &&
- window.location.protocol === "https:"
- ) {
- error = (
-
- CORS policy won't allow fetching resources served over http from https, use a https://
domain
-
- );
- }
-
- return error;
-}
-
export default class FieldUrl extends React.Component {
static propTypes = {
- "data-wd-key": PropTypes.string,
- value: PropTypes.string,
- style: PropTypes.object,
- default: PropTypes.string,
- onChange: PropTypes.func,
- onInput: PropTypes.func,
- multi: PropTypes.bool,
- required: PropTypes.bool,
- }
-
- static defaultProps = {
- onInput: () => {},
- }
-
- constructor (props) {
- super(props);
- this.state = {
- error: validate(props.value)
- };
- }
-
- onInput = (url) => {
- this.setState({
- error: validate(url)
- });
- this.props.onInput(url);
- }
-
- onChange = (url) => {
- this.setState({
- error: validate(url)
- });
- this.props.onChange(url);
+ ...InputUrl.propTypes,
}
render () {
+ const {props} = this;
+
return (
-
-
- {this.state.error}
-
+
+
+
);
}
}
diff --git a/src/components/Fieldset.jsx b/src/components/Fieldset.jsx
new file mode 100644
index 0000000..4b7cada
--- /dev/null
+++ b/src/components/Fieldset.jsx
@@ -0,0 +1,23 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+
+let IDX = 0;
+
+export default class Fieldset extends React.Component {
+ constructor (props) {
+ super(props);
+ this._labelId = `fieldset_label_${(IDX++)}`;
+ }
+
+ render () {
+ const {props} = this;
+
+ return
+
{props.label}
+
+ {props.children}
+
+
+ }
+}
diff --git a/src/components/FilterEditor.jsx b/src/components/FilterEditor.jsx
index 55a04fd..d99d8a9 100644
--- a/src/components/FilterEditor.jsx
+++ b/src/components/FilterEditor.jsx
@@ -4,11 +4,11 @@ import { combiningFilterOps } from '../libs/filterops.js'
import {mdiTableRowPlusAfter} from '@mdi/js';
import {latest, validate, migrate} from '@mapbox/mapbox-gl-style-spec'
-import FieldSelect from './FieldSelect'
+import InputSelect from './InputSelect'
import Block from './Block'
import SingleFilterEditor from './SingleFilterEditor'
import FilterEditorBlock from './FilterEditorBlock'
-import Button from './Button'
+import InputButton from './InputButton'
import Doc from './Doc'
import ExpressionProperty from './_ExpressionProperty';
import {mdiFunctionVariant} from '@mdi/js';
@@ -191,7 +191,7 @@ export default class FilterEditor extends React.Component {
Nested filters are not supported.
-
@@ -199,7 +199,7 @@ export default class FilterEditor extends React.Component {
Upgrade to expression
-
+
}
else if (displaySimpleFilter) {
@@ -209,7 +209,7 @@ export default class FilterEditor extends React.Component {
const actions = (
);
@@ -249,7 +249,7 @@ export default class FilterEditor extends React.Component {
label={"Filter"}
action={actions}
>
-
-
Add filter
-
+
-
-
+
{this.props.children}
diff --git a/src/components/InputArray.jsx b/src/components/InputArray.jsx
new file mode 100644
index 0000000..dc24362
--- /dev/null
+++ b/src/components/InputArray.jsx
@@ -0,0 +1,113 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import InputString from './InputString'
+import InputNumber from './InputNumber'
+
+export default class FieldArray extends React.Component {
+ static propTypes = {
+ value: PropTypes.array,
+ type: PropTypes.string,
+ length: PropTypes.number,
+ default: PropTypes.array,
+ onChange: PropTypes.func,
+ 'aria-label': PropTypes.string,
+ }
+
+ static defaultProps = {
+ value: [],
+ default: [],
+ }
+
+ constructor (props) {
+ super(props);
+ this.state = {
+ value: this.props.value.slice(0),
+ // This is so we can compare changes in getDerivedStateFromProps
+ initialPropsValue: this.props.value.slice(0),
+ };
+ }
+
+ static getDerivedStateFromProps(props, state) {
+ const value = [];
+ const initialPropsValue = state.initialPropsValue.slice(0);
+
+ Array(props.length).fill(null).map((_, i) => {
+ if (props.value[i] === state.initialPropsValue[i]) {
+ value[i] = state.value[i];
+ }
+ else {
+ value[i] = state.value[i];
+ initialPropsValue[i] = state.value[i];
+ }
+ })
+
+ return {
+ value,
+ initialPropsValue,
+ };
+ }
+
+ isComplete (value) {
+ return Array(this.props.length).fill(null).every((_, i) => {
+ const val = value[i]
+ return !(val === undefined || val === "");
+ });
+ }
+
+ changeValue(idx, newValue) {
+ const value = this.state.value.slice(0);
+ value[idx] = newValue;
+
+ this.setState({
+ value,
+ }, () => {
+ if (this.isComplete(value)) {
+ this.props.onChange(value);
+ }
+ else {
+ // Unset until complete
+ this.props.onChange(undefined);
+ }
+ });
+ }
+
+ render() {
+ const {value} = this.state;
+
+ const containsValues = (
+ value.length > 0 &&
+ !value.every(val => {
+ return (val === "" || val === undefined)
+ })
+ );
+
+ const inputs = Array(this.props.length).fill(null).map((_, i) => {
+ if(this.props.type === 'number') {
+ return
+ } else {
+ return
+ }
+ })
+
+ return (
+
+ {inputs}
+
+ )
+ }
+}
+
diff --git a/src/components/InputAutocomplete.jsx b/src/components/InputAutocomplete.jsx
new file mode 100644
index 0000000..8d2027b
--- /dev/null
+++ b/src/components/InputAutocomplete.jsx
@@ -0,0 +1,100 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Autocomplete from 'react-autocomplete'
+
+
+const MAX_HEIGHT = 140;
+
+export default class InputAutocomplete extends React.Component {
+ static propTypes = {
+ value: PropTypes.string,
+ options: PropTypes.array,
+ onChange: PropTypes.func,
+ keepMenuWithinWindowBounds: PropTypes.bool,
+ 'aria-label': PropTypes.string,
+ }
+
+ state = {
+ maxHeight: MAX_HEIGHT
+ }
+
+ static defaultProps = {
+ onChange: () => {},
+ options: [],
+ }
+
+ calcMaxHeight() {
+ if(this.props.keepMenuWithinWindowBounds) {
+ const maxHeight = window.innerHeight - this.autocompleteMenuEl.getBoundingClientRect().top;
+ const limitedMaxHeight = Math.min(maxHeight, MAX_HEIGHT);
+
+ if(limitedMaxHeight != this.state.maxHeight) {
+ this.setState({
+ maxHeight: limitedMaxHeight
+ })
+ }
+ }
+ }
+
+ componentDidMount() {
+ this.calcMaxHeight();
+ }
+
+ componentDidUpdate() {
+ this.calcMaxHeight();
+ }
+
+ onChange (v) {
+ this.props.onChange(v === "" ? undefined : v);
+ }
+
+ render() {
+ return
{
+ this.autocompleteMenuEl = el;
+ }}
+ >
+
item[0]}
+ onSelect={v => this.onChange(v)}
+ onChange={(e, v) => this.onChange(v)}
+ shouldItemRender={(item, value="") => {
+ if (typeof(value) === "string") {
+ return item[0].toLowerCase().indexOf(value.toLowerCase()) > -1
+ }
+ }}
+ renderItem={(item, isHighlighted) => (
+
+ {item[1]}
+
+ )}
+ />
+
+ }
+}
+
+
diff --git a/src/components/Button.jsx b/src/components/InputButton.jsx
similarity index 93%
rename from src/components/Button.jsx
rename to src/components/InputButton.jsx
index cbddd5a..e9c4b23 100644
--- a/src/components/Button.jsx
+++ b/src/components/InputButton.jsx
@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-class Button extends React.Component {
+export default class InputButton extends React.Component {
static propTypes = {
"data-wd-key": PropTypes.string,
"aria-label": PropTypes.string,
@@ -33,4 +33,3 @@ class Button extends React.Component {
}
}
-export default Button
diff --git a/src/components/InputCheckbox.jsx b/src/components/InputCheckbox.jsx
new file mode 100644
index 0000000..e01d99c
--- /dev/null
+++ b/src/components/InputCheckbox.jsx
@@ -0,0 +1,34 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+export default class InputCheckbox extends React.Component {
+ static propTypes = {
+ value: PropTypes.bool,
+ style: PropTypes.object,
+ onChange: PropTypes.func,
+ }
+
+ static defaultProps = {
+ value: false,
+ }
+
+ render() {
+ return
+ this.props.onChange(!this.props.value)}
+ checked={this.props.value}
+ />
+
+
+ }
+}
+
diff --git a/src/components/InputColor.jsx b/src/components/InputColor.jsx
new file mode 100644
index 0000000..02fb096
--- /dev/null
+++ b/src/components/InputColor.jsx
@@ -0,0 +1,135 @@
+import React from 'react'
+import Color from 'color'
+import ChromePicker from 'react-color/lib/components/chrome/Chrome'
+import PropTypes from 'prop-types'
+import lodash from 'lodash';
+
+function formatColor(color) {
+ const rgb = color.rgb
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`
+}
+
+/*** Number fields with support for min, max and units and documentation*/
+export default class InputColor extends React.Component {
+ static propTypes = {
+ onChange: PropTypes.func.isRequired,
+ name: PropTypes.string,
+ value: PropTypes.string,
+ doc: PropTypes.string,
+ style: PropTypes.object,
+ default: PropTypes.string,
+ 'aria-label': PropTypes.string,
+ }
+
+ state = {
+ pickerOpened: false
+ }
+
+ constructor () {
+ super();
+ this.onChangeNoCheck = lodash.throttle(this.onChangeNoCheck, 1000/30);
+ }
+
+ onChangeNoCheck (v) {
+ this.props.onChange(v);
+ }
+
+ //TODO: I much rather would do this with absolute positioning
+ //but I am too stupid to get it to work together with fixed position
+ //and scrollbars so I have to fallback to JavaScript
+ calcPickerOffset = () => {
+ const elem = this.colorInput
+ if(elem) {
+ const pos = elem.getBoundingClientRect()
+ return {
+ top: pos.top,
+ left: pos.left + 196,
+ }
+ } else {
+ return {
+ top: 160,
+ left: 555,
+ }
+ }
+ }
+
+ togglePicker = () => {
+ this.setState({ pickerOpened: !this.state.pickerOpened })
+ }
+
+ get color() {
+ // Catch invalid color.
+ try {
+ return Color(this.props.value).rgb()
+ }
+ catch(err) {
+ console.warn("Error parsing color: ", err);
+ return Color("rgb(255,255,255)");
+ }
+ }
+
+ onChange (v) {
+ this.props.onChange(v === "" ? undefined : v);
+ }
+
+ render() {
+ const offset = this.calcPickerOffset()
+ var currentColor = this.color.object()
+ currentColor = {
+ r: currentColor.r,
+ g: currentColor.g,
+ b: currentColor.b,
+ // Rename alpha -> a for ChromePicker
+ a: currentColor.alpha
+ }
+
+ const picker =
+
this.onChangeNoCheck(formatColor(c))}
+ />
+
+
+
+ var swatchStyle = {
+ backgroundColor: this.props.value
+ };
+
+ return
+ {this.state.pickerOpened && picker}
+
+
this.colorInput = input}
+ onClick={this.togglePicker}
+ style={this.props.style}
+ name={this.props.name}
+ placeholder={this.props.default}
+ value={this.props.value ? this.props.value : ""}
+ onChange={(e) => this.onChange(e.target.value)}
+ />
+
+ }
+}
+
diff --git a/src/components/InputDynamicArray.jsx b/src/components/InputDynamicArray.jsx
new file mode 100644
index 0000000..05d9a26
--- /dev/null
+++ b/src/components/InputDynamicArray.jsx
@@ -0,0 +1,138 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import InputString from './InputString'
+import InputNumber from './InputNumber'
+import InputButton from './InputButton'
+import {MdDelete} from 'react-icons/md'
+import FieldDocLabel from './FieldDocLabel'
+import InputEnum from './InputEnum'
+import capitalize from 'lodash.capitalize'
+import InputUrl from './InputUrl'
+
+
+export default class FieldDynamicArray extends React.Component {
+ static propTypes = {
+ value: PropTypes.array,
+ type: PropTypes.string,
+ default: PropTypes.array,
+ onChange: PropTypes.func,
+ style: PropTypes.object,
+ fieldSpec: PropTypes.object,
+ 'aria-label': PropTypes.string,
+ }
+
+ changeValue(idx, newValue) {
+ const values = this.values.slice(0)
+ values[idx] = newValue
+ this.props.onChange(values)
+ }
+
+ get values() {
+ return this.props.value || this.props.default || []
+ }
+
+ addValue = () => {
+ const values = this.values.slice(0)
+ if (this.props.type === 'number') {
+ values.push(0)
+ }
+ else if (this.props.type === 'url') {
+ values.push("");
+ }
+ else if (this.props.type === 'enum') {
+ const {fieldSpec} = this.props;
+ const defaultValue = Object.keys(fieldSpec.values)[0];
+ values.push(defaultValue);
+ } else {
+ values.push("")
+ }
+
+ this.props.onChange(values)
+ }
+
+ deleteValue(valueIdx) {
+ const values = this.values.slice(0)
+ values.splice(valueIdx, 1)
+
+ this.props.onChange(values)
+ }
+
+ render() {
+ const inputs = this.values.map((v, i) => {
+ const deleteValueBtn=
+ let input;
+ if(this.props.type === 'url') {
+ input =
+ }
+ else if (this.props.type === 'number') {
+ input =
+ }
+ else if (this.props.type === 'enum') {
+ const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)]);
+
+ input =
+ }
+ else {
+ input =
+ }
+
+ return
+
+ {deleteValueBtn}
+
+
+ {input}
+
+
+ })
+
+ return (
+
+ {inputs}
+
+ Add value
+
+
+ );
+ }
+}
+
+class DeleteValueInputButton extends React.Component {
+ static propTypes = {
+ onClick: PropTypes.func,
+ }
+
+ render() {
+ return
+ }
+ doc={"Remove array item."}
+ />
+
+ }
+}
+
diff --git a/src/components/InputEnum.jsx b/src/components/InputEnum.jsx
new file mode 100644
index 0000000..f066949
--- /dev/null
+++ b/src/components/InputEnum.jsx
@@ -0,0 +1,49 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import InputSelect from './InputSelect'
+import InputMultiInput from './InputMultiInput'
+
+
+function optionsLabelLength(options) {
+ let sum = 0;
+ options.forEach(([_, label]) => {
+ sum += label.length
+ })
+ return sum
+}
+
+
+export default class InputEnum extends React.Component {
+ static propTypes = {
+ "data-wd-key": PropTypes.string,
+ value: PropTypes.string,
+ style: PropTypes.object,
+ default: PropTypes.string,
+ name: PropTypes.string,
+ onChange: PropTypes.func,
+ options: PropTypes.array,
+ 'aria-label': PropTypes.string,
+ }
+
+ render() {
+ const {options, value, onChange, name} = this.props;
+
+ if(options.length <= 3 && optionsLabelLength(options) <= 20) {
+ return
+ } else {
+ return
+ }
+ }
+}
+
diff --git a/src/components/InputFont.jsx b/src/components/InputFont.jsx
new file mode 100644
index 0000000..9fc897b
--- /dev/null
+++ b/src/components/InputFont.jsx
@@ -0,0 +1,61 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import InputAutocomplete from './InputAutocomplete'
+
+export default class FieldFont extends React.Component {
+ static propTypes = {
+ value: PropTypes.array,
+ default: PropTypes.array,
+ fonts: PropTypes.array,
+ style: PropTypes.object,
+ onChange: PropTypes.func.isRequired,
+ 'aria-label': PropTypes.string,
+ }
+
+ static defaultProps = {
+ fonts: []
+ }
+
+ get values() {
+ const out = this.props.value || this.props.default || [];
+
+ // Always put a "" in the last field to you can keep adding entries
+ if (out[out.length-1] !== ""){
+ return out.concat("");
+ }
+ else {
+ return out;
+ }
+ }
+
+ changeFont(idx, newValue) {
+ const changedValues = this.values.slice(0)
+ changedValues[idx] = newValue
+ const filteredValues = changedValues
+ .filter(v => v !== undefined)
+ .filter(v => v !== "")
+
+ this.props.onChange(filteredValues);
+ }
+
+ render() {
+ const inputs = this.values.map((value, i) => {
+ return
+ [f, f])}
+ onChange={this.changeFont.bind(this, i)}
+ />
+
+ })
+
+ return (
+
+ );
+ }
+}
diff --git a/src/components/FieldJsonEditor.jsx b/src/components/InputJson.jsx
similarity index 98%
rename from src/components/FieldJsonEditor.jsx
rename to src/components/InputJson.jsx
index a32774c..54bac98 100644
--- a/src/components/FieldJsonEditor.jsx
+++ b/src/components/InputJson.jsx
@@ -16,7 +16,7 @@ import stringifyPretty from 'json-stringify-pretty-compact'
import '../util/codemirror-mgl';
-export default class FieldJsonEditor extends React.Component {
+export default class InputJson extends React.Component {
static propTypes = {
layer: PropTypes.any.isRequired,
maxHeight: PropTypes.number,
@@ -172,4 +172,3 @@ export default class FieldJsonEditor extends React.Component {
}
}
-
diff --git a/src/components/InputMultiInput.jsx b/src/components/InputMultiInput.jsx
new file mode 100644
index 0000000..a9d2aad
--- /dev/null
+++ b/src/components/InputMultiInput.jsx
@@ -0,0 +1,42 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import InputButton from './InputButton'
+
+export default class InputMultiInput extends React.Component {
+ static propTypes = {
+ name: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ options: PropTypes.array.isRequired,
+ onChange: PropTypes.func.isRequired,
+ }
+
+ render() {
+ let options = this.props.options
+ if(options.length > 0 && !Array.isArray(options[0])) {
+ options = options.map(v => [v, v])
+ }
+
+ const selectedValue = this.props.value || options[0][0]
+ const radios = options.map(([val, label])=> {
+ return
+ this.props.onChange(val)}
+ value={val}
+ checked={val === selectedValue}
+ />
+ {label}
+
+ })
+
+ return
+ {radios}
+
+ }
+}
+
+
diff --git a/src/components/InputNumber.jsx b/src/components/InputNumber.jsx
new file mode 100644
index 0000000..d660daf
--- /dev/null
+++ b/src/components/InputNumber.jsx
@@ -0,0 +1,235 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+let IDX = 0;
+
+export default class InputNumber extends React.Component {
+ static propTypes = {
+ value: PropTypes.number,
+ default: PropTypes.number,
+ min: PropTypes.number,
+ max: PropTypes.number,
+ onChange: PropTypes.func,
+ allowRange: PropTypes.bool,
+ rangeStep: PropTypes.number,
+ wdKey: PropTypes.string,
+ required: PropTypes.bool,
+ "aria-label": PropTypes.string,
+ }
+
+ static defaultProps = {
+ rangeStep: 1
+ }
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ uuid: IDX++,
+ editing: false,
+ value: props.value,
+ dirtyValue: props.value,
+ }
+ }
+
+ static getDerivedStateFromProps(props, state) {
+ if (!state.editing && props.value !== state.value) {
+ return {
+ value: props.value,
+ dirtyValue: props.value,
+ };
+ }
+ return null;
+ }
+
+ changeValue(newValue) {
+ const value = (newValue === "" || newValue === undefined) ?
+ undefined :
+ parseFloat(newValue);
+
+ const hasChanged = this.props.value !== value;
+ if(this.isValid(value) && hasChanged) {
+ this.props.onChange(value)
+ this.setState({
+ value: newValue,
+ });
+ }
+ else if (!this.isValid(value) && hasChanged) {
+ this.setState({
+ value: undefined,
+ });
+ }
+
+ this.setState({
+ dirtyValue: newValue === "" ? undefined : newValue,
+ })
+ }
+
+ isValid(v) {
+ if (v === undefined) {
+ return true;
+ }
+
+ const value = parseFloat(v)
+ if(isNaN(value)) {
+ return false
+ }
+
+ if(!isNaN(this.props.min) && value < this.props.min) {
+ return false
+ }
+
+ if(!isNaN(this.props.max) && value > this.props.max) {
+ return false
+ }
+
+ return true
+ }
+
+ resetValue = () => {
+ this.setState({editing: false});
+ // Reset explicitly to default value if value has been cleared
+ if(this.state.value === "") {
+ return;
+ }
+
+ // If set value is invalid fall back to the last valid value from props or at last resort the default value
+ if (!this.isValid(this.state.value)) {
+ if(this.isValid(this.props.value)) {
+ this.changeValue(this.props.value)
+ this.setState({dirtyValue: this.props.value});
+ } else {
+ this.changeValue(undefined);
+ this.setState({dirtyValue: undefined});
+ }
+ }
+ }
+
+ onChangeRange = (e) => {
+ let value = parseFloat(e.target.value, 10);
+ const step = this.props.rangeStep;
+ let dirtyValue = value;
+
+ if(step) {
+ // Can't do this with the
range step attribute else we won't be able to set a high precision value via the text input.
+ const snap = value % step;
+
+ // Round up/down to step
+ if (this._keyboardEvent) {
+ // If it's keyboard event we might get a low positive/negative value,
+ // for example we might go from 13 to 13.23, however because we know
+ // that came from a keyboard event we always want to increase by a
+ // single step value.
+ if (value < this.state.dirtyValue) {
+ value = this.state.value - step;
+ }
+ else {
+ value = this.state.value + step
+ }
+ dirtyValue = value;
+ }
+ else {
+ if (snap < step/2) {
+ value = value - snap;
+ }
+ else {
+ value = value + (step - snap);
+ };
+ }
+ }
+
+ this._keyboardEvent = false;
+
+ // Clamp between min/max
+ value = Math.max(this.props.min, Math.min(this.props.max, value));
+
+ this.setState({value, dirtyValue});
+ this.props.onChange(value);
+ }
+
+ render() {
+ if(
+ this.props.hasOwnProperty("min") && this.props.hasOwnProperty("max") &&
+ this.props.min !== undefined && this.props.max !== undefined &&
+ this.props.allowRange
+ ) {
+ const value = this.state.editing ? this.state.dirtyValue : this.state.value;
+ const defaultValue = this.props.default === undefined ? "" : this.props.default;
+ let inputValue;
+ if (this.state.editingRange) {
+ inputValue = this.state.value;
+ }
+ else {
+ inputValue = value;
+ }
+
+ return
+ {
+ this._keyboardEvent = true;
+ }}
+ onPointerDown={() => {
+ this.setState({editing: true, editingRange: true});
+ }}
+ onPointerUp={() => {
+ // Safari doesn't get onBlur event
+ this.setState({editing: false, editingRange: false});
+ }}
+ onBlur={() => {
+ this.setState({
+ editing: false,
+ editingRange: false,
+ dirtyValue: this.state.value,
+ });
+ }}
+ />
+ {
+ this.setState({editing: true});
+ }}
+ onChange={e => {
+ this.changeValue(e.target.value);
+ }}
+ onBlur={e => {
+ this.setState({editing: false});
+ this.resetValue()
+ }}
+ />
+
+ }
+ else {
+ const value = this.state.editing ? this.state.dirtyValue : this.state.value;
+
+ return
this.changeValue(e.target.value)}
+ onFocus={() => {
+ this.setState({editing: true});
+ }}
+ onBlur={this.resetValue}
+ required={this.props.required}
+ />
+ }
+ }
+}
+
+
diff --git a/src/components/InputSelect.jsx b/src/components/InputSelect.jsx
new file mode 100644
index 0000000..a138736
--- /dev/null
+++ b/src/components/InputSelect.jsx
@@ -0,0 +1,36 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+export default class InputSelect extends React.Component {
+ static propTypes = {
+ value: PropTypes.string.isRequired,
+ "data-wd-key": PropTypes.string,
+ options: PropTypes.array.isRequired,
+ style: PropTypes.object,
+ onChange: PropTypes.func.isRequired,
+ title: PropTypes.string,
+ 'aria-label': PropTypes.string,
+ }
+
+
+ render() {
+ let options = this.props.options
+ if(options.length > 0 && !Array.isArray(options[0])) {
+ options = options.map(v => [v, v])
+ }
+
+ return
this.props.onChange(e.target.value)}
+ aria-label={this.props['aria-label']}
+ >
+ { options.map(([val, label]) => {label} ) }
+
+ }
+}
+
+
diff --git a/src/components/InputSpec.jsx b/src/components/InputSpec.jsx
new file mode 100644
index 0000000..1e470b2
--- /dev/null
+++ b/src/components/InputSpec.jsx
@@ -0,0 +1,139 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import InputColor from './InputColor'
+import InputNumber from './InputNumber'
+import InputCheckbox from './InputCheckbox'
+import InputString from './InputString'
+import InputSelect from './InputSelect'
+import InputMultiInput from './InputMultiInput'
+import InputArray from './InputArray'
+import InputDynamicArray from './InputDynamicArray'
+import InputFont from './InputFont'
+import InputAutocomplete from './InputAutocomplete'
+import InputEnum from './InputEnum'
+import capitalize from 'lodash.capitalize'
+
+const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
+
+function labelFromFieldName(fieldName) {
+ let label = fieldName.split('-').slice(1).join(' ')
+ if(label.length > 0) {
+ label = label.charAt(0).toUpperCase() + label.slice(1);
+ }
+ return label
+}
+
+function optionsLabelLength(options) {
+ let sum = 0;
+ options.forEach(([_, label]) => {
+ sum += label.length
+ })
+ return sum
+}
+
+/** Display any field from the Mapbox GL style spec and
+ * choose the correct field component based on the @{fieldSpec}
+ * to display @{value}. */
+export default class SpecField extends React.Component {
+ static propTypes = {
+ onChange: PropTypes.func.isRequired,
+ fieldName: PropTypes.string.isRequired,
+ fieldSpec: PropTypes.object.isRequired,
+ value: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.array,
+ PropTypes.bool
+ ]),
+ /** Override the style of the field */
+ style: PropTypes.object,
+ 'aria-label': PropTypes.string,
+ }
+
+ render() {
+ const commonProps = {
+ error: this.props.error,
+ fieldSpec: this.props.fieldSpec,
+ label: this.props.label,
+ action: this.props.action,
+ style: this.props.style,
+ value: this.props.value,
+ default: this.props.fieldSpec.default,
+ name: this.props.fieldName,
+ onChange: newValue => this.props.onChange(this.props.fieldName, newValue),
+ 'aria-label': this.props['aria-label'],
+ }
+
+ function childNodes() {
+ switch(this.props.fieldSpec.type) {
+ case 'number': return (
+
+ )
+ case 'enum':
+ const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)])
+
+ return
+ case 'resolvedImage':
+ case 'formatted':
+ case 'string':
+ if (iconProperties.indexOf(this.props.fieldName) >= 0) {
+ const options = this.props.fieldSpec.values || [];
+ return
[f, f])}
+ />
+ } else {
+ return
+ }
+ case 'color': return (
+
+ )
+ case 'boolean': return (
+
+ )
+ case 'array':
+ if(this.props.fieldName === 'text-font') {
+ return
+ } else {
+ if (this.props.fieldSpec.length) {
+ return
+ } else {
+ return
+ }
+ }
+ default: return null
+ }
+ }
+
+ return (
+
+ {childNodes.call(this)}
+
+ );
+ }
+}
diff --git a/src/components/InputString.jsx b/src/components/InputString.jsx
new file mode 100644
index 0000000..3efd93d
--- /dev/null
+++ b/src/components/InputString.jsx
@@ -0,0 +1,95 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+export default class InputString extends React.Component {
+ static propTypes = {
+ "data-wd-key": PropTypes.string,
+ value: PropTypes.string,
+ style: PropTypes.object,
+ default: PropTypes.string,
+ onChange: PropTypes.func,
+ onInput: PropTypes.func,
+ multi: PropTypes.bool,
+ required: PropTypes.bool,
+ disabled: PropTypes.bool,
+ spellCheck: PropTypes.bool,
+ 'aria-label': PropTypes.string,
+ }
+
+ static defaultProps = {
+ onInput: () => {},
+ }
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ editing: false,
+ value: props.value || ''
+ }
+ }
+
+ static getDerivedStateFromProps(props, state) {
+ if (!state.editing) {
+ return {
+ value: props.value
+ };
+ }
+ return {};
+ }
+
+ render() {
+ let tag;
+ let classes;
+
+ if(!!this.props.multi) {
+ tag = "textarea"
+ classes = [
+ "maputnik-string",
+ "maputnik-string--multi"
+ ]
+ }
+ else {
+ tag = "input"
+ classes = [
+ "maputnik-string"
+ ]
+ }
+
+ if(!!this.props.disabled) {
+ classes.push("maputnik-string--disabled");
+ }
+
+ return React.createElement(tag, {
+ "aria-label": this.props["aria-label"],
+ "data-wd-key": this.props["data-wd-key"],
+ spellCheck: this.props.hasOwnProperty("spellCheck") ? this.props.spellCheck : !(tag === "input"),
+ disabled: this.props.disabled,
+ className: classes.join(" "),
+ style: this.props.style,
+ value: this.state.value === undefined ? "" : this.state.value,
+ placeholder: this.props.default,
+ onChange: e => {
+ this.setState({
+ editing: true,
+ value: e.target.value
+ }, () => {
+ this.props.onInput(this.state.value);
+ });
+ },
+ onBlur: () => {
+ if(this.state.value!==this.props.value) {
+ this.setState({editing: false});
+ this.props.onChange(this.state.value);
+ }
+ },
+ onKeyDown: (e) => {
+ if (e.keyCode === 13) {
+ this.props.onChange(this.state.value);
+ }
+ },
+ required: this.props.required,
+ });
+ }
+}
+
+
diff --git a/src/components/InputUrl.jsx b/src/components/InputUrl.jsx
new file mode 100644
index 0000000..dd62caa
--- /dev/null
+++ b/src/components/InputUrl.jsx
@@ -0,0 +1,103 @@
+import React, {Fragment} from 'react'
+import PropTypes from 'prop-types'
+import InputString from './InputString'
+import SmallError from './SmallError'
+
+
+function validate (url) {
+ if (url === "") {
+ return;
+ }
+
+ let error;
+ const getProtocol = (url) => {
+ try {
+ const urlObj = new URL(url);
+ return urlObj.protocol;
+ }
+ catch (err) {
+ return undefined;
+ }
+ };
+ const protocol = getProtocol(url);
+ const isSsl = window.location.protocol === "https:";
+
+ if (!protocol) {
+ error = (
+
+ Must provide protocol {
+ isSsl
+ ? https://
+ : <>http://
or https://
>
+ }
+
+ );
+ }
+ else if (
+ protocol &&
+ protocol === "http:" &&
+ window.location.protocol === "https:"
+ ) {
+ error = (
+
+ CORS policy won't allow fetching resources served over http from https, use a https://
domain
+
+ );
+ }
+
+ return error;
+}
+
+export default class FieldUrl extends React.Component {
+ static propTypes = {
+ "data-wd-key": PropTypes.string,
+ value: PropTypes.string,
+ style: PropTypes.object,
+ default: PropTypes.string,
+ onChange: PropTypes.func,
+ onInput: PropTypes.func,
+ multi: PropTypes.bool,
+ required: PropTypes.bool,
+ 'aria-label': PropTypes.string,
+ }
+
+ static defaultProps = {
+ onInput: () => {},
+ }
+
+ constructor (props) {
+ super(props);
+ this.state = {
+ error: validate(props.value)
+ };
+ }
+
+ onInput = (url) => {
+ this.setState({
+ error: validate(url)
+ });
+ this.props.onInput(url);
+ }
+
+ onChange = (url) => {
+ this.setState({
+ error: validate(url)
+ });
+ this.props.onChange(url);
+ }
+
+ render () {
+ return (
+
+
+ {this.state.error}
+
+ );
+ }
+}
+
diff --git a/src/components/LayerEditor.jsx b/src/components/LayerEditor.jsx
index 5868fd5..1479ffc 100644
--- a/src/components/LayerEditor.jsx
+++ b/src/components/LayerEditor.jsx
@@ -2,17 +2,17 @@ import React from 'react'
import PropTypes from 'prop-types'
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
-import FieldJsonEditor from './FieldJsonEditor'
+import FieldJson from './FieldJson'
import FilterEditor from './FilterEditor'
import PropertyGroup from './PropertyGroup'
import LayerEditorGroup from './LayerEditorGroup'
-import BlockType from './BlockType'
-import BlockId from './BlockId'
-import BlockMinZoom from './BlockMinZoom'
-import BlockMaxZoom from './BlockMaxZoom'
-import BlockComment from './BlockComment'
-import BlockSource from './BlockSource'
-import BlockSourceLayer from './BlockSourceLayer'
+import FieldType from './FieldType'
+import FieldId from './FieldId'
+import FieldMinZoom from './FieldMinZoom'
+import FieldMaxZoom from './FieldMaxZoom'
+import FieldComment from './FieldComment'
+import FieldSource from './FieldSource'
+import FieldSourceLayer from './FieldSourceLayer'
import {Accordion} from 'react-accessible-accordion';
import {MdMoreVert} from 'react-icons/md'
@@ -152,13 +152,13 @@ export default class LayerEditor extends React.Component {
switch(type) {
case 'layer': return
- this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)}
/>
-
- {this.props.layer.type !== 'background' &&
}
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 &&
- this.changeProperty(null, 'source-layer', v)}
/>
}
- this.changeProperty(null, 'minzoom', v)}
/>
- this.changeProperty(null, 'maxzoom', v)}
/>
- this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)}
@@ -208,22 +208,24 @@ export default class LayerEditor extends React.Component {
/>
- case 'properties': return
- case 'jsoneditor': return {
- this.props.onLayerChanged(
- this.props.layerIndex,
- layer
- );
- }}
- />
+ case 'properties':
+ return
+ case 'jsoneditor':
+ return {
+ this.props.onLayerChanged(
+ this.props.layerIndex,
+ layer
+ );
+ }}
+ />
}
}
diff --git a/src/components/MapMapboxGl.jsx b/src/components/MapMapboxGl.jsx
index ea08e85..1269934 100644
--- a/src/components/MapMapboxGl.jsx
+++ b/src/components/MapMapboxGl.jsx
@@ -237,3 +237,4 @@ export default class MapMapboxGl extends React.Component {
}
}
}
+
diff --git a/src/components/ModalAdd.jsx b/src/components/ModalAdd.jsx
index 0ed508a..ff4ff70 100644
--- a/src/components/ModalAdd.jsx
+++ b/src/components/ModalAdd.jsx
@@ -1,13 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Button from './Button'
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+import InputButton from './InputButton'
import Modal from './Modal'
-import BlockType from './BlockType'
-import BlockId from './BlockId'
-import BlockSource from './BlockSource'
-import BlockSourceLayer from './BlockSourceLayer'
+import FieldType from './FieldType'
+import FieldId from './FieldId'
+import FieldSource from './FieldSource'
+import FieldSourceLayer from './FieldSourceLayer'
export default class ModalAdd extends React.Component {
static propTypes = {
@@ -129,20 +130,22 @@ export default class ModalAdd extends React.Component {
className="maputnik-add-modal"
>
- {
this.setState({ id: v })
}}
/>
- this.setState({ type: v })}
/>
{this.state.type !== 'background' &&
-
}
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
- this.setState({ 'source-layer': v })}
/>
}
-
Add Layer
-
+
}
diff --git a/src/components/ModalExport.jsx b/src/components/ModalExport.jsx
index f1f7ff5..19e0c85 100644
--- a/src/components/ModalExport.jsx
+++ b/src/components/ModalExport.jsx
@@ -4,10 +4,9 @@ import Slugify from 'slugify'
import { saveAs } from 'file-saver'
import {format} from '@mapbox/mapbox-gl-style-spec'
-import Block from './Block'
import FieldString from './FieldString'
import FieldCheckbox from './FieldCheckbox'
-import Button from './Button'
+import InputButton from './InputButton'
import Modal from './Modal'
import {MdFileDownload} from 'react-icons/md'
import style from '../libs/style'
@@ -75,42 +74,33 @@ export default class ModalExport extends React.Component {
-
-
-
-
+
-
-
-
+
-
-
+ value={(this.props.mapStyle.metadata || {})['maputnik:thunderforest_access_token']}
+ onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
+ />
-
Download
-
+
diff --git a/src/components/ModalLoading.jsx b/src/components/ModalLoading.jsx
index 6c34485..07303c3 100644
--- a/src/components/ModalLoading.jsx
+++ b/src/components/ModalLoading.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Button from './Button'
+import InputButton from './InputButton'
import Modal from './Modal'
@@ -34,9 +34,9 @@ export default class ModalLoading extends React.Component {
{this.props.message}
- this.props.onCancel(e)}>
+ this.props.onCancel(e)}>
Cancel
-
+
}
diff --git a/src/components/ModalOpen.jsx b/src/components/ModalOpen.jsx
index 15d564c..42bde38 100644
--- a/src/components/ModalOpen.jsx
+++ b/src/components/ModalOpen.jsx
@@ -2,9 +2,9 @@ import React from 'react'
import PropTypes from 'prop-types'
import ModalLoading from './ModalLoading'
import Modal from './Modal'
-import Button from './Button'
+import InputButton from './InputButton'
import FileReaderInput from 'react-file-reader-input'
-import FieldUrl from './FieldUrl'
+import InputUrl from './InputUrl'
import {MdFileUpload} from 'react-icons/md'
import {MdAddCircleOutline} from 'react-icons/md'
@@ -22,7 +22,7 @@ class PublicStyle extends React.Component {
render() {
return
- this.props.onSelect(this.props.url)}
@@ -38,7 +38,7 @@ class PublicStyle extends React.Component {
backgroundImage: `url(${this.props.thumbnailUrl})`
}}
>
-
+
}
}
@@ -201,7 +201,7 @@ export default class ModalOpen extends React.Component {
Upload Style
Upload a JSON style from your computer.
- Upload
+ Upload
@@ -211,7 +211,7 @@ export default class ModalOpen extends React.Component {
Load from a URL. Note that the URL must have CORS enabled .
diff --git a/src/components/ModalSettings.jsx b/src/components/ModalSettings.jsx
index 831d0ca..c3e733a 100644
--- a/src/components/ModalSettings.jsx
+++ b/src/components/ModalSettings.jsx
@@ -87,169 +87,158 @@ export default class ModalSettings extends React.Component {
title={'Style Settings'}
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
}
diff --git a/src/components/ModalSources.jsx b/src/components/ModalSources.jsx
index 2d6583c..475aa45 100644
--- a/src/components/ModalSources.jsx
+++ b/src/components/ModalSources.jsx
@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Modal from './Modal'
-import Button from './Button'
+import InputButton from './InputButton'
import Block from './Block'
import FieldString from './FieldString'
import FieldSelect from './FieldSelect'
@@ -24,7 +24,7 @@ class PublicSource extends React.Component {
render() {
return
- this.props.onSelect(this.props.id)}
>
@@ -34,7 +34,7 @@ class PublicSource extends React.Component {
-
+
}
}
@@ -83,13 +83,13 @@ class ActiveModalSourcesTypeEditor extends React.Component {
#{this.props.sourceId}
- this.props.onDelete(this.props.sourceId)}
style={{backgroundColor: 'transparent'}}
>
-
+
-
- this.setState({ sourceId: v})}
- />
-
-
- this.setState({mode: mode, source: this.defaultSource(mode)})}
- value={this.state.mode}
- />
-
+ this.setState({ sourceId: v})}
+ />
+ this.setState({mode: mode, source: this.defaultSource(mode)})}
+ value={this.state.mode}
+ />
-
Add Source
-
+
}
}
diff --git a/src/components/ModalSourcesTypeEditor.jsx b/src/components/ModalSourcesTypeEditor.jsx
index 1ce995e..4a84b24 100644
--- a/src/components/ModalSourcesTypeEditor.jsx
+++ b/src/components/ModalSourcesTypeEditor.jsx
@@ -2,13 +2,12 @@ import React from 'react'
import PropTypes from 'prop-types'
import {latest} from '@mapbox/mapbox-gl-style-spec'
import Block from './Block'
-import FieldString from './FieldString'
import FieldUrl from './FieldUrl'
import FieldNumber from './FieldNumber'
import FieldSelect from './FieldSelect'
import FieldDynamicArray from './FieldDynamicArray'
import FieldArray from './FieldArray'
-import FieldJsonEditor from './FieldJsonEditor'
+import FieldJson from './FieldJson'
class TileJSONSourceEditor extends React.Component {
@@ -20,15 +19,15 @@ class TileJSONSourceEditor extends React.Component {
render() {
return
-
- this.props.onChange({
- ...this.props.source,
- url: url
- })}
- />
-
+ this.props.onChange({
+ ...this.props.source,
+ url: url
+ })}
+ />
{this.props.children}
}
@@ -50,36 +49,36 @@ class TileURLSourceEditor extends React.Component {
renderTileUrls() {
const tiles = this.props.source.tiles || [];
- return
-
-
+ return
}
render() {
return
{this.renderTileUrls()}
-
- this.props.onChange({
- ...this.props.source,
- minzoom: minzoom
- })}
- />
-
-
- this.props.onChange({
- ...this.props.source,
- maxzoom: maxzoom
- })}
- />
-
+ this.props.onChange({
+ ...this.props.source,
+ minzoom: minzoom
+ })}
+ />
+ this.props.onChange({
+ ...this.props.source,
+ maxzoom: maxzoom
+ })}
+ />
{this.props.children}
@@ -104,26 +103,26 @@ class ImageSourceEditor extends React.Component {
}
return
-
- this.props.onChange({
- ...this.props.source,
- url,
- })}
- />
-
+ this.props.onChange({
+ ...this.props.source,
+ url,
+ })}
+ />
{["top left", "top right", "bottom right", "bottom left"].map((label, idx) => {
return (
-
- changeCoord(idx, val)}
- />
-
+ changeCoord(idx, val)}
+ />
);
})}
@@ -155,25 +154,25 @@ class VideoSourceEditor extends React.Component {
}
return
-
-
-
+
{["top left", "top right", "bottom right", "bottom left"].map((label, idx) => {
return (
-
- changeCoord(idx, val)}
- />
-
+ changeCoord(idx, val)}
+ />
);
})}
@@ -187,15 +186,15 @@ class GeoJSONSourceUrlEditor extends React.Component {
}
render() {
- return
- this.props.onChange({
- ...this.props.source,
- data: data
- })}
- />
-
+ return this.props.onChange({
+ ...this.props.source,
+ data: data
+ })}
+ />
}
}
@@ -207,7 +206,7 @@ class GeoJSONSourceFieldJsonEditor extends React.Component {
render() {
return
-
case 'tilejson_raster-dem': return
case 'tilexyz_raster-dem': return
-
- this.props.onChange({
- ...this.props.source,
- encoding: encoding
- })}
- value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
- />
-
+ this.props.onChange({
+ ...this.props.source,
+ encoding: encoding
+ })}
+ value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
+ />
case 'image': return
case 'video': return
diff --git a/src/components/ModalSurvey.jsx b/src/components/ModalSurvey.jsx
index 9087142..13ab939 100644
--- a/src/components/ModalSurvey.jsx
+++ b/src/components/ModalSurvey.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Button from './Button'
+import InputButton from './InputButton'
import Modal from './Modal'
import logoImage from 'maputnik-design/logos/logo-color.svg'
@@ -26,10 +26,10 @@ export default class ModalSurvey extends React.Component {
title="Maputnik Survey"
>
-
+
You + Maputnik = Maputnik better for you
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.
-
Take the Maputnik Survey
+
Take the Maputnik Survey
It takes 7 minutes, tops! Every question is optional.
diff --git a/src/components/SingleFilterEditor.jsx b/src/components/SingleFilterEditor.jsx
index 676f78c..2b09431 100644
--- a/src/components/SingleFilterEditor.jsx
+++ b/src/components/SingleFilterEditor.jsx
@@ -2,9 +2,9 @@ import React from 'react'
import PropTypes from 'prop-types'
import { otherFilterOps } from '../libs/filterops.js'
-import FieldString from './FieldString'
-import FieldAutocomplete from './FieldAutocomplete'
-import FieldSelect from './FieldSelect'
+import InputString from './InputString'
+import InputAutocomplete from './InputAutocomplete'
+import InputSelect from './InputSelect'
function tryParseInt(v) {
if (v === '') return v
@@ -64,14 +64,14 @@ export default class SingleFilterEditor extends React.Component {
return
- [propName, propName])}
onChange={newPropertyName => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)}
/>
- this.onFilterPartChanged(newFilterOp, propertyName, filterArgs)}
options={otherFilterOps}
@@ -79,7 +79,7 @@ export default class SingleFilterEditor extends React.Component {
{filterArgs.length > 0 &&
-
this.onFilterPartChanged(filterOp, propertyName, v.split(','))}
/>
diff --git a/src/components/SpecField.jsx b/src/components/SpecField.jsx
index dc6d2df..45be8a2 100644
--- a/src/components/SpecField.jsx
+++ b/src/components/SpecField.jsx
@@ -1,132 +1,41 @@
import React from 'react'
import PropTypes from 'prop-types'
+import Block from './Block'
+import InputSpec from './InputSpec'
+import Fieldset from './Fieldset'
-import FieldColor from './FieldColor'
-import FieldNumber from './FieldNumber'
-import FieldCheckbox from './FieldCheckbox'
-import FieldString from './FieldString'
-import FieldSelect from './FieldSelect'
-import FieldMultiInput from './FieldMultiInput'
-import FieldArray from './FieldArray'
-import FieldDynamicArray from './FieldDynamicArray'
-import FieldFont from './FieldFont'
-import FieldSymbol from './FieldSymbol'
-import FieldEnum from './FieldEnum'
-import capitalize from 'lodash.capitalize'
-const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
+const typeMap = {
+ color: Block,
+ enum: Fieldset,
+ number: Block,
+ boolean: Block,
+ array: Fieldset,
+ resolvedImage: Block,
+ number: Block,
+ string: Block
+};
-function labelFromFieldName(fieldName) {
- let label = fieldName.split('-').slice(1).join(' ')
- if(label.length > 0) {
- label = label.charAt(0).toUpperCase() + label.slice(1);
- }
- return label
-}
-
-function optionsLabelLength(options) {
- let sum = 0;
- options.forEach(([_, label]) => {
- sum += label.length
- })
- return sum
-}
-
-/** Display any field from the Mapbox GL style spec and
- * choose the correct field component based on the @{fieldSpec}
- * to display @{value}. */
export default class SpecField extends React.Component {
static propTypes = {
- onChange: PropTypes.func.isRequired,
- fieldName: PropTypes.string.isRequired,
- fieldSpec: PropTypes.object.isRequired,
- value: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- PropTypes.array,
- PropTypes.bool
- ]),
- /** Override the style of the field */
- style: PropTypes.object,
+ ...InputSpec.propTypes,
+ name: PropTypes.string,
}
render() {
- const commonProps = {
- style: this.props.style,
- value: this.props.value,
- default: this.props.fieldSpec.default,
- name: this.props.fieldName,
- onChange: newValue => this.props.onChange(this.props.fieldName, newValue)
+ const {props} = this;
+
+ const fieldType = props.fieldSpec.type;
+ let TypeBlock = typeMap[fieldType];
+
+ if (!TypeBlock) {
+ console.warn("No such type for '%s'", fieldType);
+ TypeBlock = Block;
}
- function childNodes() {
- switch(this.props.fieldSpec.type) {
- case 'number': return (
-
- )
- case 'enum':
- const options = Object.keys(this.props.fieldSpec.values).map(v => [v, capitalize(v)])
-
- return
- case 'resolvedImage':
- case 'formatted':
- case 'string':
- if(iconProperties.indexOf(this.props.fieldName) >= 0) {
- return
- } else {
- return
- }
- case 'color': return (
-
- )
- case 'boolean': return (
-
- )
- case 'array':
- if(this.props.fieldName === 'text-font') {
- return
- } else {
- if (this.props.fieldSpec.length) {
- return
- } else {
- return
- }
- }
- default: return null
- }
- }
-
- return (
-
- {childNodes.call(this)}
-
- );
+ return
+
+
}
}
+
diff --git a/src/components/_DataProperty.jsx b/src/components/_DataProperty.jsx
index 6c0a4ce..7b12bb9 100644
--- a/src/components/_DataProperty.jsx
+++ b/src/components/_DataProperty.jsx
@@ -2,12 +2,12 @@ import React from 'react'
import PropTypes from 'prop-types'
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
-import Button from './Button'
-import SpecField from './SpecField'
-import FieldNumber from './FieldNumber'
-import FieldString from './FieldString'
-import FieldSelect from './FieldSelect'
-import Doc from './Doc'
+import InputButton from './InputButton'
+import InputSpec from './InputSpec'
+import InputNumber from './InputNumber'
+import InputString from './InputString'
+import InputSelect from './InputSelect'
+import FieldDocLabel from './FieldDocLabel'
import Block from './Block'
import docUid from '../libs/document-uid'
import sortNumerically from '../libs/sort-numerically'
@@ -149,8 +149,14 @@ export default class DataProperty extends React.Component {
changeStop(changeIdx, stopData, value) {
const stops = this.props.value.stops.slice(0)
- const changedStop = stopData.zoom === undefined ? stopData.value : stopData
- stops[changeIdx] = [changedStop, value]
+ // const changedStop = stopData.zoom === undefined ? stopData.value : stopData
+ stops[changeIdx] = [
+ {
+ ...stopData,
+ zoom: (stopData.zoom === undefined) ? 0 : stopData.zoom,
+ },
+ value
+ ];
const orderedStops = this.orderStopsByZoom(stops);
@@ -188,6 +194,7 @@ export default class DataProperty extends React.Component {
const deleteStopBtn =
const dataProps = {
+ 'aria-label': "Input value",
label: "Data value",
value: dataLevel,
onChange: newData => this.changeStop(idx, { zoom: zoomLevel, value: newData }, value)
@@ -195,16 +202,17 @@ export default class DataProperty extends React.Component {
let dataInput;
if(this.props.value.type === "categorical") {
- dataInput =
+ dataInput =
}
else {
- dataInput =
+ dataInput =
}
let zoomInput = null;
if(zoomLevel !== undefined) {
- zoomInput =
-
+ this.changeStop(idx, {zoom: newZoom, value: dataLevel}, value)}
min={0}
@@ -223,6 +231,27 @@ export default class DataProperty extends React.Component {
}).join("");
const error = message ? {message} : undefined;
+ return
+
+ {zoomInput}
+
+
+ {dataInput}
+
+
+ this.changeStop(idx, {zoom: zoomLevel, value: dataLevel}, newValue)}
+ />
+
+
+ {deleteStopBtn}
+
+
+
return
-
-
-
-
-
+
+ {labelFromFieldName(this.props.fieldName)}
+
+
- this.changeDataProperty("type", propVal)}
title={"Select a type of data scale (default is 'categorical')."}
options={this.getDataFunctionTypes(this.props.fieldSpec)}
/>
-
-
-
+
+
- this.changeDataProperty("property", propVal)}
/>
-
+
{dataFields &&
-
-
+ this.changeDataProperty("default", propVal)}
/>
-
- this.changeDataProperty("default", propVal)}
- />
-
+
+ }
+ {dataFields &&
+
+
+ Stops
+
+
+ Zoom
+ Input value
+ Output value
+
+
+
+ {dataFields}
+
+
}
-
-
- {dataFields &&
- <>
- {dataFields}
-
-
-
- Add stop
-
- >
- }
-
-
-
- Convert to expression
-
+
+ {dataFields &&
+
+
+
+ Add stop
+
+ }
+
+
+
+ Convert to expression
+
+
+
+
}
}
diff --git a/src/components/_DeleteStopButton.jsx b/src/components/_DeleteStopButton.jsx
index 840e1c3..d387cb3 100644
--- a/src/components/_DeleteStopButton.jsx
+++ b/src/components/_DeleteStopButton.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Button from './Button'
+import InputButton from './InputButton'
import {MdDelete} from 'react-icons/md'
@@ -11,12 +11,12 @@ export default class DeleteStopButton extends React.Component {
}
render() {
- return
-
+
}
}
diff --git a/src/components/_ExpressionProperty.jsx b/src/components/_ExpressionProperty.jsx
index f7f6803..09bdfda 100644
--- a/src/components/_ExpressionProperty.jsx
+++ b/src/components/_ExpressionProperty.jsx
@@ -2,13 +2,13 @@ import React from 'react'
import PropTypes from 'prop-types'
import Block from './Block'
-import Button from './Button'
+import InputButton from './InputButton'
import {MdDelete, MdUndo} from 'react-icons/md'
import FieldString from './FieldString'
import labelFromFieldName from './_labelFromFieldName'
import stringifyPretty from 'json-stringify-pretty-compact'
-import FieldJsonEditor from './FieldJsonEditor'
+import FieldJson from './FieldJson'
export default class ExpressionProperty extends React.Component {
@@ -59,7 +59,7 @@ export default class ExpressionProperty extends React.Component {
const deleteStopBtn = (
<>
{this.props.onUndo &&
-
-
+
}
-
-
+
>
);
@@ -114,7 +114,7 @@ export default class ExpressionProperty extends React.Component {
action={deleteStopBtn}
wideMode={true}
>
-
+
+
+ }
+}
+
diff --git a/src/components/FieldFont.jsx b/src/components/_FieldFont.jsx
similarity index 74%
rename from src/components/FieldFont.jsx
rename to src/components/_FieldFont.jsx
index 2aece10..cd6ca15 100644
--- a/src/components/FieldFont.jsx
+++ b/src/components/_FieldFont.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import Block from './Block'
import PropTypes from 'prop-types'
import FieldAutocomplete from './FieldAutocomplete'
@@ -39,17 +40,22 @@ export default class FieldFont extends React.Component {
render() {
const inputs = this.values.map((value, i) => {
- return [f, f])}
- onChange={this.changeFont.bind(this, i)}
- />
+ >
+ [f, f])}
+ onChange={this.changeFont.bind(this, i)}
+ />
+
})
- return
- {inputs}
-
+ return
+
+
}
}
diff --git a/src/components/_FieldId.jsx b/src/components/_FieldId.jsx
new file mode 100644
index 0000000..ebeedc9
--- /dev/null
+++ b/src/components/_FieldId.jsx
@@ -0,0 +1,28 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+import Block from './Block'
+import FieldString from './FieldString'
+
+export default class BlockId extends React.Component {
+ static propTypes = {
+ value: PropTypes.string.isRequired,
+ wdKey: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ error: PropTypes.object,
+ }
+
+ render() {
+ return
+
+
+ }
+}
+
diff --git a/src/components/_FieldMaxZoom.jsx b/src/components/_FieldMaxZoom.jsx
new file mode 100644
index 0000000..a032915
--- /dev/null
+++ b/src/components/_FieldMaxZoom.jsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+import Block from './Block'
+import FieldNumber from './FieldNumber'
+
+export default class BlockMaxZoom extends React.Component {
+ static propTypes = {
+ value: PropTypes.number,
+ onChange: PropTypes.func.isRequired,
+ error: PropTypes.object,
+ }
+
+ render() {
+ return
+
+
+ }
+}
+
diff --git a/src/components/_FieldMinZoom.jsx b/src/components/_FieldMinZoom.jsx
new file mode 100644
index 0000000..99e2953
--- /dev/null
+++ b/src/components/_FieldMinZoom.jsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+import Block from './Block'
+import FieldNumber from './FieldNumber'
+
+export default class BlockMinZoom extends React.Component {
+ static propTypes = {
+ value: PropTypes.number,
+ onChange: PropTypes.func.isRequired,
+ error: PropTypes.object,
+ }
+
+ render() {
+ return
+
+
+ }
+}
+
diff --git a/src/components/_FieldSource.jsx b/src/components/_FieldSource.jsx
new file mode 100644
index 0000000..bcee37f
--- /dev/null
+++ b/src/components/_FieldSource.jsx
@@ -0,0 +1,37 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+import Block from './Block'
+import FieldAutocomplete from './FieldAutocomplete'
+
+export default class BlockSource extends React.Component {
+ static propTypes = {
+ value: PropTypes.string,
+ wdKey: PropTypes.string,
+ onChange: PropTypes.func,
+ sourceIds: PropTypes.array,
+ error: PropTypes.object,
+ }
+
+ static defaultProps = {
+ onChange: () => {},
+ sourceIds: [],
+ }
+
+ render() {
+ return
+ [src, src])}
+ />
+
+ }
+}
+
diff --git a/src/components/_FieldSourceLayer.jsx b/src/components/_FieldSourceLayer.jsx
new file mode 100644
index 0000000..ff617f4
--- /dev/null
+++ b/src/components/_FieldSourceLayer.jsx
@@ -0,0 +1,35 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+import Block from './Block'
+import FieldAutocomplete from './FieldAutocomplete'
+
+export default class BlockSourceLayer extends React.Component {
+ static propTypes = {
+ value: PropTypes.string,
+ onChange: PropTypes.func,
+ sourceLayerIds: PropTypes.array,
+ isFixed: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ onChange: () => {},
+ sourceLayerIds: [],
+ isFixed: false
+ }
+
+ render() {
+ return
+ [l, l])}
+ />
+
+ }
+}
+
diff --git a/src/components/FieldSymbol.jsx b/src/components/_FieldSymbol.jsx
similarity index 100%
rename from src/components/FieldSymbol.jsx
rename to src/components/_FieldSymbol.jsx
diff --git a/src/components/_FieldType.jsx b/src/components/_FieldType.jsx
new file mode 100644
index 0000000..cc44523
--- /dev/null
+++ b/src/components/_FieldType.jsx
@@ -0,0 +1,53 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+import Block from './Block'
+import FieldSelect from './FieldSelect'
+import FieldString from './FieldString'
+
+export default class BlockType extends React.Component {
+ static propTypes = {
+ value: PropTypes.string.isRequired,
+ wdKey: PropTypes.string,
+ onChange: PropTypes.func.isRequired,
+ error: PropTypes.object,
+ disabled: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ disabled: false,
+ }
+
+ render() {
+ return
+ {this.props.disabled &&
+
+ }
+ {!this.props.disabled &&
+
+ }
+
+ }
+}
+
diff --git a/src/components/_FunctionButtons.jsx b/src/components/_FunctionButtons.jsx
index eeda73c..bacd344 100644
--- a/src/components/_FunctionButtons.jsx
+++ b/src/components/_FunctionButtons.jsx
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Button from './Button'
+import InputButton from './InputButton'
import {MdFunctions, MdInsertChart} from 'react-icons/md'
import {mdiFunctionVariant} from '@mdi/js';
@@ -24,7 +24,7 @@ function isExpression(value, fieldSpec={}) {
}
}
-export default class FunctionButtons extends React.Component {
+export default class FunctionInputButtons extends React.Component {
static propTypes = {
fieldSpec: PropTypes.object,
onZoomClick: PropTypes.func,
@@ -33,11 +33,11 @@ export default class FunctionButtons extends React.Component {
}
render() {
- let makeZoomButton, makeDataButton, expressionButton;
+ let makeZoomInputButton, makeDataInputButton, expressionInputButton;
if (this.props.fieldSpec.expression.parameters.includes('zoom')) {
- expressionButton = (
-
-
+
);
- makeZoomButton =
-
+
if (this.props.fieldSpec['property-type'] === 'data-driven') {
- makeDataButton =
-
+
}
return
- {expressionButton}
- {makeDataButton}
- {makeZoomButton}
+ {expressionInputButton}
+ {makeDataInputButton}
+ {makeZoomInputButton}
}
else {
- return {expressionButton}
+ return {expressionInputButton}
}
}
}
diff --git a/src/components/_SpecProperty.jsx b/src/components/_SpecProperty.jsx
index 43f2448..e4df518 100644
--- a/src/components/_SpecProperty.jsx
+++ b/src/components/_SpecProperty.jsx
@@ -37,13 +37,12 @@ export default class SpecProperty extends React.Component {
const error = errors[fieldType+"."+fieldName];
- return
-
-
+ />
}
}
diff --git a/src/components/_ZoomProperty.jsx b/src/components/_ZoomProperty.jsx
index 5a088f5..79ba4d9 100644
--- a/src/components/_ZoomProperty.jsx
+++ b/src/components/_ZoomProperty.jsx
@@ -2,9 +2,11 @@ import React from 'react'
import PropTypes from 'prop-types'
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
-import Button from './Button'
-import SpecField from './SpecField'
-import FieldNumber from './FieldNumber'
+import InputButton from './InputButton'
+import InputSpec from './InputSpec'
+import InputNumber from './InputNumber'
+import InputSelect from './InputSelect'
+import FieldDocLabel from './FieldDocLabel'
import Block from './Block'
import DeleteStopButton from './_DeleteStopButton'
@@ -143,52 +145,92 @@ export default class ZoomProperty extends React.Component {
}).join("");
const error = message ? {message} : undefined;
- return
-
-
- this.changeZoomStop(idx, changedStop, value)}
- min={0}
- max={22}
- />
-
-
- this.changeZoomStop(idx, zoomLevel, newValue)}
- />
-
-
-
+
+ this.changeZoomStop(idx, changedStop, value)}
+ min={0}
+ max={22}
+ />
+
+
+ this.changeZoomStop(idx, zoomLevel, newValue)}
+ />
+
+
+ {deleteStopBtn}
+
+
});
- return
- {zoomFields}
-
-
-
- Add stop
-
-
-
-
- Convert to expression
-
+ // return
+ return
+
+ {labelFromFieldName(this.props.fieldName)}
+
+
+
+ this.changeDataProperty("type", propVal)}
+ title={"Select a type of data scale (default is 'categorical')."}
+ options={this.getDataFunctionTypes(this.props.fieldSpec)}
+ />
+
+
+
+
+ Stops
+
+
+ Zoom
+ Output value
+
+
+
+ {zoomFields}
+
+
+
+
+
+
+
+ Add stop
+
+
+
+
+ Convert to expression
+
+
+
+
}
+
+ getDataFunctionTypes(fieldSpec) {
+ if (fieldSpec.expression.interpolated) {
+ return ["categorical", "interval", "exponential", "identity", "interpolate"]
+ }
+ else {
+ return ["categorical", "interval", "identity", "interpolate"]
+ }
+ }
+
}
diff --git a/src/styles/_components.scss b/src/styles/_components.scss
index eedefb5..d86896b 100644
--- a/src/styles/_components.scss
+++ b/src/styles/_components.scss
@@ -183,6 +183,7 @@
.maputnik-input-block-label {
display: inline-block;
width: 32%;
+ margin-bottom: $margin-3;
}
.maputnik-input-block-action {
diff --git a/src/styles/_input.scss b/src/styles/_input.scss
index 9f19d58..16f08bc 100644
--- a/src/styles/_input.scss
+++ b/src/styles/_input.scss
@@ -77,13 +77,13 @@
.maputnik-array-block-action {
vertical-align: top;
display: inline-block;
- width: 14%;
+ width: 2em;
}
.maputnik-array-block-content {
vertical-align: top;
display: inline-block;
- width: 86%;
+ width: calc(100% - 2em);
}
}
diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss
index 5e321f2..b1413fe 100644
--- a/src/styles/_modal.scss
+++ b/src/styles/_modal.scss
@@ -8,6 +8,9 @@
z-index: 3;
position: relative;
font-family: $font-family;
+ display: flex;
+ flex-direction: column;
+ max-height: 100vh;
}
.maputnik-modal-section {
@@ -50,7 +53,7 @@
}
.maputnik-modal-scroller {
- max-height: calc(100vh - 35px);
+ flex: 1;
overflow-y: auto;
}
diff --git a/src/styles/_zoomproperty.scss b/src/styles/_zoomproperty.scss
index 38e26c5..1108691 100644
--- a/src/styles/_zoomproperty.scss
+++ b/src/styles/_zoomproperty.scss
@@ -73,10 +73,6 @@
width: 30%;
}
- .maputnik-input-block:not(:first-child) .maputnik-input-block-label {
- visibility: hidden;
- }
-
.maputnik-input-block-content {
width: 70%;
}
diff --git a/src/styles/index.scss b/src/styles/index.scss
index 11cca10..8acf3a2 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -35,3 +35,135 @@
height: 14px;
}
+.maputnik-data-spec-property {
+}
+
+.maputnik-data-fieldset-inner {
+ background: $color-black;
+ border: solid 1px $color-midgray;
+ border-radius: 2px;
+ position: relative;
+
+ // HACK: Overide
+ .maputnik-input-block {
+ margin: $margin-2;
+ }
+
+ .maputnik-add-stop {
+ display: inline-block;
+ float: none;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ .maputnik-toolbox {
+ margin: $margin-3;
+ margin-top: $margin-3;
+ text-align: right;
+ }
+
+}
+
+.maputnik-data-spec-property {
+ legend {
+ font-size: $font-size-6;
+ color: $color-lowgray;
+ margin-bottom: $margin-3;
+ }
+
+ .maputnik-data-spec-property-group {
+ margin-bottom: $margin-2;
+ }
+}
+
+.maputnik-data-spec-block {
+ margin: $margin-3;
+}
+
+.maputnik-function-stop {
+ padding-left: $margin-2;
+ padding-right: $margin-2;
+}
+
+.maputnik-function-stop-table {
+ text-align: left;
+ margin-bottom: $margin-2;
+ box-sizing: border-box;
+ width: 100%;
+
+ thead th {
+ padding: $margin-1 $margin-2;
+ padding-left: 0;
+ color: $color-lowgray;
+ }
+
+ td, th {
+ font-size: $font-size-6;
+ color: $color-white;
+
+ // HACK
+ > * {
+ display: inline-block;
+ width: 100%;
+ vertical-align: text-top;
+ }
+
+ &:not(:first-child)
+ {
+ padding-top: $margin-1;
+ padding-left: $margin-2;
+ }
+
+ &:nth-child(1) {
+ width: 2em;
+ }
+
+ &:nth-child(2) {
+ width: 6em;
+ }
+
+ &:nth-child(3) {
+ width: auto;
+ }
+
+ &:nth-child(4) {
+ // HACK
+ width: 1.8em;
+
+ .maputnik-delete-stop {
+ padding: 0;
+ width: 1em;
+ }
+ }
+ }
+
+ &--zoom {
+ td, th {
+ &:nth-child(2) {
+ width: auto;
+ }
+
+ &:nth-child(3) {
+ // HACK
+ width: 1.8em;
+
+ .maputnik-delete-stop {
+ padding: 0;
+ width: 1em;
+ }
+ }
+ }
+ }
+
+ caption {
+ color: $color-lowgray;
+ text-align: left;
+ border-top: solid 1px $color-black;
+ font-size: $font-size-6;
+ height: 0px;
+ overflow: hidden;
+ }
+}
+
diff --git a/stories/FieldArray.stories.js b/stories/FieldArray.stories.js
index 0ee6aab..2b4e9db 100644
--- a/stories/FieldArray.stories.js
+++ b/stories/FieldArray.stories.js
@@ -17,6 +17,7 @@ export const NumberType = () => {
return (
{
return (
{
return (
{
return (
@@ -30,6 +31,7 @@ export const BasicChecked = () => {
return (
diff --git a/stories/FieldColor.stories.js b/stories/FieldColor.stories.js
index 31a35ac..b88b792 100644
--- a/stories/FieldColor.stories.js
+++ b/stories/FieldColor.stories.js
@@ -17,7 +17,7 @@ export const Basic = () => {
return (
diff --git a/stories/FieldComment.stories.js b/stories/FieldComment.stories.js
deleted file mode 100644
index 6c815d9..0000000
--- a/stories/FieldComment.stories.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import {useActionState} from './helper';
-import FieldComment from '../src/components/FieldComment';
-import {Wrapper} from './ui';
-import {withA11y} from '@storybook/addon-a11y';
-
-export default {
- title: 'FieldComment',
- component: FieldComment,
- decorators: [withA11y],
-};
-
-
-export const Basic = () => {
- const [value, setValue] = useActionState("onChange", "Hello\nworld");
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldDynamicArray.stories.js b/stories/FieldDynamicArray.stories.js
index 85555fd..1fef7d9 100644
--- a/stories/FieldDynamicArray.stories.js
+++ b/stories/FieldDynamicArray.stories.js
@@ -17,6 +17,7 @@ export const NumberType = () => {
return (
{
return (
{
return (
{
return (
{
return (
{
return (
{
return (
{
- const fonts = ["Comic Sans", "Helvectica", "Gotham"];
- const [value, setValue] = useActionState("onChange", ["Comic Sans"]);
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldFunction.stories.js b/stories/FieldFunction.stories.js
new file mode 100644
index 0000000..9efca27
--- /dev/null
+++ b/stories/FieldFunction.stories.js
@@ -0,0 +1,43 @@
+import React from 'react';
+import FieldFunction from '../src/components/FieldFunction';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+
+
+export default {
+ title: 'FieldFunction',
+ component: FieldFunction,
+ decorators: [withA11y],
+};
+
+export const Basic = () => {
+ const value = {
+ "property": "rank",
+ "type": "categorical",
+ "default": "#222",
+ "stops": [
+ [
+ {"zoom": 6, "value": ""},
+ ["#777"]
+ ],
+ [
+ {"zoom": 10, "value": ""},
+ ["#444"]
+ ]
+ ]
+ };
+
+ return
+ {}}
+ value={value}
+ errors={[]}
+ fieldName={"Color"}
+ fieldType={"color"}
+ fieldSpec={latest['paint_fill']['fill-color']}
+ />
+
+};
+
diff --git a/stories/FieldId.stories.js b/stories/FieldId.stories.js
deleted file mode 100644
index b2b97ae..0000000
--- a/stories/FieldId.stories.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import {useActionState} from './helper';
-import FieldId from '../src/components/FieldId';
-import {Wrapper} from './ui';
-import {withA11y} from '@storybook/addon-a11y';
-
-export default {
- title: 'FieldId',
- component: FieldId,
- decorators: [withA11y],
-};
-
-
-export const Basic = () => {
- const [value, setValue] = useActionState("onChange", "water");
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldMaxZoom.stories.js b/stories/FieldMaxZoom.stories.js
deleted file mode 100644
index 729f889..0000000
--- a/stories/FieldMaxZoom.stories.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import {useActionState} from './helper';
-import FieldMaxZoom from '../src/components/FieldMaxZoom';
-import {Wrapper} from './ui';
-import {withA11y} from '@storybook/addon-a11y';
-
-export default {
- title: 'FieldMaxZoom',
- component: FieldMaxZoom,
- decorators: [withA11y],
-};
-
-
-export const Basic = () => {
- const [value, setValue] = useActionState("onChange", 12);
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldMinZoom.stories.js b/stories/FieldMinZoom.stories.js
deleted file mode 100644
index f88eaee..0000000
--- a/stories/FieldMinZoom.stories.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import {useActionState} from './helper';
-import FieldMinZoom from '../src/components/FieldMinZoom';
-import {Wrapper} from './ui';
-import {withA11y} from '@storybook/addon-a11y';
-
-export default {
- title: 'FieldMinZoom',
- component: FieldMinZoom,
- decorators: [withA11y],
-};
-
-
-export const Basic = () => {
- const [value, setValue] = useActionState("onChange", 2);
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldMultiInput.stories.js b/stories/FieldMultiInput.stories.js
index 443bb14..444d38a 100644
--- a/stories/FieldMultiInput.stories.js
+++ b/stories/FieldMultiInput.stories.js
@@ -18,6 +18,7 @@ export const Basic = () => {
return (
{
return (
@@ -30,7 +30,7 @@ export const Range = () => {
return (
{
return (
{
- const [value, setValue] = useActionState("onChange", "openmaptiles");
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldSourceLayer.stories.js b/stories/FieldSourceLayer.stories.js
deleted file mode 100644
index 5d8b0ec..0000000
--- a/stories/FieldSourceLayer.stories.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import {useActionState} from './helper';
-import FieldSourceLayer from '../src/components/FieldSourceLayer';
-import {Wrapper} from './ui';
-import {withA11y} from '@storybook/addon-a11y';
-
-export default {
- title: 'FieldSourceLayer',
- component: FieldSourceLayer,
- decorators: [withA11y],
-};
-
-
-export const Basic = () => {
- const [value, setValue] = useActionState("onChange", "water");
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldString.stories.js b/stories/FieldString.stories.js
index 2889b45..9a65417 100644
--- a/stories/FieldString.stories.js
+++ b/stories/FieldString.stories.js
@@ -17,49 +17,7 @@ export const Basic = () => {
return (
-
- );
-};
-
-export const WithDefault = () => {
- const [value, setValue] = useActionState("onChange", null);
-
- return (
-
-
-
- );
-};
-
-export const Multiline = () => {
- const [value, setValue] = useActionState("onChange", "Hello\nworld");
-
- return (
-
-
-
- );
-};
-
-export const MultilineWithDefault = () => {
- const [value, setValue] = useActionState("onChange", null);
-
- return (
-
-
diff --git a/stories/FieldSymbol.stories.js b/stories/FieldSymbol.stories.js
deleted file mode 100644
index 300eabf..0000000
--- a/stories/FieldSymbol.stories.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import {useActionState} from './helper';
-import FieldSymbol from '../src/components/FieldSymbol';
-import {Wrapper} from './ui';
-import {withA11y} from '@storybook/addon-a11y';
-
-export default {
- title: 'FieldSymbol',
- component: FieldSymbol,
- decorators: [withA11y],
-};
-
-
-export const Basic = () => {
- const icons = ["Bicycle", "Ski", "Ramp"];
- const [value, setValue] = useActionState("onChange", "Ski");
-
- return (
-
-
-
- );
-};
-
-
diff --git a/stories/FieldType.stories.js b/stories/FieldType.stories.js
deleted file mode 100644
index 2312bca..0000000
--- a/stories/FieldType.stories.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import {useActionState} from './helper';
-import FieldType from '../src/components/FieldType';
-import {Wrapper} from './ui';
-import {withA11y} from '@storybook/addon-a11y';
-
-export default {
- title: 'FieldType',
- component: FieldType,
- decorators: [withA11y],
-};
-
-
-export const Basic = () => {
- const [value, setValue] = useActionState("onChange", "background");
-
- return (
-
-
-
- );
-};
-
diff --git a/stories/FieldUrl.stories.js b/stories/FieldUrl.stories.js
index b30d7bc..091cd81 100644
--- a/stories/FieldUrl.stories.js
+++ b/stories/FieldUrl.stories.js
@@ -17,6 +17,7 @@ export const Valid = () => {
return (
{
return (
{
+ const types = [
+ 'fill-extrusion',
+ 'raster',
+ 'hillshade',
+ 'heatmap',
+ 'fill',
+ 'background',
+ 'line',
+ 'symbol',
+ 'circle',
+ 'INVALID',
+ ]
+
+ return
+
+
+
+ ID
+ Preview
+
+
+
+ {types.map(type => (
+
+
+ {type}
+
+
+
+
+
+ ))}
+
+
+
+};
+
+
+
+
+
+
diff --git a/stories/InputArray.stories.js b/stories/InputArray.stories.js
new file mode 100644
index 0000000..ce50607
--- /dev/null
+++ b/stories/InputArray.stories.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputArray from '../src/components/InputArray';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputArray',
+ component: InputArray,
+ decorators: [withA11y],
+};
+
+
+export const NumberType = () => {
+ const [value, setValue] = useActionState("onChange", [1,2,3]);
+
+ return (
+
+
+
+ );
+};
+
+export const StringType = () => {
+ const [value, setValue] = useActionState("onChange", ["a", "b", "c"]);
+
+ return (
+
+
+
+ );
+};
+
+
diff --git a/stories/InputAutocomplete.stories.js b/stories/InputAutocomplete.stories.js
new file mode 100644
index 0000000..108e918
--- /dev/null
+++ b/stories/InputAutocomplete.stories.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputAutocomplete from '../src/components/InputAutocomplete';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputAutocomplete',
+ component: InputAutocomplete,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const options = [["FOO", "foo"], ["BAR", "bar"], ["BAZ", "baz"]];
+ const [value, setValue] = useActionState("onChange", "bar");
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/InputButton.stories.js b/stories/InputButton.stories.js
new file mode 100644
index 0000000..e984568
--- /dev/null
+++ b/stories/InputButton.stories.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import InputButton from '../src/components/InputButton';
+import {action} from '@storybook/addon-actions';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'InputButton',
+ component: InputButton,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+ Hello InputButton
+
+
+);
+
diff --git a/stories/InputCheckbox.stories.js b/stories/InputCheckbox.stories.js
new file mode 100644
index 0000000..0ec0da4
--- /dev/null
+++ b/stories/InputCheckbox.stories.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputCheckbox from '../src/components/InputCheckbox';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputCheckbox',
+ component: InputCheckbox,
+ decorators: [withA11y],
+};
+
+
+export const BasicUnchecked = () => {
+ const [value, setValue] = useActionState("onChange", false);
+
+ return (
+
+
+
+ );
+};
+
+export const BasicChecked = () => {
+ const [value, setValue] = useActionState("onChange", true);
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/InputColor.stories.js b/stories/InputColor.stories.js
new file mode 100644
index 0000000..7d34c57
--- /dev/null
+++ b/stories/InputColor.stories.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputColor from '../src/components/InputColor';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputColor',
+ component: InputColor,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [color, setColor] = useActionState("onChange", "#ff0000");
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/InputDynamicArray.stories.js b/stories/InputDynamicArray.stories.js
new file mode 100644
index 0000000..fde4438
--- /dev/null
+++ b/stories/InputDynamicArray.stories.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputDynamicArray from '../src/components/InputDynamicArray';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputDynamicArray',
+ component: InputDynamicArray,
+ decorators: [withA11y],
+};
+
+
+export const NumberType = () => {
+ const [value, setValue] = useActionState("onChange", [1,2,3]);
+
+ return (
+
+
+
+ );
+};
+
+export const UrlType = () => {
+ const [value, setValue] = useActionState("onChange", ["http://example.com"]);
+
+ return (
+
+
+
+ );
+};
+
+export const EnumType = () => {
+ const [value, setValue] = useActionState("onChange", ["foo"]);
+
+ return (
+
+
+
+ );
+};
diff --git a/stories/InputEnum.stories.js b/stories/InputEnum.stories.js
new file mode 100644
index 0000000..802a2de
--- /dev/null
+++ b/stories/InputEnum.stories.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputEnum from '../src/components/InputEnum';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputEnum',
+ component: InputEnum,
+ decorators: [withA11y],
+};
+
+
+export const BasicFew = () => {
+ const options = ["Foo", "Bar", "Baz"];
+ const [value, setValue] = useActionState("onChange", "Foo");
+
+ return (
+
+
+
+ );
+};
+
+export const BasicFewWithDefault = () => {
+ const options = ["Foo", "Bar", "Baz"];
+ const [value, setValue] = useActionState("onChange", null);
+
+ return (
+
+
+
+ );
+};
+
+export const BasicMany = () => {
+ const options = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
+ const [value, setValue] = useActionState("onChange", "a");
+
+ return (
+
+
+
+ );
+};
+
+export const BasicManyWithDefault = () => {
+ const options = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
+ const [value, setValue] = useActionState("onChange", "a");
+
+ return (
+
+
+
+ );
+};
+
+
+
diff --git a/stories/InputJson.stories.js b/stories/InputJson.stories.js
new file mode 100644
index 0000000..200f532
--- /dev/null
+++ b/stories/InputJson.stories.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import InputJson from '../src/components/InputJson';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'InputJson',
+ component: InputJson,
+ decorators: [withA11y],
+};
+
+export const Basic = () => {
+ const layer = {
+ id: "background",
+ type: "background",
+ };
+
+ return
+
+
+};
+
diff --git a/stories/InputMultiInput.stories.js b/stories/InputMultiInput.stories.js
new file mode 100644
index 0000000..05a8aaf
--- /dev/null
+++ b/stories/InputMultiInput.stories.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputMultiInput from '../src/components/InputMultiInput';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputMultiInput',
+ component: InputMultiInput,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const options = [["FOO", "foo"], ["BAR", "bar"], ["BAZ", "baz"]];
+ const [value, setValue] = useActionState("onChange", "FOO");
+
+ return (
+
+
+
+ );
+};
+
+
diff --git a/stories/InputNumber.stories.js b/stories/InputNumber.stories.js
new file mode 100644
index 0000000..3132275
--- /dev/null
+++ b/stories/InputNumber.stories.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputNumber from '../src/components/InputNumber';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputNumber',
+ component: InputNumber,
+ decorators: [withA11y],
+};
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", 1);
+
+ return (
+
+
+
+ );
+};
+
+export const Range = () => {
+ const [value, setValue] = useActionState("onChange", 1);
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/InputSelect.stories.js b/stories/InputSelect.stories.js
new file mode 100644
index 0000000..48a1497
--- /dev/null
+++ b/stories/InputSelect.stories.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputSelect from '../src/components/InputSelect';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputSelect',
+ component: InputSelect,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const options = [["FOO", "Foo"], ["BAR", "Bar"], ["BAZ", "Baz"]];
+ const [value, setValue] = useActionState("onChange", "FOO");
+
+ return (
+
+
+
+ );
+};
+
+
+
diff --git a/stories/InputString.stories.js b/stories/InputString.stories.js
new file mode 100644
index 0000000..0d33528
--- /dev/null
+++ b/stories/InputString.stories.js
@@ -0,0 +1,69 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputString from '../src/components/InputString';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputString',
+ component: InputString,
+ decorators: [withA11y],
+};
+
+
+export const Basic = () => {
+ const [value, setValue] = useActionState("onChange", "Hello world");
+
+ return (
+
+
+
+ );
+};
+
+export const WithDefault = () => {
+ const [value, setValue] = useActionState("onChange", null);
+
+ return (
+
+
+
+ );
+};
+
+export const Multiline = () => {
+ const [value, setValue] = useActionState("onChange", "Hello\nworld");
+
+ return (
+
+
+
+ );
+};
+
+export const MultilineWithDefault = () => {
+ const [value, setValue] = useActionState("onChange", null);
+
+ return (
+
+
+
+ );
+};
+
diff --git a/stories/InputUrl.stories.js b/stories/InputUrl.stories.js
new file mode 100644
index 0000000..4b2bd53
--- /dev/null
+++ b/stories/InputUrl.stories.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import {useActionState} from './helper';
+import InputUrl from '../src/components/InputUrl';
+import {InputContainer} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+export default {
+ title: 'InputUrl',
+ component: InputUrl,
+ decorators: [withA11y],
+};
+
+
+export const Valid = () => {
+ const [value, setValue] = useActionState("onChange", "http://example.com");
+
+ return (
+
+
+
+ );
+};
+
+export const Invalid = () => {
+ const [value, setValue] = useActionState("onChange", "foo");
+
+ return (
+
+
+
+ );
+};
+
+
diff --git a/stories/LayerEditor.stories.js b/stories/LayerEditor.stories.js
new file mode 100644
index 0000000..0af25a8
--- /dev/null
+++ b/stories/LayerEditor.stories.js
@@ -0,0 +1,202 @@
+import React from 'react';
+import LayerEditor from '../src/components/LayerEditor';
+import {action} from '@storybook/addon-actions';
+import {withA11y} from '@storybook/addon-a11y';
+import {latest} from '@mapbox/mapbox-gl-style-spec'
+
+
+export default {
+ title: 'LayerEditor',
+ component: LayerEditor,
+ decorators: [withA11y],
+};
+
+export const Background = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const Fill = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const Line = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const Symbol = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const Raster = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const Cirlce = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const FillExtrusion = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const Heatmap = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
+export const Hillshade = () => (
+
+ {}}
+ onLayerIdChange={() => {}}
+ onMoveLayer={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ isFirstLayer={true}
+ isLastLayer={false}
+ layerIndex={0}
+ />
+
+);
+
diff --git a/stories/LayerListItem.stories.js b/stories/LayerListItem.stories.js
new file mode 100644
index 0000000..8870e7c
--- /dev/null
+++ b/stories/LayerListItem.stories.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import LayerList from '../src/components/LayerList';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'LayerList',
+ component: LayerList,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+ {}}
+ onLayerSelect={() => {}}
+ onLayerDestroy={() => {}}
+ onLayerCopy={() => {}}
+ onLayerVisibilityToggle={() => {}}
+ onMoveLayer={() => {}}
+ sources={{}}
+ errors={[]}
+ />
+
+
+);
+
+
diff --git a/stories/MapMapboxGl.stories.js b/stories/MapMapboxGl.stories.js
new file mode 100644
index 0000000..a8dc589
--- /dev/null
+++ b/stories/MapMapboxGl.stories.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import MapMapboxGl from '../src/components/MapMapboxGl';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'MapMapboxGl',
+ component: MapMapboxGl,
+ decorators: [withA11y],
+};
+
+const mapStyle = {
+ "version": 8,
+ "sources": {
+ "test1": {
+ "type": "geojson",
+ "data": {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [0, -10]
+ },
+ "properties": {}
+ }
+ ]
+ }
+ },
+ "test2": {
+ "type": "geojson",
+ "data": {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [15, 10]
+ },
+ "properties": {}
+ }
+ ]
+ }
+ },
+ "test3": {
+ "type": "geojson",
+ "data": {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [-15, 10]
+ },
+ "properties": {}
+ }
+ ]
+ }
+ }
+ },
+ "sprite": "",
+ "glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "test1",
+ "type": "circle",
+ "source": "test1",
+ "paint": {
+ "circle-radius": 40,
+ "circle-color": "red"
+ }
+ },
+ {
+ "id": "test2",
+ "type": "circle",
+ "source": "test2",
+ "paint": {
+ "circle-radius": 40,
+ "circle-color": "green"
+ }
+ },
+ {
+ "id": "test3",
+ "type": "circle",
+ "source": "test3",
+ "paint": {
+ "circle-radius": 40,
+ "circle-color": "blue"
+ }
+ }
+ ]
+}
+
+export const Basic = () => {
+ return
+ s}
+ />
+
+};
+
+
diff --git a/stories/MapOpenLayers.stories.js b/stories/MapOpenLayers.stories.js
new file mode 100644
index 0000000..a8458bd
--- /dev/null
+++ b/stories/MapOpenLayers.stories.js
@@ -0,0 +1,112 @@
+import React from 'react';
+import MapOpenLayers from '../src/components/MapOpenLayers';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'MapOpenLayers',
+ component: MapOpenLayers,
+ decorators: [withA11y],
+};
+
+const mapStyle = {
+ "version": 8,
+ "sources": {
+ "test1": {
+ "type": "geojson",
+ "data": {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [0, -10]
+ },
+ "properties": {}
+ }
+ ]
+ }
+ },
+ "test2": {
+ "type": "geojson",
+ "data": {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [15, 10]
+ },
+ "properties": {}
+ }
+ ]
+ }
+ },
+ "test3": {
+ "type": "geojson",
+ "data": {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [-15, 10]
+ },
+ "properties": {}
+ }
+ ]
+ }
+ }
+ },
+ "sprite": "",
+ "glyphs": "https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "test1",
+ "type": "circle",
+ "source": "test1",
+ "paint": {
+ "circle-radius": 40,
+ "circle-color": "red"
+ }
+ },
+ {
+ "id": "test2",
+ "type": "circle",
+ "source": "test2",
+ "paint": {
+ "circle-radius": 40,
+ "circle-color": "green"
+ }
+ },
+ {
+ "id": "test3",
+ "type": "circle",
+ "source": "test3",
+ "paint": {
+ "circle-radius": 40,
+ "circle-color": "blue"
+ }
+ }
+ ]
+}
+
+export const Basic = () => {
+ return
+ s}
+ onChange={() => {}}
+ debugToolbox={true}
+ />
+
+};
+
+
+
diff --git a/stories/Modal.stories.js b/stories/Modal.stories.js
new file mode 100644
index 0000000..039e512
--- /dev/null
+++ b/stories/Modal.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import Modal from '../src/components/Modal';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'Modal',
+ component: Modal,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+ {[...Array(50).keys()].map(() => {
+ return Some text
+ })}
+
+
+
+);
+
+
diff --git a/stories/ModalAdd.stories.js b/stories/ModalAdd.stories.js
new file mode 100644
index 0000000..be28966
--- /dev/null
+++ b/stories/ModalAdd.stories.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import ModalAdd from '../src/components/ModalAdd';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ModalAdd',
+ component: ModalAdd,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+
+
+);
+
diff --git a/stories/ModalDebug.stories.js b/stories/ModalDebug.stories.js
new file mode 100644
index 0000000..34202a3
--- /dev/null
+++ b/stories/ModalDebug.stories.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import ModalDebug from '../src/components/ModalDebug';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ModalDebug',
+ component: ModalDebug,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+
+
+);
+
+
+
diff --git a/stories/ModalExport.stories.js b/stories/ModalExport.stories.js
new file mode 100644
index 0000000..8040397
--- /dev/null
+++ b/stories/ModalExport.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import ModalExport from '../src/components/ModalExport';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ModalExport',
+ component: ModalExport,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+
+
+);
+
+
+
diff --git a/stories/ModalLoading.stories.js b/stories/ModalLoading.stories.js
new file mode 100644
index 0000000..bd93459
--- /dev/null
+++ b/stories/ModalLoading.stories.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import ModalLoading from '../src/components/ModalLoading';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ModalLoading',
+ component: ModalLoading,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+
+
+);
+
diff --git a/stories/ModalOpen.stories.js b/stories/ModalOpen.stories.js
new file mode 100644
index 0000000..c97bf45
--- /dev/null
+++ b/stories/ModalOpen.stories.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import ModalOpen from '../src/components/ModalOpen';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ModalOpen',
+ component: ModalOpen,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+
+
+);
+
+
+
+
+
+
diff --git a/stories/ModalSettings.stories.js b/stories/ModalSettings.stories.js
new file mode 100644
index 0000000..49f42db
--- /dev/null
+++ b/stories/ModalSettings.stories.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import ModalSettings from '../src/components/ModalSettings';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ModalSettings',
+ component: ModalSettings,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+
+
+);
+
+
+
+
diff --git a/stories/ModalShortcuts.stories.js b/stories/ModalShortcuts.stories.js
new file mode 100644
index 0000000..e69de29
diff --git a/stories/ModalSources.stories.js b/stories/ModalSources.stories.js
new file mode 100644
index 0000000..03bcc65
--- /dev/null
+++ b/stories/ModalSources.stories.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import ModalSources from '../src/components/ModalSources';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ModalSources',
+ component: ModalSources,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+
+
+);
+
+
+
+
+
+
+
diff --git a/stories/Button.stories.js b/stories/ModalSurvey.stories.js
similarity index 55%
rename from stories/Button.stories.js
rename to stories/ModalSurvey.stories.js
index c158d7c..b0bacd7 100644
--- a/stories/Button.stories.js
+++ b/stories/ModalSurvey.stories.js
@@ -1,21 +1,29 @@
import React from 'react';
-import Button from '../src/components/Button';
+import ModalSurvey from '../src/components/ModalSurvey';
import {action} from '@storybook/addon-actions';
import {Wrapper} from './ui';
import {withA11y} from '@storybook/addon-a11y';
export default {
- title: 'Button',
- component: Button,
+ title: 'ModalSurvey',
+ component: ModalSurvey,
decorators: [withA11y],
};
export const Basic = () => (
-
- Hello Button
-
+
+
+
);
+
+
+
+
+
+
diff --git a/stories/ScrollContainer.stories.js b/stories/ScrollContainer.stories.js
new file mode 100644
index 0000000..f684f07
--- /dev/null
+++ b/stories/ScrollContainer.stories.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import ScrollContainer from '../src/components/ScrollContainer';
+import {action} from '@storybook/addon-actions';
+import {Wrapper} from './ui';
+import {withA11y} from '@storybook/addon-a11y';
+
+
+export default {
+ title: 'ScrollContainer',
+ component: ScrollContainer,
+ decorators: [withA11y],
+};
+
+export const Basic = () => (
+
+
+
+ {[...Array(50).keys()].map(() => {
+ return Some text
+ })}
+
+
+
+);
+
+
diff --git a/stories/ui.js b/stories/ui.js
index eb6333c..d04db8a 100644
--- a/stories/ui.js
+++ b/stories/ui.js
@@ -17,3 +17,11 @@ export function Wrapper ({children}) {
);
};
+export function InputContainer ({children}) {
+ return (
+
+ {children}
+
+ );
+};
+
From f911ed3522dec0b639e0a921f38026d1044aad6a Mon Sep 17 00:00:00 2001
From: orangemug
Date: Wed, 10 Jun 2020 10:10:40 +0100
Subject: [PATCH 03/25] Fix issues when trying to close picker.
---
src/components/Block.jsx | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/src/components/Block.jsx b/src/components/Block.jsx
index 4f798d0..878d98f 100644
--- a/src/components/Block.jsx
+++ b/src/components/Block.jsx
@@ -40,6 +40,23 @@ export default class Block extends React.Component {
});
}
+ /**
+ * Some fields for example bind click events inside the element
+ * to close the picker. This in turn propagates to the element
+ * causing the picker to reopen. This causes a scenario where the picker can
+ * never be closed once open.
+ */
+ onLabelClick = (event) => {
+ const el = event.nativeEvent.target;
+ const nativeEvent = event.nativeEvent;
+ const contains = this._blockEl.contains(el);
+
+ if (contains) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+
render() {
const errors = [].concat(this.props.error || []);
@@ -51,11 +68,11 @@ export default class Block extends React.Component {
"maputnik-action-block": this.props.action
})}
>
-
+
{this.props.label}
-
+
this._blockEl = el}>
{this.props.children}
From f3906c8dd8e7da2c9f15118478ae2092e5cb5564 Mon Sep 17 00:00:00 2001
From: orangemug
Date: Wed, 10 Jun 2020 10:25:56 +0100
Subject: [PATCH 04/25] A role="navigation" should not be on as it
changes how screen readers announce them.
---
src/components/LayerList.jsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/components/LayerList.jsx b/src/components/LayerList.jsx
index fccdf22..5d94c60 100644
--- a/src/components/LayerList.jsx
+++ b/src/components/LayerList.jsx
@@ -302,12 +302,14 @@ class LayerListContainer extends React.Component {
-
+
+
}
}
From 40579c3e0c35691bc9eb06c61537464dce3b25d3 Mon Sep 17 00:00:00 2001
From: orangemug
Date: Wed, 10 Jun 2020 10:59:44 +0100
Subject: [PATCH 05/25] Added back in action buttons to input label/fieldset
---
src/components/Block.jsx | 3 +++
src/components/Fieldset.jsx | 7 ++++++-
src/components/SpecField.jsx | 2 +-
src/styles/_components.scss | 12 +++++++++++-
4 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/src/components/Block.jsx b/src/components/Block.jsx
index 878d98f..bf754c5 100644
--- a/src/components/Block.jsx
+++ b/src/components/Block.jsx
@@ -72,6 +72,9 @@ export default class Block extends React.Component {
{this.props.label}
+
+ {this.props.action}
+
this._blockEl = el}>
{this.props.children}
diff --git a/src/components/Fieldset.jsx b/src/components/Fieldset.jsx
index 4b7cada..aa3b23e 100644
--- a/src/components/Fieldset.jsx
+++ b/src/components/Fieldset.jsx
@@ -14,7 +14,12 @@ export default class Fieldset extends React.Component {
const {props} = this;
return
-
{props.label}
+
+ {props.label}
+
+
+ {this.props.action}
+
{props.children}
diff --git a/src/components/SpecField.jsx b/src/components/SpecField.jsx
index 45be8a2..3b4dfcc 100644
--- a/src/components/SpecField.jsx
+++ b/src/components/SpecField.jsx
@@ -33,7 +33,7 @@ export default class SpecField extends React.Component {
TypeBlock = Block;
}
- return
+ return
}
diff --git a/src/styles/_components.scss b/src/styles/_components.scss
index d86896b..3a04689 100644
--- a/src/styles/_components.scss
+++ b/src/styles/_components.scss
@@ -168,11 +168,21 @@
color: $color-lowgray;
display: inline-block;
user-select: none;
- width: 50%;
+ width: 32%;
vertical-align: top;
font-size: 12px;
}
+ &-action {
+ color: $color-lowgray;
+ display: inline-block;
+ user-select: none;
+ width: 18%;
+ vertical-align: top;
+ font-size: 12px;
+ text-align: right;
+ }
+
&-content {
display: inline-block;
width: 50%;
From 8ae6e9fc6146c991acda3c464e8dcf293ba89937 Mon Sep 17 00:00:00 2001
From: orangemug
Date: Wed, 10 Jun 2020 13:02:38 +0100
Subject: [PATCH 06/25] Fix to ignore click suppression on inputs
---
src/components/Block.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Block.jsx b/src/components/Block.jsx
index bf754c5..06b77f9 100644
--- a/src/components/Block.jsx
+++ b/src/components/Block.jsx
@@ -51,7 +51,7 @@ export default class Block extends React.Component {
const nativeEvent = event.nativeEvent;
const contains = this._blockEl.contains(el);
- if (contains) {
+ if (event.nativeEvent.target.nodeName !== "INPUT" && contains) {
event.stopPropagation();
event.preventDefault();
}
From 5804b3c72a0f3ca30a78777ef1d628a817063923 Mon Sep 17 00:00:00 2001
From: orangemug
Date: Wed, 10 Jun 2020 13:04:04 +0100
Subject: [PATCH 07/25] CSS outline fixes for keyboard users.
---
src/styles/_base.scss | 8 ++++++--
src/styles/_input.scss | 26 ++++++++++++++++++++++----
src/styles/_layer.scss | 2 ++
3 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/src/styles/_base.scss b/src/styles/_base.scss
index 9e860f7..0c97d23 100644
--- a/src/styles/_base.scss
+++ b/src/styles/_base.scss
@@ -63,9 +63,13 @@ h4 {
}
input:focus,
+textarea:focus,
+*[role="button"]:focus,
+button:focus,
+.maputnik-toolbar-link:focus,
select:focus {
- color: $color-white !important;
- outline: #8e8e8e auto 1px !important;
+ color: $color-white;
+ outline: #8e8e8e auto 1px;
}
label:hover {
diff --git a/src/styles/_input.scss b/src/styles/_input.scss
index 16f08bc..87d6a0a 100644
--- a/src/styles/_input.scss
+++ b/src/styles/_input.scss
@@ -124,9 +124,13 @@
// CHECKBOX
.maputnik-checkbox {
position: absolute;
- z-index: -1;
- opacity: 0;
- width: 50%;
+ z-index: 1;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ left: 0;
+ top: 0;
+ outline: none;
&-wrapper {
@extend .maputnik-input;
@@ -141,6 +145,7 @@
}
&-box {
+ z-index: 2;
display: inline-block;
text-align: center;
height: 24px;
@@ -152,12 +157,26 @@
border-width: 2px;
border-color: $color-gray;
transition: background-color 0.1s ease-out;
+ position: absolute;
+ top: 0;
+ left: 0;
+ // So they fall through to the beneath
+ pointer-events: none;
@media screen and (prefers-reduced-motion: reduce) {
transition-duration: 0ms;
}
}
+ &:focus {
+ z-index: 8;
+ }
+
+ &:focus + &-box {
+ z-index: 9;
+ }
+
+
&-icon {
width: 50%;
height: 50%;
@@ -203,7 +222,6 @@
.maputnik-input-block-content {
position: relative;
- overflow: hidden;
}
.SpecDoc__sdk-support {
diff --git a/src/styles/_layer.scss b/src/styles/_layer.scss
index 394f6f1..c3cc579 100644
--- a/src/styles/_layer.scss
+++ b/src/styles/_layer.scss
@@ -143,6 +143,8 @@
color: inherit;
text-decoration: none;
cursor: pointer;
+ // HACK: Remove important
+ outline: none !important;
}
&-group-header {
From 34299c94ee9c7525ede6d0755bda5fd64754bec1 Mon Sep 17 00:00:00 2001
From: orangemug
Date: Wed, 10 Jun 2020 16:22:13 +0100
Subject: [PATCH 08/25] Fixed some 'axe' accessibility checker issues.
---
src/components/AppToolbar.jsx | 4 ++--
src/components/InputCheckbox.jsx | 4 ++--
src/components/InputNumber.jsx | 1 -
src/components/LayerEditor.jsx | 4 ++--
src/components/LayerList.jsx | 5 +++--
src/components/LayerListItem.jsx | 1 +
src/components/ModalDebug.jsx | 12 ++++++------
src/components/ModalExport.jsx | 6 +++---
src/components/ModalOpen.jsx | 17 +++++++++--------
src/components/ModalShortcuts.jsx | 4 ++--
src/components/ModalSources.jsx | 19 ++++++++++---------
src/styles/_components.scss | 4 +---
src/template.html | 1 +
13 files changed, 42 insertions(+), 40 deletions(-)
diff --git a/src/components/AppToolbar.jsx b/src/components/AppToolbar.jsx
index 052e1ee..7480210 100644
--- a/src/components/AppToolbar.jsx
+++ b/src/components/AppToolbar.jsx
@@ -184,7 +184,7 @@ export default class AppToolbar extends React.Component {
return view.id === this.props.mapState;
});
- return
+
}
}
diff --git a/src/components/InputCheckbox.jsx b/src/components/InputCheckbox.jsx
index e01d99c..7cf8323 100644
--- a/src/components/InputCheckbox.jsx
+++ b/src/components/InputCheckbox.jsx
@@ -13,7 +13,7 @@ export default class InputCheckbox extends React.Component {
}
render() {
- return
+ return
-
+
}
}
diff --git a/src/components/InputNumber.jsx b/src/components/InputNumber.jsx
index d660daf..f256288 100644
--- a/src/components/InputNumber.jsx
+++ b/src/components/InputNumber.jsx
@@ -172,7 +172,6 @@ export default class InputNumber extends React.Component {
step="any"
spellCheck="false"
value={value === undefined ? defaultValue : value}
- aria-hidden="true"
onChange={this.onChangeRange}
onKeyDown={() => {
this._keyboardEvent = true;
diff --git a/src/components/LayerEditor.jsx b/src/components/LayerEditor.jsx
index 1479ffc..f4dbb8e 100644
--- a/src/components/LayerEditor.jsx
+++ b/src/components/LayerEditor.jsx
@@ -290,7 +290,7 @@ export default class LayerEditor extends React.Component {
items[id].handler();
}
- return
@@ -332,6 +332,6 @@ export default class LayerEditor extends React.Component {
>
{groups}
-
+
}
}
diff --git a/src/components/LayerList.jsx b/src/components/LayerList.jsx
index 5d94c60..cadb166 100644
--- a/src/components/LayerList.jsx
+++ b/src/components/LayerList.jsx
@@ -248,6 +248,7 @@ class LayerListContainer extends React.Component {
})}
index={idx}
key={layer.key}
+ id={layer.key}
layerId={layer.id}
layerIndex={idx}
layerType={layer.type}
@@ -264,7 +265,7 @@ class LayerListContainer extends React.Component {
})
})
- return
-
+
}
}
diff --git a/src/components/LayerListItem.jsx b/src/components/LayerListItem.jsx
index f135288..6e8f18c 100644
--- a/src/components/LayerListItem.jsx
+++ b/src/components/LayerListItem.jsx
@@ -100,6 +100,7 @@ class LayerListItem extends React.Component {
const visibilityAction = this.props.visibility === 'visible' ? 'show' : 'hide';
return this.props.onLayerSelect(this.props.layerIndex)}
data-wd-key={"layer-list-item:"+this.props.layerId}
diff --git a/src/components/ModalDebug.jsx b/src/components/ModalDebug.jsx
index 6b92729..a7e040c 100644
--- a/src/components/ModalDebug.jsx
+++ b/src/components/ModalDebug.jsx
@@ -29,8 +29,8 @@ export default class ModalDebug extends React.Component {
onOpenToggle={this.props.onOpenToggle}
title={'Debug'}
>
-
-
Options
+
+ Options
{this.props.renderer === 'mbgljs' &&
{Object.entries(this.props.mapboxGlDebugOptions).map(([key, val]) => {
@@ -53,9 +53,9 @@ export default class ModalDebug extends React.Component {
})}
}
-
-
+
}
}
diff --git a/src/components/ModalExport.jsx b/src/components/ModalExport.jsx
index 19e0c85..5af01cc 100644
--- a/src/components/ModalExport.jsx
+++ b/src/components/ModalExport.jsx
@@ -67,8 +67,8 @@ export default class ModalExport extends React.Component {
className="maputnik-export-modal"
>
-
-
Download Style
+
+ Download Style
Download a JSON style to your computer.
@@ -101,7 +101,7 @@ export default class ModalExport extends React.Component {
Download
-
+
}
diff --git a/src/components/ModalOpen.jsx b/src/components/ModalOpen.jsx
index 42bde38..8ef91be 100644
--- a/src/components/ModalOpen.jsx
+++ b/src/components/ModalOpen.jsx
@@ -27,11 +27,11 @@ class PublicStyle extends React.Component {
aria-label={this.props.title}
onClick={() => this.props.onSelect(this.props.url)}
>
-