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