mirror of
https://github.com/maputnik/editor.git
synced 2025-12-07 14:50:02 +00:00
Add lint to CI and fix errors (#853)
Adds lint to CI and fixes errors. I'm not sure I'm fully proud of all the solutions there. But there's no lint issues and the lint is being checked as part of the CI. --------- Co-authored-by: Yuri Astrakhan <yuriastrakhan@gmail.com>
This commit is contained in:
45
.eslintrc
Normal file
45
.eslintrc
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2020": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:react/jsx-runtime",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": { "version": "16.4" }
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint",
|
||||||
|
"react-refresh"],
|
||||||
|
"rules": {
|
||||||
|
"react-refresh/only-export-components": [
|
||||||
|
"warn",
|
||||||
|
{ "allowConstantExport": true }
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"warn",
|
||||||
|
{ "argsIgnorePattern": "^_" }
|
||||||
|
],
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"react/prop-types": ["off"],
|
||||||
|
// Disable no-undef. It's covered by @typescript-eslint
|
||||||
|
"no-undef": "off",
|
||||||
|
"indent": ["error", 2]
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"global": "readonly"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: { browser: true, es2020: true },
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'plugin:react/jsx-runtime',
|
|
||||||
'plugin:react-hooks/recommended',
|
|
||||||
],
|
|
||||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
|
||||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
|
||||||
settings: { react: { version: '18.2' } },
|
|
||||||
plugins: ['react-refresh'],
|
|
||||||
rules: {
|
|
||||||
'react-refresh/only-export-components': [
|
|
||||||
'warn',
|
|
||||||
{ allowConstantExport: true },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -42,6 +42,9 @@ jobs:
|
|||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
- run: npm run lint
|
||||||
|
- run: npm run lint-css
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
build-artifacts:
|
build-artifacts:
|
||||||
|
|||||||
383
package-lock.json
generated
383
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint ./src --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0 && npm run lint-css",
|
"lint": "eslint ./src --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"test": "cypress run",
|
"test": "cypress run",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"lint-css": "stylelint \"src/styles/*.scss\"",
|
"lint-css": "stylelint \"src/styles/*.scss\"",
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
"@maplibre/maplibre-gl-style-spec": "^17.0.1",
|
"@maplibre/maplibre-gl-style-spec": "^17.0.1",
|
||||||
"@mdi/js": "^6.6.96",
|
"@mdi/js": "^6.6.96",
|
||||||
"@mdi/react": "^1.5.0",
|
"@mdi/react": "^1.5.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
||||||
|
"@typescript-eslint/parser": "^6.16.0",
|
||||||
"array-move": "^4.0.0",
|
"array-move": "^4.0.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
@@ -103,6 +105,7 @@
|
|||||||
"@types/color": "^3.0.6",
|
"@types/color": "^3.0.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
|
"@types/geojson": "^7946.0.13",
|
||||||
"@types/json-to-ast": "^2.1.4",
|
"@types/json-to-ast": "^2.1.4",
|
||||||
"@types/lodash.capitalize": "^4.2.9",
|
"@types/lodash.capitalize": "^4.2.9",
|
||||||
"@types/lodash.clamp": "^4.0.9",
|
"@types/lodash.clamp": "^4.0.9",
|
||||||
@@ -124,10 +127,10 @@
|
|||||||
"@vitejs/plugin-react": "^4.2.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cypress": "^13.6.1",
|
"cypress": "^13.6.1",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.4",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
"express": "^4.17.3",
|
"express": "^4.17.3",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"istanbul-lib-coverage": "^3.2.0",
|
"istanbul-lib-coverage": "^3.2.0",
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
const metadata: {[key: string]: string} = this.state.mapStyle.metadata || {} as any
|
const metadata: {[key: string]: string} = this.state.mapStyle.metadata || {} as any
|
||||||
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
|
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
|
||||||
|
|
||||||
let glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate;
|
const glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate;
|
||||||
downloadGlyphsMetadata(glyphUrl, fonts => {
|
downloadGlyphsMetadata(glyphUrl, fonts => {
|
||||||
this.setState({ spec: updateRootSpec(this.state.spec, 'glyphs', fonts)})
|
this.setState({ spec: updateRootSpec(this.state.spec, 'glyphs', fonts)})
|
||||||
})
|
})
|
||||||
@@ -402,7 +402,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
// Special case: Duplicate layer id
|
// Special case: Duplicate layer id
|
||||||
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
|
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
|
||||||
if (dupMatch) {
|
if (dupMatch) {
|
||||||
const [_matchStr, index, message] = dupMatch;
|
const [, index, message] = dupMatch;
|
||||||
return {
|
return {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
parsed: {
|
parsed: {
|
||||||
@@ -419,7 +419,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
// Special case: Invalid source
|
// Special case: Invalid source
|
||||||
const invalidSourceMatch = error.message.match(/layers\[(\d+)\]: (source "(?:.*)" not found)/);
|
const invalidSourceMatch = error.message.match(/layers\[(\d+)\]: (source "(?:.*)" not found)/);
|
||||||
if (invalidSourceMatch) {
|
if (invalidSourceMatch) {
|
||||||
const [_matchStr, index, message] = invalidSourceMatch;
|
const [, index, message] = invalidSourceMatch;
|
||||||
return {
|
return {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
parsed: {
|
parsed: {
|
||||||
@@ -435,7 +435,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
||||||
if (layerMatch) {
|
if (layerMatch) {
|
||||||
const [_matchStr, index, group, property, message] = layerMatch;
|
const [, index, group, property, message] = layerMatch;
|
||||||
const key = (group && property) ? [group, property].join(".") : property;
|
const key = (group && property) ? [group, property].join(".") : property;
|
||||||
return {
|
return {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
@@ -466,7 +466,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
try {
|
try {
|
||||||
const objPath = message.split(":")[0];
|
const objPath = message.split(":")[0];
|
||||||
// Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
|
// Errors can be deply nested for example 'layers[0].filter[1][1][0]' we only care upto the property 'layers[0].filter'
|
||||||
const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^\[]+/)![0];
|
const unsetPath = objPath.match(/^\S+?\[\d+\]\.[^[]+/)![0];
|
||||||
unset(dirtyMapStyle, unsetPath);
|
unset(dirtyMapStyle, unsetPath);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -547,14 +547,14 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLayerDestroy = (index: number) => {
|
onLayerDestroy = (index: number) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
const layers = this.state.mapStyle.layers;
|
||||||
const remainingLayers = layers.slice(0);
|
const remainingLayers = layers.slice(0);
|
||||||
remainingLayers.splice(index, 1);
|
remainingLayers.splice(index, 1);
|
||||||
this.onLayersChange(remainingLayers);
|
this.onLayersChange(remainingLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerCopy = (index: number) => {
|
onLayerCopy = (index: number) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
const layers = this.state.mapStyle.layers;
|
||||||
const changedLayers = layers.slice(0)
|
const changedLayers = layers.slice(0)
|
||||||
|
|
||||||
const clonedLayer = cloneDeep(changedLayers[index])
|
const clonedLayer = cloneDeep(changedLayers[index])
|
||||||
@@ -564,7 +564,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLayerVisibilityToggle = (index: number) => {
|
onLayerVisibilityToggle = (index: number) => {
|
||||||
let layers = this.state.mapStyle.layers;
|
const layers = this.state.mapStyle.layers;
|
||||||
const changedLayers = layers.slice(0)
|
const changedLayers = layers.slice(0)
|
||||||
|
|
||||||
const layer = { ...changedLayers[index] }
|
const layer = { ...changedLayers[index] }
|
||||||
@@ -624,11 +624,11 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
fetchSources() {
|
fetchSources() {
|
||||||
const sourceList: {[key: string]: any} = {};
|
const sourceList: {[key: string]: any} = {};
|
||||||
|
|
||||||
for(let [key, val] of Object.entries(this.state.mapStyle.sources)) {
|
for(const [key, val] of Object.entries(this.state.mapStyle.sources)) {
|
||||||
if(
|
if(
|
||||||
!this.state.sources.hasOwnProperty(key) &&
|
!Object.prototype.hasOwnProperty.call(this.state.sources, key) &&
|
||||||
val.type === "vector" &&
|
val.type === "vector" &&
|
||||||
val.hasOwnProperty("url")
|
Object.prototype.hasOwnProperty.call(val, "url")
|
||||||
) {
|
) {
|
||||||
sourceList[key] = {
|
sourceList[key] = {
|
||||||
type: val.type,
|
type: val.type,
|
||||||
@@ -646,30 +646,30 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
fetch(url!, {
|
fetch(url!, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
|
|
||||||
if(!json.hasOwnProperty("vector_layers")) {
|
if(!Object.prototype.hasOwnProperty.call(json, "vector_layers")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new objects before setState
|
// Create new objects before setState
|
||||||
const sources = Object.assign({}, {
|
const sources = Object.assign({}, {
|
||||||
[key]: this.state.sources[key],
|
[key]: this.state.sources[key],
|
||||||
|
});
|
||||||
|
|
||||||
|
for(const layer of json.vector_layers) {
|
||||||
|
(sources[key] as any).layers.push(layer.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug("Updating source: "+key);
|
||||||
|
this.setState({
|
||||||
|
sources: sources
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Failed to process sources for '%s'", url, err);
|
||||||
});
|
});
|
||||||
|
|
||||||
for(let layer of json.vector_layers) {
|
|
||||||
(sources[key] as any).layers.push(layer.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug("Updating source: "+key);
|
|
||||||
this.setState({
|
|
||||||
sources: sources
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error("Failed to process sources for '%s'", url, err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sourceList[key] = this.state.sources[key] || this.state.mapStyle.sources[key];
|
sourceList[key] = this.state.sources[key] || this.state.mapStyle.sources[key];
|
||||||
@@ -760,8 +760,8 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
url.searchParams.set("layer", `${hashVal}~${selectedLayerIndex}`);
|
url.searchParams.set("layer", `${hashVal}~${selectedLayerIndex}`);
|
||||||
|
|
||||||
const openModals = Object.entries(isOpen)
|
const openModals = Object.entries(isOpen)
|
||||||
.map(([key, val]) => (val === true ? key : null))
|
.map(([key, val]) => (val === true ? key : null))
|
||||||
.filter(val => val !== null);
|
.filter(val => val !== null);
|
||||||
|
|
||||||
if (openModals.length > 0) {
|
if (openModals.length > 0) {
|
||||||
url.searchParams.set("modal", openModals.join(","));
|
url.searchParams.set("modal", openModals.join(","));
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ class AppLayout extends React.Component<AppLayoutProps> {
|
|||||||
</div>
|
</div>
|
||||||
{this.props.map}
|
{this.props.map}
|
||||||
{this.props.bottom && <div className="maputnik-layout-bottom">
|
{this.props.bottom && <div className="maputnik-layout-bottom">
|
||||||
{this.props.bottom}
|
{this.props.bottom}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{this.props.modals}
|
{this.props.modals}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default class Block extends React.Component<BlockProps, BlockState> {
|
|||||||
if (event.nativeEvent.target.nodeName !== "INPUT" && !contains) {
|
if (event.nativeEvent.target.nodeName !== "INPUT" && !contains) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default class Doc extends React.Component<DocProps> {
|
|||||||
<tr key={key}>
|
<tr key={key}>
|
||||||
<td>{key}</td>
|
<td>{key}</td>
|
||||||
{Object.keys(headers).map((k) => {
|
{Object.keys(headers).map((k) => {
|
||||||
if (supportObj.hasOwnProperty(k)) {
|
if (Object.prototype.hasOwnProperty.call(supportObj, k)) {
|
||||||
return <td key={k}>{supportObj[k as keyof typeof headers]}</td>;
|
return <td key={k}>{supportObj[k as keyof typeof headers]}</td>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function isIdentityProperty(value: any) {
|
|||||||
return (
|
return (
|
||||||
typeof(value) === 'object' &&
|
typeof(value) === 'object' &&
|
||||||
value.type === "identity" &&
|
value.type === "identity" &&
|
||||||
value.hasOwnProperty("property")
|
Object.prototype.hasOwnProperty.call(value, "property")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import FieldDocLabel from './FieldDocLabel'
|
import FieldDocLabel from './FieldDocLabel'
|
||||||
import Doc from './Doc'
|
import Doc from './Doc'
|
||||||
|
import generateUniqueId from '../libs/document-uid';
|
||||||
|
|
||||||
type FieldsetProps = {
|
type FieldsetProps = {
|
||||||
label?: string,
|
label?: string,
|
||||||
@@ -12,14 +13,12 @@ type FieldsetState = {
|
|||||||
showDoc: boolean
|
showDoc: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
let IDX = 0;
|
|
||||||
|
|
||||||
export default class Fieldset extends React.Component<FieldsetProps, FieldsetState> {
|
export default class Fieldset extends React.Component<FieldsetProps, FieldsetState> {
|
||||||
_labelId: string;
|
_labelId: string;
|
||||||
|
|
||||||
constructor (props: FieldsetProps) {
|
constructor (props: FieldsetProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this._labelId = `fieldset_label_${(IDX++)}`;
|
this._labelId = generateUniqueId(`fieldset_label_`);
|
||||||
this.state = {
|
this.state = {
|
||||||
showDoc: false,
|
showDoc: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import ExpressionProperty from './_ExpressionProperty';
|
|||||||
|
|
||||||
|
|
||||||
function combiningFilter(props: FilterEditorProps): LegacyFilterSpecification | ExpressionSpecification {
|
function combiningFilter(props: FilterEditorProps): LegacyFilterSpecification | ExpressionSpecification {
|
||||||
let filter = props.filter || ['all'];
|
const filter = props.filter || ['all'];
|
||||||
|
|
||||||
if (!Array.isArray(filter)) {
|
if (!Array.isArray(filter)) {
|
||||||
return filter;
|
return filter;
|
||||||
@@ -148,7 +148,7 @@ export default class FilterEditor extends React.Component<FilterEditorProps, Fil
|
|||||||
}
|
}
|
||||||
|
|
||||||
makeExpression = () => {
|
makeExpression = () => {
|
||||||
let filter = combiningFilter(this.props);
|
const filter = combiningFilter(this.props);
|
||||||
this.props.onChange(migrateFilter(filter));
|
this.props.onChange(migrateFilter(filter));
|
||||||
this.setState({
|
this.setState({
|
||||||
displaySimpleFilter: false,
|
displaySimpleFilter: false,
|
||||||
@@ -205,8 +205,8 @@ export default class FilterEditor extends React.Component<FilterEditorProps, Fil
|
|||||||
}
|
}
|
||||||
else if (displaySimpleFilter) {
|
else if (displaySimpleFilter) {
|
||||||
const filter = combiningFilter(this.props);
|
const filter = combiningFilter(this.props);
|
||||||
let combiningOp = filter[0];
|
const combiningOp = filter[0];
|
||||||
let filters = filter.slice(1) as (LegacyFilterSpecification | ExpressionSpecification)[];
|
const filters = filter.slice(1) as (LegacyFilterSpecification | ExpressionSpecification)[];
|
||||||
|
|
||||||
const actions = (
|
const actions = (
|
||||||
<div>
|
<div>
|
||||||
@@ -282,7 +282,7 @@ export default class FilterEditor extends React.Component<FilterEditorProps, Fil
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let {filter} = this.props;
|
const {filter} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -17,16 +17,16 @@ export default class IconLayer extends React.Component<IconLayerProps> {
|
|||||||
render() {
|
render() {
|
||||||
const iconProps = { style: this.props.style }
|
const iconProps = { style: this.props.style }
|
||||||
switch(this.props.type) {
|
switch(this.props.type) {
|
||||||
case 'fill-extrusion': return <IconBackground {...iconProps} />
|
case 'fill-extrusion': return <IconBackground {...iconProps} />
|
||||||
case 'raster': return <IconFill {...iconProps} />
|
case 'raster': return <IconFill {...iconProps} />
|
||||||
case 'hillshade': return <IconFill {...iconProps} />
|
case 'hillshade': return <IconFill {...iconProps} />
|
||||||
case 'heatmap': return <IconFill {...iconProps} />
|
case 'heatmap': return <IconFill {...iconProps} />
|
||||||
case 'fill': return <IconFill {...iconProps} />
|
case 'fill': return <IconFill {...iconProps} />
|
||||||
case 'background': return <IconBackground {...iconProps} />
|
case 'background': return <IconBackground {...iconProps} />
|
||||||
case 'line': return <IconLine {...iconProps} />
|
case 'line': return <IconLine {...iconProps} />
|
||||||
case 'symbol': return <IconSymbol {...iconProps} />
|
case 'symbol': return <IconSymbol {...iconProps} />
|
||||||
case 'circle': return <IconCircle {...iconProps} />
|
case 'circle': return <IconCircle {...iconProps} />
|
||||||
default: return <IconMissing {...iconProps} />
|
default: return <IconMissing {...iconProps} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export default class InputAutocomplete extends React.Component<InputAutocomplete
|
|||||||
"maputnik-autocomplete-menu-item-selected": isHighlighted,
|
"maputnik-autocomplete-menu-item-selected": isHighlighted,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{item[1]}
|
{item[1]}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export default class InputCheckbox extends React.Component<InputCheckboxProps> {
|
|||||||
/>
|
/>
|
||||||
<div className="maputnik-checkbox-box">
|
<div className="maputnik-checkbox-box">
|
||||||
<svg style={{
|
<svg style={{
|
||||||
display: this.props.value ? 'inline' : 'none'
|
display: this.props.value ? 'inline' : 'none'
|
||||||
}} className="maputnik-checkbox-icon" viewBox='0 0 32 32'>
|
}} className="maputnik-checkbox-icon" viewBox='0 0 32 32'>
|
||||||
<path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' />
|
<path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
const picker = <div
|
const picker = <div
|
||||||
className="maputnik-color-picker-offset"
|
className="maputnik-color-picker-offset"
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
left: offset.left,
|
left: offset.left,
|
||||||
top: offset.top,
|
top: offset.top,
|
||||||
}}>
|
}}>
|
||||||
@@ -110,7 +110,7 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
var swatchStyle = {
|
const swatchStyle = {
|
||||||
backgroundColor: this.props.value
|
backgroundColor: this.props.value
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export default class FieldDynamicArray extends React.Component<FieldDynamicArray
|
|||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
key={i}
|
key={i}
|
||||||
className="maputnik-array-block"
|
className="maputnik-array-block"
|
||||||
>
|
>
|
||||||
<div className="maputnik-array-block-action">
|
<div className="maputnik-array-block-action">
|
||||||
{deleteValueBtn}
|
{deleteValueBtn}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { BaseSyntheticEvent } from 'react'
|
import React, { BaseSyntheticEvent } from 'react'
|
||||||
|
import generateUniqueId from '../libs/document-uid';
|
||||||
let IDX = 0;
|
|
||||||
|
|
||||||
export type InputNumberProps = {
|
export type InputNumberProps = {
|
||||||
value?: number
|
value?: number
|
||||||
@@ -32,7 +31,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
constructor(props: InputNumberProps) {
|
constructor(props: InputNumberProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
uuid: IDX++,
|
uuid: +generateUniqueId(),
|
||||||
editing: false,
|
editing: false,
|
||||||
value: props.value,
|
value: props.value,
|
||||||
dirtyValue: props.value,
|
dirtyValue: props.value,
|
||||||
@@ -140,7 +139,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
value = value + (step - snap);
|
value = value + (step - snap);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +154,8 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(
|
if(
|
||||||
this.props.hasOwnProperty("min") && this.props.hasOwnProperty("max") &&
|
Object.prototype.hasOwnProperty.call(this.props, "min") &&
|
||||||
|
Object.prototype.hasOwnProperty.call(this.props, "max") &&
|
||||||
this.props.min !== undefined && this.props.max !== undefined &&
|
this.props.min !== undefined && this.props.max !== undefined &&
|
||||||
this.props.allowRange
|
this.props.allowRange
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -53,66 +53,67 @@ export default class SpecField extends React.Component<SpecFieldProps> {
|
|||||||
'aria-label': this.props['aria-label'],
|
'aria-label': this.props['aria-label'],
|
||||||
}
|
}
|
||||||
switch(this.props.fieldSpec?.type) {
|
switch(this.props.fieldSpec?.type) {
|
||||||
case 'number': return (
|
case 'number': return (
|
||||||
<InputNumber
|
<InputNumber
|
||||||
{...commonProps as InputNumberProps}
|
{...commonProps as InputNumberProps}
|
||||||
min={this.props.fieldSpec.minimum}
|
min={this.props.fieldSpec.minimum}
|
||||||
max={this.props.fieldSpec.maximum}
|
max={this.props.fieldSpec.maximum}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'enum':
|
case 'enum': {
|
||||||
const options = Object.keys(this.props.fieldSpec.values || []).map(v => [v, capitalize(v)])
|
const options = Object.keys(this.props.fieldSpec.values || []).map(v => [v, capitalize(v)])
|
||||||
|
|
||||||
return <InputEnum
|
return <InputEnum
|
||||||
{...commonProps as Omit<InputEnumProps, "options">}
|
{...commonProps as Omit<InputEnumProps, "options">}
|
||||||
options={options}
|
options={options}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
case 'resolvedImage':
|
||||||
|
case 'formatted':
|
||||||
|
case 'string':
|
||||||
|
if (iconProperties.indexOf(this.props.fieldName!) >= 0) {
|
||||||
|
const options = this.props.fieldSpec.values || [];
|
||||||
|
return <InputAutocomplete
|
||||||
|
{...commonProps as Omit<InputAutocompleteProps, "options">}
|
||||||
|
options={options.map(f => [f, f])}
|
||||||
/>
|
/>
|
||||||
case 'resolvedImage':
|
} else {
|
||||||
case 'formatted':
|
return <InputString
|
||||||
case 'string':
|
{...commonProps as InputStringProps}
|
||||||
if (iconProperties.indexOf(this.props.fieldName!) >= 0) {
|
/>
|
||||||
const options = this.props.fieldSpec.values || [];
|
}
|
||||||
return <InputAutocomplete
|
case 'color': return (
|
||||||
{...commonProps as Omit<InputAutocompleteProps, "options">}
|
<InputColor
|
||||||
options={options.map(f => [f, f])}
|
{...commonProps as InputColorProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'boolean': return (
|
||||||
|
<InputCheckbox
|
||||||
|
{...commonProps as InputCheckboxProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'array':
|
||||||
|
if(this.props.fieldName === 'text-font') {
|
||||||
|
return <InputFont
|
||||||
|
{...commonProps as FieldFontProps}
|
||||||
|
fonts={this.props.fieldSpec.values}
|
||||||
|
/>
|
||||||
|
} else {
|
||||||
|
if (this.props.fieldSpec.length) {
|
||||||
|
return <InputArray
|
||||||
|
{...commonProps as FieldArrayProps}
|
||||||
|
type={this.props.fieldSpec.value}
|
||||||
|
length={this.props.fieldSpec.length}
|
||||||
/>
|
/>
|
||||||
} else {
|
} else {
|
||||||
return <InputString
|
return <InputDynamicArray
|
||||||
{...commonProps as InputStringProps}
|
{...commonProps as FieldDynamicArrayProps}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
type={this.props.fieldSpec.value as FieldDynamicArrayProps['type']}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
case 'color': return (
|
}
|
||||||
<InputColor
|
default: return null
|
||||||
{...commonProps as InputColorProps}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'boolean': return (
|
|
||||||
<InputCheckbox
|
|
||||||
{...commonProps as InputCheckboxProps}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'array':
|
|
||||||
if(this.props.fieldName === 'text-font') {
|
|
||||||
return <InputFont
|
|
||||||
{...commonProps as FieldFontProps}
|
|
||||||
fonts={this.props.fieldSpec.values}
|
|
||||||
/>
|
|
||||||
} else {
|
|
||||||
if (this.props.fieldSpec.length) {
|
|
||||||
return <InputArray
|
|
||||||
{...commonProps as FieldArrayProps}
|
|
||||||
type={this.props.fieldSpec.value}
|
|
||||||
length={this.props.fieldSpec.length}
|
|
||||||
/>
|
|
||||||
} else {
|
|
||||||
return <InputDynamicArray
|
|
||||||
{...commonProps as FieldDynamicArrayProps}
|
|
||||||
fieldSpec={this.props.fieldSpec}
|
|
||||||
type={this.props.fieldSpec.value as FieldDynamicArrayProps['type']}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default: return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default class InputString extends React.Component<InputStringProps, Input
|
|||||||
let tag;
|
let tag;
|
||||||
let classes;
|
let classes;
|
||||||
|
|
||||||
if(!!this.props.multi) {
|
if(this.props.multi) {
|
||||||
tag = "textarea"
|
tag = "textarea"
|
||||||
classes = [
|
classes = [
|
||||||
"maputnik-string",
|
"maputnik-string",
|
||||||
@@ -60,14 +60,14 @@ export default class InputString extends React.Component<InputStringProps, Input
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!!this.props.disabled) {
|
if(this.props.disabled) {
|
||||||
classes.push("maputnik-string--disabled");
|
classes.push("maputnik-string--disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
return React.createElement(tag, {
|
return React.createElement(tag, {
|
||||||
"aria-label": this.props["aria-label"],
|
"aria-label": this.props["aria-label"],
|
||||||
"data-wd-key": this.props["data-wd-key"],
|
"data-wd-key": this.props["data-wd-key"],
|
||||||
spellCheck: this.props.hasOwnProperty("spellCheck") ? this.props.spellCheck : !(tag === "input"),
|
spellCheck: Object.prototype.hasOwnProperty.call(this.props, "spellCheck") ? this.props.spellCheck : !(tag === "input"),
|
||||||
disabled: this.props.disabled,
|
disabled: this.props.disabled,
|
||||||
className: classes.join(" "),
|
className: classes.join(" "),
|
||||||
style: this.props.style,
|
style: this.props.style,
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ function validate(url: string) {
|
|||||||
<SmallError>
|
<SmallError>
|
||||||
Must provide protocol {
|
Must provide protocol {
|
||||||
isSsl
|
isSsl
|
||||||
? <code>https://</code>
|
? <code>https://</code>
|
||||||
: <><code>http://</code> or <code>https://</code></>
|
: <><code>http://</code> or <code>https://</code></>
|
||||||
}
|
}
|
||||||
</SmallError>
|
</SmallError>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, {type JSX} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
|
import { Wrapper, Button, Menu, MenuItem } from 'react-aria-menubutton'
|
||||||
import {Accordion} from 'react-accessible-accordion';
|
import {Accordion} from 'react-accessible-accordion';
|
||||||
@@ -150,87 +150,87 @@ export default class LayerEditor extends React.Component<LayerEditorProps, Layer
|
|||||||
|
|
||||||
let sourceLayerIds;
|
let sourceLayerIds;
|
||||||
const layer = this.props.layer as Exclude<LayerSpecification, BackgroundLayerSpecification>;
|
const layer = this.props.layer as Exclude<LayerSpecification, BackgroundLayerSpecification>;
|
||||||
if(this.props.sources.hasOwnProperty(layer.source)) {
|
if(Object.prototype.hasOwnProperty.call(this.props.sources, layer.source)) {
|
||||||
sourceLayerIds = (this.props.sources[layer.source] as any).layers;
|
sourceLayerIds = (this.props.sources[layer.source] as any).layers;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case 'layer': return <div>
|
case 'layer': return <div>
|
||||||
<FieldId
|
<FieldId
|
||||||
value={this.props.layer.id}
|
value={this.props.layer.id}
|
||||||
wdKey="layer-editor.layer-id"
|
wdKey="layer-editor.layer-id"
|
||||||
error={errorData.id}
|
error={errorData.id}
|
||||||
onChange={newId => this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)}
|
onChange={newId => this.props.onLayerIdChange(this.props.layerIndex, this.props.layer.id, newId)}
|
||||||
/>
|
/>
|
||||||
<FieldType
|
<FieldType
|
||||||
disabled={true}
|
disabled={true}
|
||||||
error={errorData.type}
|
error={errorData.type}
|
||||||
value={this.props.layer.type}
|
value={this.props.layer.type}
|
||||||
onChange={newType => this.props.onLayerChanged(
|
onChange={newType => this.props.onLayerChanged(
|
||||||
this.props.layerIndex,
|
this.props.layerIndex,
|
||||||
changeType(this.props.layer, newType)
|
changeType(this.props.layer, newType)
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{this.props.layer.type !== 'background' && <FieldSource
|
{this.props.layer.type !== 'background' && <FieldSource
|
||||||
error={errorData.source}
|
error={errorData.source}
|
||||||
sourceIds={Object.keys(this.props.sources!)}
|
sourceIds={Object.keys(this.props.sources!)}
|
||||||
value={this.props.layer.source}
|
value={this.props.layer.source}
|
||||||
onChange={v => this.changeProperty(null, 'source', v)}
|
onChange={v => this.changeProperty(null, 'source', v)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 &&
|
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.props.layer.type) < 0 &&
|
||||||
<FieldSourceLayer
|
<FieldSourceLayer
|
||||||
error={errorData['source-layer']}
|
error={errorData['source-layer']}
|
||||||
sourceLayerIds={sourceLayerIds}
|
sourceLayerIds={sourceLayerIds}
|
||||||
value={(this.props.layer as any)['source-layer']}
|
value={(this.props.layer as any)['source-layer']}
|
||||||
onChange={v => this.changeProperty(null, 'source-layer', v)}
|
onChange={v => this.changeProperty(null, 'source-layer', v)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<FieldMinZoom
|
<FieldMinZoom
|
||||||
error={errorData.minzoom}
|
error={errorData.minzoom}
|
||||||
value={this.props.layer.minzoom}
|
value={this.props.layer.minzoom}
|
||||||
onChange={v => this.changeProperty(null, 'minzoom', v)}
|
onChange={v => this.changeProperty(null, 'minzoom', v)}
|
||||||
/>
|
/>
|
||||||
<FieldMaxZoom
|
<FieldMaxZoom
|
||||||
error={errorData.maxzoom}
|
error={errorData.maxzoom}
|
||||||
value={this.props.layer.maxzoom}
|
value={this.props.layer.maxzoom}
|
||||||
onChange={v => this.changeProperty(null, 'maxzoom', v)}
|
onChange={v => this.changeProperty(null, 'maxzoom', v)}
|
||||||
/>
|
/>
|
||||||
<FieldComment
|
<FieldComment
|
||||||
error={errorData.comment}
|
error={errorData.comment}
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={v => this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)}
|
onChange={v => this.changeProperty('metadata', 'maputnik:comment', v == "" ? undefined : v)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
case 'filter': return <div>
|
case 'filter': return <div>
|
||||||
<div className="maputnik-filter-editor-wrapper">
|
<div className="maputnik-filter-editor-wrapper">
|
||||||
<FilterEditor
|
<FilterEditor
|
||||||
errors={errorData}
|
|
||||||
filter={(this.props.layer as any).filter}
|
|
||||||
properties={this.props.vectorLayers[(this.props.layer as any)['source-layer']]}
|
|
||||||
onChange={f => this.changeProperty(null, 'filter', f)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
case 'properties':
|
|
||||||
return <PropertyGroup
|
|
||||||
errors={errorData}
|
errors={errorData}
|
||||||
layer={this.props.layer}
|
filter={(this.props.layer as any).filter}
|
||||||
groupFields={fields!}
|
properties={this.props.vectorLayers[(this.props.layer as any)['source-layer']]}
|
||||||
spec={this.props.spec}
|
onChange={f => this.changeProperty(null, 'filter', f)}
|
||||||
onChange={this.changeProperty.bind(this)}
|
|
||||||
/>
|
/>
|
||||||
case 'jsoneditor':
|
</div>
|
||||||
return <FieldJson
|
</div>
|
||||||
layer={this.props.layer}
|
case 'properties':
|
||||||
onChange={(layer) => {
|
return <PropertyGroup
|
||||||
this.props.onLayerChanged(
|
errors={errorData}
|
||||||
this.props.layerIndex,
|
layer={this.props.layer}
|
||||||
layer
|
groupFields={fields!}
|
||||||
);
|
spec={this.props.spec}
|
||||||
}}
|
onChange={this.changeProperty.bind(this)}
|
||||||
/>
|
/>
|
||||||
default: return <></>
|
case 'jsoneditor':
|
||||||
|
return <FieldJson
|
||||||
|
layer={this.props.layer}
|
||||||
|
onChange={(layer) => {
|
||||||
|
this.props.onLayerChanged(
|
||||||
|
this.props.layerIndex,
|
||||||
|
layer
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
default: return <></>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
mdiMenuUp
|
mdiMenuUp
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import {
|
import {
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionItemHeading,
|
AccordionItemHeading,
|
||||||
AccordionItemButton,
|
AccordionItemButton,
|
||||||
AccordionItemPanel,
|
AccordionItemPanel,
|
||||||
} from 'react-accessible-accordion';
|
} from 'react-accessible-accordion';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, {type JSX} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
|
|
||||||
@@ -8,26 +8,8 @@ import ModalAdd from './ModalAdd'
|
|||||||
|
|
||||||
import {SortEndHandler, SortableContainer} from 'react-sortable-hoc';
|
import {SortEndHandler, SortableContainer} from 'react-sortable-hoc';
|
||||||
import type {LayerSpecification} from 'maplibre-gl';
|
import type {LayerSpecification} from 'maplibre-gl';
|
||||||
|
import generateUniqueId from '../libs/document-uid';
|
||||||
function layerPrefix(name: string) {
|
import { findClosestCommonPrefix, layerPrefix } from '../libs/layer';
|
||||||
return name.replace(' ', '-').replace('_', '-').split('-')[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
function findClosestCommonPrefix(layers: LayerSpecification[], idx: number) {
|
|
||||||
const currentLayerPrefix = layerPrefix(layers[idx].id)
|
|
||||||
let closestIdx = idx
|
|
||||||
for (let i = idx; i > 0; i--) {
|
|
||||||
const previousLayerPrefix = layerPrefix(layers[i-1].id)
|
|
||||||
if(previousLayerPrefix === currentLayerPrefix) {
|
|
||||||
closestIdx = i - 1
|
|
||||||
} else {
|
|
||||||
return closestIdx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return closestIdx
|
|
||||||
}
|
|
||||||
|
|
||||||
let UID = 0;
|
|
||||||
|
|
||||||
type LayerListContainerProps = {
|
type LayerListContainerProps = {
|
||||||
layers: LayerSpecification[]
|
layers: LayerSpecification[]
|
||||||
@@ -64,7 +46,7 @@ class LayerListContainer extends React.Component<LayerListContainerProps, LayerL
|
|||||||
collapsedGroups: {},
|
collapsedGroups: {},
|
||||||
areAllGroupsExpanded: false,
|
areAllGroupsExpanded: false,
|
||||||
keys: {
|
keys: {
|
||||||
add: UID++,
|
add: +generateUniqueId(),
|
||||||
},
|
},
|
||||||
isOpen: {
|
isOpen: {
|
||||||
add: false,
|
add: false,
|
||||||
@@ -76,7 +58,7 @@ class LayerListContainer extends React.Component<LayerListContainerProps, LayerL
|
|||||||
this.setState({
|
this.setState({
|
||||||
keys: {
|
keys: {
|
||||||
...this.state.keys,
|
...this.state.keys,
|
||||||
[modalName]: UID++,
|
[modalName]: +generateUniqueId(),
|
||||||
},
|
},
|
||||||
isOpen: {
|
isOpen: {
|
||||||
...this.state.isOpen,
|
...this.state.isOpen,
|
||||||
@@ -88,7 +70,7 @@ class LayerListContainer extends React.Component<LayerListContainerProps, LayerL
|
|||||||
toggleLayers = () => {
|
toggleLayers = () => {
|
||||||
let idx = 0
|
let idx = 0
|
||||||
|
|
||||||
let newGroups: {[key:string]: boolean} = {}
|
const newGroups: {[key:string]: boolean} = {}
|
||||||
|
|
||||||
this.groupedLayers().forEach(layers => {
|
this.groupedLayers().forEach(layers => {
|
||||||
const groupPrefix = layerPrefix(layers[0].id)
|
const groupPrefix = layerPrefix(layers[0].id)
|
||||||
@@ -284,12 +266,12 @@ class LayerListContainer extends React.Component<LayerListContainerProps, LayerL
|
|||||||
ref={this.scrollContainerRef}
|
ref={this.scrollContainerRef}
|
||||||
>
|
>
|
||||||
<ModalAdd
|
<ModalAdd
|
||||||
key={this.state.keys.add}
|
key={this.state.keys.add}
|
||||||
layers={this.props.layers}
|
layers={this.props.layers}
|
||||||
sources={this.props.sources}
|
sources={this.props.sources}
|
||||||
isOpen={this.state.isOpen.add}
|
isOpen={this.state.isOpen.add}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'add')}
|
onOpenToggle={this.toggleModal.bind(this, 'add')}
|
||||||
onLayersChange={this.props.onLayersChange}
|
onLayersChange={this.props.onLayersChange}
|
||||||
/>
|
/>
|
||||||
<header className="maputnik-layer-list-header">
|
<header className="maputnik-layer-list-header">
|
||||||
<span className="maputnik-layer-list-header-title">Layers</span>
|
<span className="maputnik-layer-list-header-title">Layers</span>
|
||||||
@@ -328,6 +310,7 @@ class LayerListContainer extends React.Component<LayerListContainerProps, LayerL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
const LayerListContainerSortable = SortableContainer((props: LayerListContainerProps) => <LayerListContainer {...props} />)
|
const LayerListContainerSortable = SortableContainer((props: LayerListContainerProps) => <LayerListContainer {...props} />)
|
||||||
|
|
||||||
type LayerListProps = LayerListContainerProps & {
|
type LayerListProps = LayerListContainerProps & {
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ type IconActionProps = {
|
|||||||
class IconAction extends React.Component<IconActionProps> {
|
class IconAction extends React.Component<IconActionProps> {
|
||||||
renderIcon() {
|
renderIcon() {
|
||||||
switch(this.props.action) {
|
switch(this.props.action) {
|
||||||
case 'duplicate': return <MdContentCopy />
|
case 'duplicate': return <MdContentCopy />
|
||||||
case 'show': return <MdVisibility />
|
case 'show': return <MdVisibility />
|
||||||
case 'hide': return <MdVisibilityOff />
|
case 'hide': return <MdVisibilityOff />
|
||||||
case 'delete': return <MdDelete />
|
case 'delete': return <MdDelete />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,27 +114,27 @@ class LayerListItem extends React.Component<LayerListItemProps> {
|
|||||||
"maputnik-layer-list-item-selected": this.props.isSelected,
|
"maputnik-layer-list-item-selected": this.props.isSelected,
|
||||||
[this.props.className!]: true,
|
[this.props.className!]: true,
|
||||||
})}>
|
})}>
|
||||||
<DraggableLabel {...this.props} />
|
<DraggableLabel {...this.props} />
|
||||||
<span style={{flexGrow: 1}} />
|
<span style={{flexGrow: 1}} />
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
|
wdKey={"layer-list-item:"+this.props.layerId+":delete"}
|
||||||
action={'delete'}
|
action={'delete'}
|
||||||
classBlockName="delete"
|
classBlockName="delete"
|
||||||
onClick={_e => this.props.onLayerDestroy!(this.props.layerIndex)}
|
onClick={_e => this.props.onLayerDestroy!(this.props.layerIndex)}
|
||||||
/>
|
/>
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
|
wdKey={"layer-list-item:"+this.props.layerId+":copy"}
|
||||||
action={'duplicate'}
|
action={'duplicate'}
|
||||||
classBlockName="duplicate"
|
classBlockName="duplicate"
|
||||||
onClick={_e => this.props.onLayerCopy!(this.props.layerIndex)}
|
onClick={_e => this.props.onLayerCopy!(this.props.layerIndex)}
|
||||||
/>
|
/>
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
|
wdKey={"layer-list-item:"+this.props.layerId+":toggle-visibility"}
|
||||||
action={visibilityAction}
|
action={visibilityAction}
|
||||||
classBlockName="visibility"
|
classBlockName="visibility"
|
||||||
classBlockModifier={visibilityAction}
|
classBlockModifier={visibilityAction}
|
||||||
onClick={_e => this.props.onLayerVisibilityToggle!(this.props.layerIndex)}
|
onClick={_e => this.props.onLayerVisibilityToggle!(this.props.layerIndex)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, {type JSX} from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import MapLibreGl, {LayerSpecification, LngLat, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
|
import MapLibreGl, {LayerSpecification, LngLat, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ function renderFeatureId(feature: InspectFeature) {
|
|||||||
return <Block key={"feature-id"} label={"feature_id"}>
|
return <Block key={"feature-id"} label={"feature_id"}>
|
||||||
<FieldString value={displayValue(feature.id)} style={{backgroundColor: 'transparent'}} />
|
<FieldString value={displayValue(feature.id)} style={{backgroundColor: 'transparent'}} />
|
||||||
</Block>
|
</Block>
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFeature(feature: InspectFeature, idx: number) {
|
function renderFeature(feature: InspectFeature, idx: number) {
|
||||||
return <div key={`${feature.sourceLayer}-${idx}`}>
|
return <div key={`${feature.sourceLayer}-${idx}`}>
|
||||||
@@ -48,7 +48,7 @@ function renderFeature(feature: InspectFeature, idx: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
||||||
let uniqueFeatures: InspectFeature[] = [];
|
const uniqueFeatures: InspectFeature[] = [];
|
||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
const featureIndex = uniqueFeatures.findIndex(feature2 => {
|
const featureIndex = uniqueFeatures.findIndex(feature2 => {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import type {InspectFeature} from './MapMaplibreGlFeaturePropertyPopup';
|
|||||||
function groupFeaturesBySourceLayer(features: InspectFeature[]) {
|
function groupFeaturesBySourceLayer(features: InspectFeature[]) {
|
||||||
const sources: {[key: string]: InspectFeature[]} = {}
|
const sources: {[key: string]: InspectFeature[]} = {}
|
||||||
|
|
||||||
let returnedFeatures: {[key: string]: number} = {}
|
const returnedFeatures: {[key: string]: number} = {}
|
||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
if(returnedFeatures.hasOwnProperty(feature.layer.id)) {
|
if(Object.prototype.hasOwnProperty.call(returnedFeatures, feature.layer.id)) {
|
||||||
returnedFeatures[feature.layer.id]++
|
returnedFeatures[feature.layer.id]++
|
||||||
|
|
||||||
const featureObject = sources[feature.layer['source-layer']].find((f: InspectFeature) => f.layer.id === feature.layer.id)
|
const featureObject = sources[feature.layer['source-layer']].find((f: InspectFeature) => f.layer.id === feature.layer.id)
|
||||||
@@ -42,21 +42,21 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
|||||||
const paintProps = feature.layer.paint;
|
const paintProps = feature.layer.paint;
|
||||||
let propName;
|
let propName;
|
||||||
|
|
||||||
if(paintProps.hasOwnProperty("text-color") && paintProps["text-color"]) {
|
if(Object.prototype.hasOwnProperty.call(paintProps, "text-color") && paintProps["text-color"]) {
|
||||||
propName = "text-color";
|
propName = "text-color";
|
||||||
}
|
}
|
||||||
else if (paintProps.hasOwnProperty("fill-color") && paintProps["fill-color"]) {
|
else if (Object.prototype.hasOwnProperty.call(paintProps, "fill-color") && paintProps["fill-color"]) {
|
||||||
propName = "fill-color";
|
propName = "fill-color";
|
||||||
}
|
}
|
||||||
else if (paintProps.hasOwnProperty("line-color") && paintProps["line-color"]) {
|
else if (Object.prototype.hasOwnProperty.call(paintProps, "line-color") && paintProps["line-color"]) {
|
||||||
propName = "line-color";
|
propName = "line-color";
|
||||||
}
|
}
|
||||||
else if (paintProps.hasOwnProperty("fill-extrusion-color") && paintProps["fill-extrusion-color"]) {
|
else if (Object.prototype.hasOwnProperty.call(paintProps, "fill-extrusion-color") && paintProps["fill-extrusion-color"]) {
|
||||||
propName = "fill-extrusion-color";
|
propName = "fill-extrusion-color";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(propName) {
|
if(propName) {
|
||||||
let color = feature.layer.paint[propName];
|
const color = feature.layer.paint[propName];
|
||||||
return String(color);
|
return String(color);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -80,8 +80,8 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
|||||||
const featureColor = this._getFeatureColor(feature, this.props.zoom);
|
const featureColor = this._getFeatureColor(feature, this.props.zoom);
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="maputnik-popup-layer"
|
className="maputnik-popup-layer"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="maputnik-popup-layer__swatch"
|
className="maputnik-popup-layer__swatch"
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export default class MapOpenLayers extends React.Component<MapOpenLayersProps, M
|
|||||||
});
|
});
|
||||||
|
|
||||||
map.on('pointermove', (evt) => {
|
map.on('pointermove', (evt) => {
|
||||||
var coords = toLonLat(evt.coordinate);
|
const coords = toLonLat(evt.coordinate);
|
||||||
this.setState({
|
this.setState({
|
||||||
cursor: [
|
cursor: [
|
||||||
coords[0].toFixed(2),
|
coords[0].toFixed(2),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export default class ModalAdd extends React.Component<ModalAddProps, ModalAddSta
|
|||||||
const availableSourcesNew = this.getSources(newType);
|
const availableSourcesNew = this.getSources(newType);
|
||||||
|
|
||||||
if(
|
if(
|
||||||
// Type has changed
|
// Type has changed
|
||||||
oldType !== newType
|
oldType !== newType
|
||||||
&& prevState.source !== ""
|
&& prevState.source !== ""
|
||||||
// Was a valid source previously
|
// Was a valid source previously
|
||||||
@@ -113,7 +113,7 @@ export default class ModalAdd extends React.Component<ModalAddProps, ModalAddSta
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let [key, val] of Object.entries(this.props.sources) as any) {
|
for(const [key, val] of Object.entries(this.props.sources) as any) {
|
||||||
const valType = val.type as keyof typeof types;
|
const valType = val.type as keyof typeof types;
|
||||||
if(types[valType] && types[valType].indexOf(type) > -1) {
|
if(types[valType] && types[valType].indexOf(type) > -1) {
|
||||||
sources.push(key);
|
sources.push(key);
|
||||||
@@ -136,41 +136,41 @@ export default class ModalAdd extends React.Component<ModalAddProps, ModalAddSta
|
|||||||
className="maputnik-add-modal"
|
className="maputnik-add-modal"
|
||||||
>
|
>
|
||||||
<div className="maputnik-add-layer">
|
<div className="maputnik-add-layer">
|
||||||
<FieldId
|
<FieldId
|
||||||
value={this.state.id}
|
value={this.state.id}
|
||||||
wdKey="add-layer.layer-id"
|
wdKey="add-layer.layer-id"
|
||||||
onChange={(v: string) => {
|
onChange={(v: string) => {
|
||||||
this.setState({ id: v })
|
this.setState({ id: v })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FieldType
|
<FieldType
|
||||||
value={this.state.type}
|
value={this.state.type}
|
||||||
wdKey="add-layer.layer-type"
|
wdKey="add-layer.layer-type"
|
||||||
onChange={(v: LayerSpecification["type"]) => this.setState({ type: v })}
|
onChange={(v: LayerSpecification["type"]) => this.setState({ type: v })}
|
||||||
/>
|
/>
|
||||||
{this.state.type !== 'background' &&
|
{this.state.type !== 'background' &&
|
||||||
<FieldSource
|
<FieldSource
|
||||||
sourceIds={sources}
|
sourceIds={sources}
|
||||||
wdKey="add-layer.layer-source-block"
|
wdKey="add-layer.layer-source-block"
|
||||||
value={this.state.source}
|
value={this.state.source}
|
||||||
onChange={(v: string) => this.setState({ source: v })}
|
onChange={(v: string) => this.setState({ source: v })}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
|
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
|
||||||
<FieldSourceLayer
|
<FieldSourceLayer
|
||||||
isFixed={true}
|
isFixed={true}
|
||||||
sourceLayerIds={layers}
|
sourceLayerIds={layers}
|
||||||
value={this.state['source-layer']}
|
value={this.state['source-layer']}
|
||||||
onChange={(v: string) => this.setState({ 'source-layer': v })}
|
onChange={(v: string) => this.setState({ 'source-layer': v })}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<InputButton
|
<InputButton
|
||||||
className="maputnik-add-layer-button"
|
className="maputnik-add-layer-button"
|
||||||
onClick={this.addLayer}
|
onClick={this.addLayer}
|
||||||
data-wd-key="add-layer"
|
data-wd-key="add-layer"
|
||||||
>
|
>
|
||||||
Add Layer
|
Add Layer
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default class ModalLoading extends React.Component<ModalLoadingProps> {
|
|||||||
underlayClickExits={false}
|
underlayClickExits={false}
|
||||||
underlayProps={{
|
underlayProps={{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
onClick: (e: Event) => underlayProps(e)
|
onClick: (e: Event) => underlayProps(e)
|
||||||
}}
|
}}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
onOpenToggle={() => this.props.onCancel()}
|
onOpenToggle={() => this.props.onCancel()}
|
||||||
|
|||||||
@@ -91,33 +91,33 @@ export default class ModalOpen extends React.Component<ModalOpenProps, ModalOpen
|
|||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
credentials: "same-origin"
|
credentials: "same-origin"
|
||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((body) => {
|
.then((body) => {
|
||||||
if(canceled) {
|
if(canceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
activeRequest: null,
|
activeRequest: null,
|
||||||
activeRequestUrl: null
|
activeRequestUrl: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStyle = style.ensureStyleValidity(body)
|
const mapStyle = style.ensureStyleValidity(body)
|
||||||
console.log('Loaded style ', mapStyle.id)
|
console.log('Loaded style ', mapStyle.id)
|
||||||
this.props.onStyleOpen(mapStyle)
|
this.props.onStyleOpen(mapStyle)
|
||||||
this.onOpenToggle()
|
this.onOpenToggle()
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: `Failed to load: '${styleUrl}'`,
|
error: `Failed to load: '${styleUrl}'`,
|
||||||
activeRequest: null,
|
activeRequest: null,
|
||||||
activeRequestUrl: null
|
activeRequestUrl: null
|
||||||
});
|
});
|
||||||
console.error(err);
|
console.error(err);
|
||||||
console.warn('Could not open the style URL', styleUrl)
|
console.warn('Could not open the style URL', styleUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
activeRequest: {
|
activeRequest: {
|
||||||
@@ -135,7 +135,7 @@ export default class ModalOpen extends React.Component<ModalOpenProps, ModalOpen
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUpload = (_: any, files: Result[]) => {
|
onUpload = (_: any, files: Result[]) => {
|
||||||
const [_e, file] = files[0];
|
const [, file] = files[0];
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
this.clearError();
|
this.clearError();
|
||||||
@@ -244,7 +244,7 @@ export default class ModalOpen extends React.Component<ModalOpenProps, ModalOpen
|
|||||||
Open one of the publicly available styles to start from.
|
Open one of the publicly available styles to start from.
|
||||||
</p>
|
</p>
|
||||||
<div className="maputnik-style-gallery-container">
|
<div className="maputnik-style-gallery-container">
|
||||||
{styleOptions}
|
{styleOptions}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ type PublicSourceProps = {
|
|||||||
class PublicSource extends React.Component<PublicSourceProps> {
|
class PublicSource extends React.Component<PublicSourceProps> {
|
||||||
render() {
|
render() {
|
||||||
return <div className="maputnik-public-source">
|
return <div className="maputnik-public-source">
|
||||||
<InputButton
|
<InputButton
|
||||||
className="maputnik-public-source-select"
|
className="maputnik-public-source-select"
|
||||||
onClick={() => this.props.onSelect(this.props.id)}
|
onClick={() => this.props.onSelect(this.props.id)}
|
||||||
>
|
>
|
||||||
<div className="maputnik-public-source-info">
|
<div className="maputnik-public-source-info">
|
||||||
<p className="maputnik-public-source-name">{this.props.title}</p>
|
<p className="maputnik-public-source-name">{this.props.title}</p>
|
||||||
<p className="maputnik-public-source-id">#{this.props.id}</p>
|
<p className="maputnik-public-source-id">#{this.props.id}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<MdAddCircleOutline />
|
<MdAddCircleOutline />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,68 +127,68 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
const {protocol} = window.location;
|
const {protocol} = window.location;
|
||||||
|
|
||||||
switch(mode) {
|
switch(mode) {
|
||||||
case 'geojson_url': return {
|
case 'geojson_url': return {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
data: `${protocol}//localhost:3000/geojson.json`
|
data: `${protocol}//localhost:3000/geojson.json`
|
||||||
}
|
}
|
||||||
case 'geojson_json': return {
|
case 'geojson_json': return {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
cluster: (source as GeoJSONSourceSpecification).cluster || false,
|
cluster: (source as GeoJSONSourceSpecification).cluster || false,
|
||||||
data: {}
|
data: {}
|
||||||
}
|
}
|
||||||
case 'tilejson_vector': return {
|
case 'tilejson_vector': return {
|
||||||
type: 'vector',
|
type: 'vector',
|
||||||
url: (source as VectorSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
|
url: (source as VectorSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
|
||||||
}
|
}
|
||||||
case 'tilexyz_vector': return {
|
case 'tilexyz_vector': return {
|
||||||
type: 'vector',
|
type: 'vector',
|
||||||
tiles: (source as VectorSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
tiles: (source as VectorSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
||||||
minzoom: (source as VectorSourceSpecification).minzoom || 0,
|
minzoom: (source as VectorSourceSpecification).minzoom || 0,
|
||||||
maxzoom: (source as VectorSourceSpecification).maxzoom || 14
|
maxzoom: (source as VectorSourceSpecification).maxzoom || 14
|
||||||
}
|
}
|
||||||
case 'tilejson_raster': return {
|
case 'tilejson_raster': return {
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
url: (source as RasterSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
|
url: (source as RasterSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
|
||||||
}
|
}
|
||||||
case 'tilexyz_raster': return {
|
case 'tilexyz_raster': return {
|
||||||
type: 'raster',
|
type: 'raster',
|
||||||
tiles: (source as RasterSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
tiles: (source as RasterSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
||||||
minzoom: (source as RasterSourceSpecification).minzoom || 0,
|
minzoom: (source as RasterSourceSpecification).minzoom || 0,
|
||||||
maxzoom: (source as RasterSourceSpecification).maxzoom || 14
|
maxzoom: (source as RasterSourceSpecification).maxzoom || 14
|
||||||
}
|
}
|
||||||
case 'tilejson_raster-dem': return {
|
case 'tilejson_raster-dem': return {
|
||||||
type: 'raster-dem',
|
type: 'raster-dem',
|
||||||
url: (source as RasterDEMSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
|
url: (source as RasterDEMSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json`
|
||||||
}
|
}
|
||||||
case 'tilexyz_raster-dem': return {
|
case 'tilexyz_raster-dem': return {
|
||||||
type: 'raster-dem',
|
type: 'raster-dem',
|
||||||
tiles: (source as RasterDEMSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
tiles: (source as RasterDEMSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`],
|
||||||
minzoom: (source as RasterDEMSourceSpecification).minzoom || 0,
|
minzoom: (source as RasterDEMSourceSpecification).minzoom || 0,
|
||||||
maxzoom: (source as RasterDEMSourceSpecification).maxzoom || 14
|
maxzoom: (source as RasterDEMSourceSpecification).maxzoom || 14
|
||||||
}
|
}
|
||||||
case 'image': return {
|
case 'image': return {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
url: `${protocol}//localhost:3000/image.png`,
|
url: `${protocol}//localhost:3000/image.png`,
|
||||||
coordinates: [
|
coordinates: [
|
||||||
[0,0],
|
[0,0],
|
||||||
[0,0],
|
[0,0],
|
||||||
[0,0],
|
[0,0],
|
||||||
[0,0],
|
[0,0],
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
case 'video': return {
|
case 'video': return {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
urls: [
|
urls: [
|
||||||
`${protocol}//localhost:3000/movie.mp4`
|
`${protocol}//localhost:3000/movie.mp4`
|
||||||
],
|
],
|
||||||
coordinates: [
|
coordinates: [
|
||||||
[0,0],
|
[0,0],
|
||||||
[0,0],
|
[0,0],
|
||||||
[0,0],
|
[0,0],
|
||||||
[0,0],
|
[0,0],
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
default: return {} as any
|
default: return {} as any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
/>
|
/>
|
||||||
<InputButton
|
<InputButton
|
||||||
className="maputnik-add-source-button"
|
className="maputnik-add-source-button"
|
||||||
onClick={this.onAdd}
|
onClick={this.onAdd}
|
||||||
>
|
>
|
||||||
Add Source
|
Add Source
|
||||||
</InputButton>
|
</InputButton>
|
||||||
@@ -308,16 +308,16 @@ export default class ModalSources extends React.Component<ModalSourcesProps> {
|
|||||||
Add one of the publicly available sources to your style.
|
Add one of the publicly available sources to your style.
|
||||||
</p>
|
</p>
|
||||||
<div className="maputnik-public-sources" style={{maxWidth: 500}}>
|
<div className="maputnik-public-sources" style={{maxWidth: 500}}>
|
||||||
{tilesetOptions}
|
{tilesetOptions}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>Add New Source</h1>
|
<h1>Add New Source</h1>
|
||||||
<p>Add a new source to your style. You can only choose the source type and id at creation time!</p>
|
<p>Add a new source to your style. You can only choose the source type and id at creation time!</p>
|
||||||
<AddSource
|
<AddSource
|
||||||
onAdd={(sourceId: string, source: SourceSpecification) => this.props.onStyleChanged(addSource(mapStyle, sourceId, source))}
|
onAdd={(sourceId: string, source: SourceSpecification) => this.props.onStyleChanged(addSource(mapStyle, sourceId, source))}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,28 +269,28 @@ export default class ModalSourcesTypeEditor extends React.Component<ModalSources
|
|||||||
onChange: this.props.onChange,
|
onChange: this.props.onChange,
|
||||||
}
|
}
|
||||||
switch(this.props.mode) {
|
switch(this.props.mode) {
|
||||||
case 'geojson_url': return <GeoJSONSourceUrlEditor {...commonProps} />
|
case 'geojson_url': return <GeoJSONSourceUrlEditor {...commonProps} />
|
||||||
case 'geojson_json': return <GeoJSONSourceFieldJsonEditor {...commonProps} />
|
case 'geojson_json': return <GeoJSONSourceFieldJsonEditor {...commonProps} />
|
||||||
case 'tilejson_vector': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_vector': return <TileJSONSourceEditor {...commonProps} />
|
||||||
case 'tilexyz_vector': return <TileURLSourceEditor {...commonProps} />
|
case 'tilexyz_vector': return <TileURLSourceEditor {...commonProps} />
|
||||||
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
|
||||||
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
|
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
|
||||||
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
|
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
|
||||||
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
|
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps}>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
label={"Encoding"}
|
label={"Encoding"}
|
||||||
fieldSpec={latest.source_raster_dem.encoding}
|
fieldSpec={latest.source_raster_dem.encoding}
|
||||||
options={Object.keys(latest.source_raster_dem.encoding.values)}
|
options={Object.keys(latest.source_raster_dem.encoding.values)}
|
||||||
onChange={encoding => this.props.onChange({
|
onChange={encoding => this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
encoding: encoding
|
encoding: encoding
|
||||||
})}
|
})}
|
||||||
value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
|
value={this.props.source.encoding || latest.source_raster_dem.encoding.default}
|
||||||
/>
|
/>
|
||||||
</TileURLSourceEditor>
|
</TileURLSourceEditor>
|
||||||
case 'image': return <ImageSourceEditor {...commonProps} />
|
case 'image': return <ImageSourceEditor {...commonProps} />
|
||||||
case 'video': return <VideoSourceEditor {...commonProps} />
|
case 'video': return <VideoSourceEditor {...commonProps} />
|
||||||
default: return null
|
default: return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function setStopRefs(props: DataPropertyProps, state: DataPropertyState) {
|
|||||||
|
|
||||||
if(props.value && props.value.stops) {
|
if(props.value && props.value.stops) {
|
||||||
props.value.stops.forEach((_val, idx) => {
|
props.value.stops.forEach((_val, idx) => {
|
||||||
if(!state.refs.hasOwnProperty(idx)) {
|
if(!Object.prototype.hasOwnProperty.call(state.refs, idx)) {
|
||||||
if(!newRefs) {
|
if(!newRefs) {
|
||||||
newRefs = {...state};
|
newRefs = {...state};
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ export default class DataProperty extends React.Component<DataPropertyProps, Dat
|
|||||||
data: stop
|
data: stop
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Sort by zoom
|
// Sort by zoom
|
||||||
.sort((a, b) => sortNumerically(a.data[0].zoom, b.data[0].zoom));
|
.sort((a, b) => sortNumerically(a.data[0].zoom, b.data[0].zoom));
|
||||||
|
|
||||||
// Fetch the new position of the stops
|
// Fetch the new position of the stops
|
||||||
|
|||||||
@@ -96,12 +96,12 @@ export default class ExpressionProperty extends React.Component<ExpressionProper
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Object.entries(errors!)
|
Object.entries(errors!)
|
||||||
.filter(([key, _error]) => {
|
.filter(([key, _error]) => {
|
||||||
return key.startsWith(errorKeyStart);
|
return key.startsWith(errorKeyStart);
|
||||||
})
|
})
|
||||||
.forEach(([_key, error]) => {
|
.forEach(([_key, error]) => {
|
||||||
return foundErrors.push(error);
|
return foundErrors.push(error);
|
||||||
})
|
})
|
||||||
|
|
||||||
if (fieldError) {
|
if (fieldError) {
|
||||||
foundErrors.push(fieldError);
|
foundErrors.push(fieldError);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function setStopRefs(props: ZoomPropertyProps, state: ZoomPropertyState) {
|
|||||||
|
|
||||||
if(props.value && (props.value as ZoomWithStops).stops) {
|
if(props.value && (props.value as ZoomWithStops).stops) {
|
||||||
(props.value as ZoomWithStops).stops.forEach((_val, idx: number) => {
|
(props.value as ZoomWithStops).stops.forEach((_val, idx: number) => {
|
||||||
if(!state.refs.hasOwnProperty(idx)) {
|
if(Object.prototype.hasOwnProperty.call(!state.refs, idx)) {
|
||||||
if(!newRefs) {
|
if(!newRefs) {
|
||||||
newRefs = {...state};
|
newRefs = {...state};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { IconContext } from "react-icons";
|
import { IconContext } from "react-icons";
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import './favicon.ico'
|
import './favicon.ico'
|
||||||
|
|||||||
@@ -29,18 +29,18 @@ export class ApiStyleStore {
|
|||||||
fetch(this.localUrl + '/styles', {
|
fetch(this.localUrl + '/styles', {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((body) => {
|
.then((body) => {
|
||||||
const styleIds = body;
|
const styleIds = body;
|
||||||
this.latestStyleId = styleIds[0]
|
this.latestStyleId = styleIds[0]
|
||||||
this.notifyLocalChanges()
|
this.notifyLocalChanges()
|
||||||
cb(null)
|
cb(null)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
cb(new Error('Can not connect to style API'))
|
cb(new Error('Can not connect to style API'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyLocalChanges() {
|
notifyLocalChanges() {
|
||||||
@@ -64,12 +64,12 @@ export class ApiStyleStore {
|
|||||||
fetch(this.localUrl + '/styles/' + this.latestStyleId, {
|
fetch(this.localUrl + '/styles/' + this.latestStyleId, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(function(body) {
|
.then(function(body) {
|
||||||
cb(style.ensureStyleValidity(body))
|
cb(style.ensureStyleValidity(body))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No latest style available. You need to init the api backend first.')
|
throw new Error('No latest style available. You need to init the api backend first.')
|
||||||
}
|
}
|
||||||
@@ -92,9 +92,9 @@ export class ApiStyleStore {
|
|||||||
},
|
},
|
||||||
body: styleJSON
|
body: styleJSON
|
||||||
})
|
})
|
||||||
.catch(function(error) {
|
.catch(function(error) {
|
||||||
if(error) console.error(error)
|
if(error) console.error(error)
|
||||||
})
|
})
|
||||||
return mapStyle
|
return mapStyle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function get(namespace: keyof DebugStore, key: string) {
|
|||||||
if(!enabled()) {
|
if(!enabled()) {
|
||||||
throw genErr();
|
throw genErr();
|
||||||
}
|
}
|
||||||
if(debugStore.hasOwnProperty(namespace)) {
|
if(Object.prototype.hasOwnProperty.call(debugStore, namespace)) {
|
||||||
return debugStore[namespace][key];
|
return debugStore[namespace][key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,3 +70,21 @@ export function changeProperty(layer: LayerSpecification, group: keyof LayerSpec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function layerPrefix(name: string) {
|
||||||
|
return name.replace(' ', '-').replace('_', '-').split('-')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findClosestCommonPrefix(layers: LayerSpecification[], idx: number) {
|
||||||
|
const currentLayerPrefix = layerPrefix(layers[idx].id)
|
||||||
|
let closestIdx = idx
|
||||||
|
for (let i = idx; i > 0; i--) {
|
||||||
|
const previousLayerPrefix = layerPrefix(layers[i-1].id)
|
||||||
|
if(previousLayerPrefix === currentLayerPrefix) {
|
||||||
|
closestIdx = i - 1
|
||||||
|
} else {
|
||||||
|
return closestIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestIdx
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ function loadJSON(url: string, defaultValue: any, cb: (...args: any[]) => void)
|
|||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
credentials: "same-origin"
|
credentials: "same-origin"
|
||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(function(body) {
|
.then(function(body) {
|
||||||
cb(body)
|
cb(body)
|
||||||
})
|
})
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
console.warn('Can not metadata for ' + url)
|
console.warn('Can not metadata for ' + url)
|
||||||
cb(defaultValue)
|
cb(defaultValue)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadGlyphsMetadata(urlTemplate: string, cb: (...args: any[]) => void) {
|
export function downloadGlyphsMetadata(urlTemplate: string, cb: (...args: any[]) => void) {
|
||||||
@@ -22,14 +22,14 @@ export function downloadGlyphsMetadata(urlTemplate: string, cb: (...args: any[])
|
|||||||
|
|
||||||
// Special handling because Tileserver GL serves the fontstacks metadata differently
|
// Special handling because Tileserver GL serves the fontstacks metadata differently
|
||||||
// https://github.com/klokantech/tileserver-gl/pull/104#issuecomment-274444087
|
// https://github.com/klokantech/tileserver-gl/pull/104#issuecomment-274444087
|
||||||
let urlObj = npmurl.parse(urlTemplate);
|
const urlObj = npmurl.parse(urlTemplate);
|
||||||
const normPathPart = '/%7Bfontstack%7D/%7Brange%7D.pbf';
|
const normPathPart = '/%7Bfontstack%7D/%7Brange%7D.pbf';
|
||||||
if(urlObj.pathname === normPathPart) {
|
if(urlObj.pathname === normPathPart) {
|
||||||
urlObj.pathname = '/fontstacks.json';
|
urlObj.pathname = '/fontstacks.json';
|
||||||
} else {
|
} else {
|
||||||
urlObj.pathname = urlObj.pathname!.replace(normPathPart, '.json');
|
urlObj.pathname = urlObj.pathname!.replace(normPathPart, '.json');
|
||||||
}
|
}
|
||||||
let url = npmurl.format(urlObj);
|
const url = npmurl.format(urlObj);
|
||||||
|
|
||||||
loadJSON(url, [], cb)
|
loadJSON(url, [], cb)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ export function loadDefaultStyle(cb: (...args: any[]) => void) {
|
|||||||
function loadStoredStyles() {
|
function loadStoredStyles() {
|
||||||
const styles = []
|
const styles = []
|
||||||
for (let i = 0; i < window.localStorage.length; i++) {
|
for (let i = 0; i < window.localStorage.length; i++) {
|
||||||
const key = window.localStorage.key(i)
|
const key = window.localStorage.key(i)
|
||||||
if(isStyleKey(key!)) {
|
if(isStyleKey(key!)) {
|
||||||
styles.push(fromKey(key!))
|
styles.push(fromKey(key!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return styles
|
return styles
|
||||||
}
|
}
|
||||||
@@ -70,10 +70,10 @@ export class StyleStore {
|
|||||||
// Delete entire style history
|
// Delete entire style history
|
||||||
purge() {
|
purge() {
|
||||||
for (let i = 0; i < window.localStorage.length; i++) {
|
for (let i = 0; i < window.localStorage.length; i++) {
|
||||||
const key = window.localStorage.key(i) as string;
|
const key = window.localStorage.key(i) as string;
|
||||||
if(key.startsWith(storagePrefix)) {
|
if(key.startsWith(storagePrefix)) {
|
||||||
window.localStorage.removeItem(key)
|
window.localStorage.removeItem(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ export function loadStyleUrl(styleUrl: string, cb: (...args: any[]) => void) {
|
|||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
credentials: "same-origin"
|
credentials: "same-origin"
|
||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(function(body) {
|
.then(function(body) {
|
||||||
cb(style.ensureStyleValidity(body))
|
cb(style.ensureStyleValidity(body))
|
||||||
})
|
})
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
console.warn('Could not fetch default style', styleUrl)
|
console.warn('Could not fetch default style', styleUrl)
|
||||||
cb(style.emptyStyle)
|
cb(style.emptyStyle)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeStyleQuerystring() {
|
export function removeStyleQuerystring() {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@use 'sass:color';
|
||||||
// MAP
|
// MAP
|
||||||
.maputnik-map__container {
|
.maputnik-map__container {
|
||||||
background: white;
|
background: white;
|
||||||
@@ -110,12 +111,12 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: lighten($color-midgray, 12);
|
background-color: color.adjust($color-midgray, $lightness: 12%);
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: darken($color-midgray, 5);
|
background-color: color.adjust($color-midgray, $lightness: -5%);
|
||||||
color: $color-midgray;
|
color: $color-midgray;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-filter-editor {
|
.maputnik-filter-editor {
|
||||||
@extend .clearfix;
|
@extend .clearfix; /* stylelint-disable-line */
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-delete-filter {
|
.maputnik-delete-filter {
|
||||||
@extend .maputnik-icon-button;
|
@extend .maputnik-icon-button; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-filter-editor-block-action {
|
.maputnik-filter-editor-block-action {
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-radio-as-button {
|
.maputnik-radio-as-button {
|
||||||
@extend .maputnik-button;
|
@extend .maputnik-button; /* stylelint-disable-line */
|
||||||
|
|
||||||
border: solid 1px transparent;
|
border: solid 1px transparent;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@use 'sass:color';
|
||||||
//INPUT
|
//INPUT
|
||||||
.maputnik-input {
|
.maputnik-input {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
padding-right: $margin-2;
|
padding-right: $margin-2;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: $color-gray;
|
background-color: $color-gray;
|
||||||
color: lighten($color-lowgray, 12);
|
color: color.adjust($color-lowgray, $lightness: 12%);
|
||||||
|
|
||||||
&:invalid {
|
&:invalid {
|
||||||
border: solid 1px #B71C1C;
|
border: solid 1px #B71C1C;
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-string {
|
.maputnik-string {
|
||||||
@extend .maputnik-input;
|
@extend .maputnik-input; /* stylelint-disable-line */
|
||||||
|
|
||||||
&--multi {
|
&--multi {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
@@ -43,12 +44,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-number {
|
.maputnik-number {
|
||||||
@extend .maputnik-input;
|
@extend .maputnik-input; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
//COLOR PICKER
|
//COLOR PICKER
|
||||||
.maputnik-color {
|
.maputnik-color {
|
||||||
@extend .maputnik-input;
|
@extend .maputnik-input; /* stylelint-disable-line */
|
||||||
|
|
||||||
height: 26px;
|
height: 26px;
|
||||||
}
|
}
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
|
|
||||||
// SELECT
|
// SELECT
|
||||||
.maputnik-select {
|
.maputnik-select {
|
||||||
@extend .maputnik-input;
|
@extend .maputnik-input; /* stylelint-disable-line */
|
||||||
|
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
@@ -117,7 +118,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-button-selected {
|
.maputnik-button-selected {
|
||||||
background-color: lighten($color-midgray, 12);
|
background-color: color.adjust($color-midgray, $lightness: 12%);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
&-wrapper {
|
&-wrapper {
|
||||||
@extend .maputnik-input;
|
@extend .maputnik-input; /* stylelint-disable-line */
|
||||||
|
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@use 'sass:color';
|
||||||
// LAYER LIST
|
// LAYER LIST
|
||||||
.maputnik-layer-list {
|
.maputnik-layer-list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
font-size: $font-size-6;
|
font-size: $font-size-6;
|
||||||
border-bottom-color: lighten($color-black, 0.1);
|
border-bottom-color: color.adjust($color-black, $lightness: 0.1%);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
height: 15px;
|
height: 15px;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: darken($color-lowgray, 20);
|
fill: color.adjust($color-lowgray, $lightness: -20%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
fill: $color-white;
|
fill: $color-white;
|
||||||
@@ -101,13 +102,13 @@
|
|||||||
|
|
||||||
.maputnik-layer-list-item:hover,
|
.maputnik-layer-list-item:hover,
|
||||||
.maputnik-layer-list-item-selected {
|
.maputnik-layer-list-item-selected {
|
||||||
background-color: lighten($color-black, 2);
|
background-color: color.adjust($color-black, $lightness: 2%);
|
||||||
|
|
||||||
.maputnik-layer-list-icon-action {
|
.maputnik-layer-list-icon-action {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: darken($color-lowgray, 0.5);
|
fill: color.adjust($color-lowgray, $lightness: -0.5%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +152,7 @@
|
|||||||
border: solid 1px transparent;
|
border: solid 1px transparent;
|
||||||
font-size: $font-size-6;
|
font-size: $font-size-6;
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
background-color: lighten($color-black, 2);
|
background-color: color.adjust($color-black, $lightness: 2%);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: $margin-2;
|
padding: $margin-2;
|
||||||
|
|
||||||
@@ -185,7 +186,7 @@
|
|||||||
.maputnik-layer-editor-group {
|
.maputnik-layer-editor-group {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: $font-size-5;
|
font-size: $font-size-5;
|
||||||
background-color: lighten($color-black, 2);
|
background-color: color.adjust($color-black, $lightness: 2%);
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
// PROPERTY
|
// PROPERTY
|
||||||
.maputnik-default-property {
|
.maputnik-default-property {
|
||||||
.maputnik-input-block-label {
|
.maputnik-input-block-label {
|
||||||
color: darken($color-lowgray, 20%);
|
color: color.adjust($color-lowgray, $lightness: -20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-string,
|
.maputnik-string,
|
||||||
@@ -240,8 +241,8 @@
|
|||||||
.maputnik-color,
|
.maputnik-color,
|
||||||
.maputnik-select,
|
.maputnik-select,
|
||||||
.maputnik-checkbox-wrapper {
|
.maputnik-checkbox-wrapper {
|
||||||
background-color: darken($color-gray, 2%);
|
background-color: color.adjust($color-gray, $lightness: -2%);
|
||||||
color: darken($color-lowgray, 20%);
|
color: color.adjust($color-lowgray, $lightness: -20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-make-zoom-function svg {
|
.maputnik-make-zoom-function svg {
|
||||||
@@ -249,17 +250,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-multibutton .maputnik-button {
|
.maputnik-multibutton .maputnik-button {
|
||||||
background-color: darken($color-midgray, 10%);
|
background-color: color.adjust($color-midgray, $lightness: -10%);
|
||||||
color: darken($color-lowgray, 20%);
|
color: color.adjust($color-lowgray, $lightness: -20%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: lighten($color-midgray, 12);
|
background-color: color.adjust($color-midgray, $lightness: 12%);
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-multibutton .maputnik-button-selected {
|
.maputnik-multibutton .maputnik-button-selected {
|
||||||
background-color: darken($color-midgray, 2%);
|
background-color: color.adjust($color-midgray, $lightness: -2%);
|
||||||
color: $color-lowgray;
|
color: $color-lowgray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,12 +72,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-modal-header-space {
|
.maputnik-modal-header-space {
|
||||||
@extend .maputnik-space;
|
@extend .maputnik-space; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
//OPEN MODAL
|
//OPEN MODAL
|
||||||
.maputnik-upload-button {
|
.maputnik-upload-button {
|
||||||
@extend .maputnik-big-button;
|
@extend .maputnik-big-button; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-style-gallery-container {
|
.maputnik-style-gallery-container {
|
||||||
@@ -139,12 +139,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-layer {
|
.maputnik-add-layer {
|
||||||
@extend .clearfix;
|
@extend .clearfix; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
//ADD MODAL
|
//ADD MODAL
|
||||||
.maputnik-add-layer-button {
|
.maputnik-add-layer-button {
|
||||||
@extend .maputnik-big-button;
|
@extend .maputnik-big-button; /* stylelint-disable-line */
|
||||||
|
|
||||||
margin-right: $margin-3;
|
margin-right: $margin-3;
|
||||||
float: right;
|
float: right;
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-source {
|
.maputnik-add-source {
|
||||||
@extend .clearfix;
|
@extend .clearfix; /* stylelint-disable-line */
|
||||||
|
|
||||||
.maputnik-input-block-label {
|
.maputnik-input-block-label {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-source-button {
|
.maputnik-add-source-button {
|
||||||
@extend .maputnik-big-button;
|
@extend .maputnik-big-button; /* stylelint-disable-line */
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@use 'sass:color';
|
||||||
// TOOLBAR
|
// TOOLBAR
|
||||||
.maputnik-toolbar {
|
.maputnik-toolbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover .maputnik-toolbar-link-wrapper {
|
&:hover .maputnik-toolbar-link-wrapper {
|
||||||
background-color: lighten($color-midgray, 12);
|
background-color: color.adjust($color-midgray, $lightness: 12%);
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,13 +94,13 @@
|
|||||||
.maputnik-toolbar-action {
|
.maputnik-toolbar-action {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
@extend .maputnik-toolbar-link;
|
@extend .maputnik-toolbar-link; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-toolbar-select {
|
.maputnik-toolbar-select {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
@extend .maputnik-toolbar-link;
|
@extend .maputnik-toolbar-link; /* stylelint-disable-line */
|
||||||
|
|
||||||
select {
|
select {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding: 0 $margin-2 0 0;
|
padding: 0 $margin-2 0 0;
|
||||||
|
|
||||||
@extend .maputnik-icon-button;
|
@extend .maputnik-icon-button; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZOOM PROPERTY
|
// ZOOM PROPERTY
|
||||||
.maputnik-zoom-spec-property {
|
.maputnik-zoom-spec-property {
|
||||||
@extend .clearfix;
|
@extend .clearfix; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-zoom-spec-property-label {
|
.maputnik-zoom-spec-property-label {
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
@extend .maputnik-icon-button;
|
@extend .maputnik-icon-button; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-add-stop {
|
.maputnik-add-stop {
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding: 0 $margin-2 0 0;
|
padding: 0 $margin-2 0 0;
|
||||||
|
|
||||||
@extend .maputnik-icon-button;
|
@extend .maputnik-icon-button; /* stylelint-disable-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-data-spec-property {
|
.maputnik-data-spec-property {
|
||||||
|
|||||||
@@ -28,9 +28,6 @@
|
|||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maputnik-data-spec-property {
|
|
||||||
}
|
|
||||||
|
|
||||||
.maputnik-data-fieldset-inner {
|
.maputnik-data-fieldset-inner {
|
||||||
background: $color-black;
|
background: $color-black;
|
||||||
border: solid 1px $color-midgray;
|
border: solid 1px $color-midgray;
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) =>
|
|||||||
try {
|
try {
|
||||||
parser.parse(text);
|
parser.parse(text);
|
||||||
}
|
}
|
||||||
catch (e) {}
|
catch (e) {
|
||||||
|
// ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
if (found.length > 0) {
|
if (found.length > 0) {
|
||||||
// JSON invalid so don't go any further
|
// JSON invalid so don't go any further
|
||||||
@@ -154,7 +156,7 @@ CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) =>
|
|||||||
found.push(err);
|
found.push(err);
|
||||||
}
|
}
|
||||||
else if (key) {
|
else if (key) {
|
||||||
const path = key.replace(/^\[|\]$/g, "").split(/\.|[\[\]]+/).filter(Boolean)
|
const path = key.replace(/^\[|\]$/g, "").split(/\.|[[\]]+/).filter(Boolean)
|
||||||
const parsedError = getArrayPositionalFromAst(ast, path);
|
const parsedError = getArrayPositionalFromAst(ast, path);
|
||||||
if (!parsedError) {
|
if (!parsedError) {
|
||||||
console.warn("Something went wrong parsing error:", error);
|
console.warn("Something went wrong parsing error:", error);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* If we don't have a default value just make one up
|
* If we don't have a default value just make one up
|
||||||
*/
|
*/
|
||||||
export function findDefaultFromSpec(spec: { type: 'string' | 'color' | 'boolean' | 'array', default?: any }) {
|
export function findDefaultFromSpec(spec: { type: 'string' | 'color' | 'boolean' | 'array', default?: any }) {
|
||||||
if (spec.hasOwnProperty('default')) {
|
if (Object.prototype.hasOwnProperty.call(spec, 'default')) {
|
||||||
return spec.default;
|
return spec.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["geojson"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user