mirror of
https://github.com/maputnik/editor.git
synced 2025-12-06 06:10:00 +00:00
chore(deps-dev): Bump typescript from 5.8.3 to 5.9.2 (#1301)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.8.3 to 5.9.2. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/microsoft/TypeScript/releases">typescript's releases</a>.</em></p> <blockquote> <h2>TypeScript 5.9</h2> <p>Release notes pending.</p> <!-- raw HTML omitted --> <ul> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.0%22+is%3Aclosed+">fixed issues query for Typescript 5.9.0 (Beta)</a>.</li> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.1%22+is%3Aclosed+">fixed issues query for Typescript 5.9.1 (RC)</a>.</li> <li>[[No specific changes for TypeScript 5.9.2 (Stable)]]</li> </ul> <p>Downloads are available on:</p> <ul> <li><a href="https://www.npmjs.com/package/typescript">npm</a></li> </ul> <h2>TypeScript 5.9 RC</h2> <p>For release notes, check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-9-rc/">release announcement</a></p> <ul> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.0%22+is%3Aclosed+">fixed issues query for Typescript 5.9.0 (Beta)</a>.</li> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.1%22+is%3Aclosed+">fixed issues query for Typescript 5.9.1 (RC)</a>.</li> </ul> <p>Downloads are available on:</p> <ul> <li><a href="https://www.npmjs.com/package/typescript">npm</a></li> </ul> <h2>TypeScript 5.9 Beta</h2> <p>For release notes, check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-9-beta/">release announcement</a>.</p> <ul> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.0%22+is%3Aclosed+">fixed issues query for Typescript 5.9.0 (Beta)</a>.</li> </ul> <p>Downloads are available on:</p> <ul> <li><a href="https://www.npmjs.com/package/typescript">npm</a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="be86783155"><code>be86783</code></a> Give more specific errors for <code>verbatimModuleSyntax</code> (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62113">#62113</a>)</li> <li><a href="22ef57786f"><code>22ef577</code></a> LEGO: Pull request from lego/hb_5378966c-b857-470a-8675-daebef4a6da1_20250714...</li> <li><a href="d5a414cd1d"><code>d5a414c</code></a> Don't use <code>noErrorTruncation</code> when printing types with <code>maximumLength</code> set (#...</li> <li><a href="f14b5c8a2f"><code>f14b5c8</code></a> Remove unused and confusing dom.iterable.d.ts file (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62037">#62037</a>)</li> <li><a href="2778e84ed8"><code>2778e84</code></a> Restore AbortSignal.abort (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62086">#62086</a>)</li> <li><a href="65cb4bd2d5"><code>65cb4bd</code></a> LEGO: Pull request from lego/hb_5378966c-b857-470a-8675-daebef4a6da1_20250710...</li> <li><a href="9e20e032ef"><code>9e20e03</code></a> Clear out checker-level stacks on pop (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62016">#62016</a>)</li> <li><a href="87740bc7fe"><code>87740bc</code></a> Fix for Issue 61081 (<a href="https://redirect.github.com/microsoft/TypeScript/issues/61221">#61221</a>)</li> <li><a href="833a8d492c"><code>833a8d4</code></a> Fix Symbol completion priority and cursor positioning (<a href="https://redirect.github.com/microsoft/TypeScript/issues/61945">#61945</a>)</li> <li><a href="0018c9ff12"><code>0018c9f</code></a> LEGO: Pull request from lego/hb_5378966c-b857-470a-8675-daebef4a6da1_20250702...</li> <li>Additional commits viewable in <a href="https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) You can trigger a rebase of this PR by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> > **Note** > Automatic rebases have been disabled on this pull request as it has been open for over 30 days. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Harel M <harel.mazor@gmail.com>
This commit is contained in:
@@ -10,12 +10,12 @@
|
|||||||
* https://github.com/maplibre/maplibre-gl-js/blob/bc70bc559cea5c987fa1b79fd44766cef68bbe28/build/release-notes.js
|
* https://github.com/maplibre/maplibre-gl-js/blob/bc70bc559cea5c987fa1b79fd44766cef68bbe28/build/release-notes.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
|
|
||||||
const changelogPath = 'CHANGELOG.md';
|
const changelogPath = "CHANGELOG.md";
|
||||||
let changelog = fs.readFileSync(changelogPath, 'utf8');
|
let changelog = fs.readFileSync(changelogPath, "utf8");
|
||||||
changelog = changelog.replace('## main', `## ${process.argv[2]}`);
|
changelog = changelog.replace("## main", `## ${process.argv[2]}`);
|
||||||
changelog = changelog.replaceAll('- _...Add new stuff here..._\n', '');
|
changelog = changelog.replaceAll("- _...Add new stuff here..._\n", "");
|
||||||
changelog = `## main
|
changelog = `## main
|
||||||
|
|
||||||
### ✨ Features and improvements
|
### ✨ Features and improvements
|
||||||
@@ -26,4 +26,4 @@ changelog = `## main
|
|||||||
|
|
||||||
` + changelog;
|
` + changelog;
|
||||||
|
|
||||||
fs.writeFileSync(changelogPath, changelog, 'utf8');
|
fs.writeFileSync(changelogPath, changelog, "utf8");
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
// Copied from maplibre/maplibre-gl-js
|
// Copied from maplibre/maplibre-gl-js
|
||||||
// https://github.com/maplibre/maplibre-gl-js/blob/bc70bc559cea5c987fa1b79fd44766cef68bbe28/build/release-notes.js
|
// https://github.com/maplibre/maplibre-gl-js/blob/bc70bc559cea5c987fa1b79fd44766cef68bbe28/build/release-notes.js
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
|
|
||||||
const changelogPath = 'CHANGELOG.md';
|
const changelogPath = "CHANGELOG.md";
|
||||||
const changelog = fs.readFileSync(changelogPath, 'utf8');
|
const changelog = fs.readFileSync(changelogPath, "utf8");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Parse the raw changelog text and split it into individual releases.
|
Parse the raw changelog text and split it into individual releases.
|
||||||
@@ -25,8 +25,8 @@ let match;
|
|||||||
// eslint-disable-next-line no-cond-assign
|
// eslint-disable-next-line no-cond-assign
|
||||||
while (match = regex.exec(changelog)) {
|
while (match = regex.exec(changelog)) {
|
||||||
releaseNotes.push({
|
releaseNotes.push({
|
||||||
'version': match[1],
|
"version": match[1],
|
||||||
'changelog': match[2].trim(),
|
"changelog": match[2].trim(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +35,10 @@ const previous = releaseNotes[1];
|
|||||||
|
|
||||||
// Print the release notes template.
|
// Print the release notes template.
|
||||||
|
|
||||||
let header = 'Changes since previous version'
|
let header = "Changes since previous version";
|
||||||
if (previous) {
|
if (previous) {
|
||||||
header = `https://github.com/maplibre/maputnik
|
header = `https://github.com/maplibre/maputnik
|
||||||
[Changes](https://github.com/maplibre/maputnik/compare/v${previous.version}...v${latest.version}) since [Maputnik v${previous.version}](https://github.com/maplibre/maputnik/releases/tag/v${previous.version})`
|
[Changes](https://github.com/maplibre/maputnik/compare/v${previous.version}...v${latest.version}) since [Maputnik v${previous.version}](https://github.com/maplibre/maputnik/releases/tag/v${previous.version})`;
|
||||||
}
|
}
|
||||||
const templatedReleaseNotes = `${header}
|
const templatedReleaseNotes = `${header}
|
||||||
|
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ describe("layers", () => {
|
|||||||
|
|
||||||
it("should revert to a valid value when focus out", () => {
|
it("should revert to a valid value when focus out", () => {
|
||||||
when.click("layer-list-item:background:" + bgId);
|
when.click("layer-list-item:background:" + bgId);
|
||||||
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue('0');
|
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -575,10 +575,10 @@ describe("layers", () => {
|
|||||||
});
|
});
|
||||||
when.collapseGroupInLayerEditor();
|
when.collapseGroupInLayerEditor();
|
||||||
when.collapseGroupInLayerEditor(1);
|
when.collapseGroupInLayerEditor(1);
|
||||||
when.setValueToPropertyArray("spec-field:hillshade-illumination-direction", '1');
|
when.setValueToPropertyArray("spec-field:hillshade-illumination-direction", "1");
|
||||||
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", '2');
|
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "2");
|
||||||
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", '3');
|
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "3");
|
||||||
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", '4');
|
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "4");
|
||||||
|
|
||||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
layers: [
|
layers: [
|
||||||
@@ -600,9 +600,9 @@ describe("layers", () => {
|
|||||||
layer: "example",
|
layer: "example",
|
||||||
});
|
});
|
||||||
when.collapseGroupInLayerEditor();
|
when.collapseGroupInLayerEditor();
|
||||||
when.setValueToPropertyArray("spec-field:hillshade-highlight-color", 'blue');
|
when.setValueToPropertyArray("spec-field:hillshade-highlight-color", "blue");
|
||||||
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", '#00ff00');
|
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "#00ff00");
|
||||||
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", 'rgba(255, 255, 0, 1)');
|
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "rgba(255, 255, 0, 1)");
|
||||||
|
|
||||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
layers: [
|
layers: [
|
||||||
@@ -706,8 +706,8 @@ describe("layers", () => {
|
|||||||
sourceText.click();
|
sourceText.click();
|
||||||
sourceText.type("\"");
|
sourceText.type("\"");
|
||||||
|
|
||||||
const error = get.element('.CodeMirror-lint-marker-error');
|
const error = get.element(".CodeMirror-lint-marker-error");
|
||||||
error.should('exist');
|
error.should("exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe("map", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("search", () => {
|
describe("search", () => {
|
||||||
it('should exist', () => {
|
it("should exist", () => {
|
||||||
then(get.searchControl()).shouldBeVisible();
|
then(get.searchControl()).shouldBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -33,7 +33,7 @@ describe("map", () => {
|
|||||||
describe("popup", () => {
|
describe("popup", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
when.setStyle("rectangles");
|
when.setStyle("rectangles");
|
||||||
})
|
});
|
||||||
it("should open on feature click", () => {
|
it("should open on feature click", () => {
|
||||||
when.clickCenter("maplibre:map");
|
when.clickCenter("maplibre:map");
|
||||||
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="cypress-real-events" />
|
/// <reference types="cypress-real-events" />
|
||||||
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
import { CypressHelper } from "@shellygo/cypress-test-utils";
|
||||||
import 'cypress-real-events/support';
|
import "cypress-real-events/support";
|
||||||
|
|
||||||
export default class MaputnikCypressHelper {
|
export default class MaputnikCypressHelper {
|
||||||
private helper = new CypressHelper({ defaultDataAttribute: "data-wd-key" });
|
private helper = new CypressHelper({ defaultDataAttribute: "data-wd-key" });
|
||||||
@@ -17,7 +17,7 @@ export default class MaputnikCypressHelper {
|
|||||||
dragAndDropWithWait: (element: string, targetElement: string) => {
|
dragAndDropWithWait: (element: string, targetElement: string) => {
|
||||||
this.helper.get.elementByTestId(element).realMouseDown({ button: "left", position: "center" });
|
this.helper.get.elementByTestId(element).realMouseDown({ button: "left", position: "center" });
|
||||||
this.helper.get.elementByTestId(element).realMouseMove(0, 10, { position: "center" });
|
this.helper.get.elementByTestId(element).realMouseMove(0, 10, { position: "center" });
|
||||||
this.helper.get.elementByTestId(targetElement).realMouseMove(0, 0, { position: "center" })
|
this.helper.get.elementByTestId(targetElement).realMouseMove(0, 0, { position: "center" });
|
||||||
this.helper.when.wait(1);
|
this.helper.when.wait(1);
|
||||||
this.helper.get.elementByTestId(targetElement).realMouseUp();
|
this.helper.get.elementByTestId(targetElement).realMouseUp();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -119,21 +119,21 @@ export class MaputnikDriver {
|
|||||||
) => {
|
) => {
|
||||||
const url = new URL(baseUrl);
|
const url = new URL(baseUrl);
|
||||||
switch (styleProperties) {
|
switch (styleProperties) {
|
||||||
case "geojson":
|
case "geojson":
|
||||||
url.searchParams.set("style", baseUrl + "geojson-style.json");
|
url.searchParams.set("style", baseUrl + "geojson-style.json");
|
||||||
break;
|
break;
|
||||||
case "raster":
|
case "raster":
|
||||||
url.searchParams.set("style", baseUrl + "raster-style.json");
|
url.searchParams.set("style", baseUrl + "raster-style.json");
|
||||||
break;
|
break;
|
||||||
case "both":
|
case "both":
|
||||||
url.searchParams.set("style", baseUrl + "geojson-raster-style.json");
|
url.searchParams.set("style", baseUrl + "geojson-raster-style.json");
|
||||||
break;
|
break;
|
||||||
case "layer":
|
case "layer":
|
||||||
url.searchParams.set("style", baseUrl + "example-layer-style.json");
|
url.searchParams.set("style", baseUrl + "example-layer-style.json");
|
||||||
break;
|
break;
|
||||||
case "rectangles":
|
case "rectangles":
|
||||||
url.searchParams.set("style", baseUrl + "rectangles-style.json");
|
url.searchParams.set("style", baseUrl + "rectangles-style.json");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zoom) {
|
if (zoom) {
|
||||||
@@ -144,7 +144,7 @@ export class MaputnikDriver {
|
|||||||
this.helper.when.acceptConfirm();
|
this.helper.when.acceptConfirm();
|
||||||
}
|
}
|
||||||
// when methods should not include assertions
|
// when methods should not include assertions
|
||||||
const toolbarLink = this.helper.get.elementByTestId("toolbar:link")
|
const toolbarLink = this.helper.get.elementByTestId("toolbar:link");
|
||||||
toolbarLink.scrollIntoView();
|
toolbarLink.scrollIntoView();
|
||||||
toolbarLink.should("be.visible");
|
toolbarLink.should("be.visible");
|
||||||
},
|
},
|
||||||
@@ -215,6 +215,6 @@ export class MaputnikDriver {
|
|||||||
skipTargetLayerEditor: () =>
|
skipTargetLayerEditor: () =>
|
||||||
this.helper.get.elementByTestId("skip-target-layer-editor"),
|
this.helper.get.elementByTestId("skip-target-layer-editor"),
|
||||||
canvas: () => this.helper.get.element("canvas"),
|
canvas: () => this.helper.get.element("canvas"),
|
||||||
searchControl: () => this.helper.get.element('.maplibregl-ctrl-geocoder')
|
searchControl: () => this.helper.get.element(".maplibregl-ctrl-geocoder")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,10 +253,10 @@ describe("modals", () => {
|
|||||||
|
|
||||||
it("inlcude API key when change renderer", () => {
|
it("inlcude API key when change renderer", () => {
|
||||||
|
|
||||||
when.click("modal:settings.close-modal")
|
when.click("modal:settings.close-modal");
|
||||||
when.click("nav:open");
|
when.click("nav:open");
|
||||||
|
|
||||||
get.elementByAttribute('aria-label', "MapTiler Basic").should('exist').click();
|
get.elementByAttribute("aria-label", "MapTiler Basic").should("exist").click();
|
||||||
when.wait(1000);
|
when.wait(1000);
|
||||||
when.click("nav:settings");
|
when.click("nav:settings");
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ describe("modals", () => {
|
|||||||
win.localStorage.setItem(key, chunk);
|
win.localStorage.setItem(key, chunk);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// Verify it's a quota error
|
// Verify it's a quota error
|
||||||
if (e.name === 'QuotaExceededError') {
|
if (e.name === "QuotaExceededError") {
|
||||||
if (chunkSize <= 1) return;
|
if (chunkSize <= 1) return;
|
||||||
else {
|
else {
|
||||||
chunkSize /= 2;
|
chunkSize /= 2;
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
// Import commands.js using ES2015 syntax:
|
||||||
import './commands'
|
import "./commands";
|
||||||
|
|
||||||
import { mount } from 'cypress/react'
|
import { mount } from "cypress/react";
|
||||||
|
|
||||||
// Augment the Cypress namespace to include type definitions for
|
// Augment the Cypress namespace to include type definitions for
|
||||||
// your custom command.
|
// your custom command.
|
||||||
@@ -31,7 +31,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add('mount', mount)
|
Cypress.Commands.add("mount", mount);
|
||||||
|
|
||||||
// Example use:
|
// Example use:
|
||||||
// cy.mount(<MyComponent />)
|
// cy.mount(<MyComponent />)
|
||||||
|
|||||||
@@ -1,56 +1,65 @@
|
|||||||
import eslint from '@eslint/js';
|
import eslint from "@eslint/js";
|
||||||
import tseslint from 'typescript-eslint';
|
import {defineConfig} from "eslint/config";
|
||||||
import reactPlugin from 'eslint-plugin-react';
|
import stylisticTs from "@stylistic/eslint-plugin";
|
||||||
import reactHooksPlugin from 'eslint-plugin-react-hooks';
|
import tseslint from "typescript-eslint";
|
||||||
import reactRefreshPlugin from 'eslint-plugin-react-refresh';
|
import reactPlugin from "eslint-plugin-react";
|
||||||
|
import reactHooksPlugin from "eslint-plugin-react-hooks";
|
||||||
|
import reactRefreshPlugin from "eslint-plugin-react-refresh";
|
||||||
|
|
||||||
export default tseslint.config({
|
export default defineConfig({
|
||||||
extends: [
|
extends: [
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended,
|
||||||
],
|
],
|
||||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
files: ["**/*.{js,jsx,ts,tsx}"],
|
||||||
ignores: [
|
ignores: [
|
||||||
"dist/**/*",
|
"dist/**/*",
|
||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2024,
|
ecmaVersion: 2024,
|
||||||
sourceType: 'module',
|
sourceType: "module",
|
||||||
globals: {
|
globals: {
|
||||||
global: 'readonly'
|
global: "readonly"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
react: { version: '18.2' }
|
react: { version: "18.2" }
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'react': reactPlugin,
|
"react": reactPlugin,
|
||||||
'react-hooks': reactHooksPlugin,
|
"react-hooks": reactHooksPlugin,
|
||||||
'react-refresh': reactRefreshPlugin
|
"react-refresh": reactRefreshPlugin,
|
||||||
|
"@stylistic": stylisticTs
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'react-refresh/only-export-components': [
|
"react-refresh/only-export-components": [
|
||||||
'warn',
|
"warn",
|
||||||
{ allowConstantExport: true }
|
{ allowConstantExport: true }
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
'@typescript-eslint/no-unused-vars': [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
'warn',
|
"warn",
|
||||||
{
|
{
|
||||||
varsIgnorePattern: '^_',
|
varsIgnorePattern: "^_",
|
||||||
caughtErrors: 'all',
|
caughtErrors: "all",
|
||||||
caughtErrorsIgnorePattern: '^_',
|
caughtErrorsIgnorePattern: "^_",
|
||||||
argsIgnorePattern: '^_'
|
argsIgnorePattern: "^_"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'no-unused-vars': 'off',
|
"no-unused-vars": "off",
|
||||||
'react/prop-types': 'off',
|
"react/prop-types": "off",
|
||||||
'no-undef': 'off',
|
"no-undef": "off",
|
||||||
'indent': ['error', 2],
|
"indent": "off",
|
||||||
'no-var': 'error',
|
"@stylistic/indent": ["error", 2],
|
||||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
|
"semi": "off",
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
"@stylistic/semi": ["error", "always"],
|
||||||
'@typescript-eslint/no-empty-object-type': 'off',
|
"quotes": "off",
|
||||||
|
"@stylistic/quotes": ["error", "double", { avoidEscape: true }],
|
||||||
|
"no-var": "error",
|
||||||
|
"@typescript-eslint/no-non-null-asserted-optional-chain": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-empty-object-type": "off",
|
||||||
|
"@typescript-eslint/consistent-type-imports": ["error", { "fixStyle": "inline-type-imports" }],
|
||||||
|
|
||||||
},
|
},
|
||||||
linterOptions: {
|
linterOptions: {
|
||||||
@@ -58,4 +67,4 @@ export default tseslint.config({
|
|||||||
noInlineConfig: false
|
noInlineConfig: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
output: 'src/locales/$LOCALE/$NAMESPACE.json',
|
output: "src/locales/$LOCALE/$NAMESPACE.json",
|
||||||
locales: [ 'de', 'fr', 'he', 'it','ja', 'zh' ],
|
locales: [ "de", "fr", "he", "it","ja", "zh" ],
|
||||||
|
|
||||||
// Because some keys are dynamically generated, i18next-parser can't detect them.
|
// Because some keys are dynamically generated, i18next-parser can't detect them.
|
||||||
// We add these keys manually, so we don't want to remove them.
|
// We add these keys manually, so we don't want to remove them.
|
||||||
@@ -12,6 +12,6 @@ export default {
|
|||||||
|
|
||||||
defaultValue: (_locale, _ns, _key) => {
|
defaultValue: (_locale, _ns, _key) => {
|
||||||
// The default value is a string that indicates that the string is not translated.
|
// The default value is a string that indicates that the string is not translated.
|
||||||
return '__STRING_NOT_TRANSLATED__';
|
return "__STRING_NOT_TRANSLATED__";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -67,6 +67,7 @@
|
|||||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||||
"@rollup/plugin-replace": "^6.0.2",
|
"@rollup/plugin-replace": "^6.0.2",
|
||||||
"@shellygo/cypress-test-utils": "^6.0.1",
|
"@shellygo/cypress-test-utils": "^6.0.1",
|
||||||
|
"@stylistic/eslint-plugin": "^5.3.1",
|
||||||
"@types/codemirror": "^5.60.16",
|
"@types/codemirror": "^5.60.16",
|
||||||
"@types/color": "^4.2.0",
|
"@types/color": "^4.2.0",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
"stylelint": "^16.24.0",
|
"stylelint": "^16.24.0",
|
||||||
"stylelint-config-recommended-scss": "^16.0.1",
|
"stylelint-config-recommended-scss": "^16.0.1",
|
||||||
"stylelint-scss": "^6.12.1",
|
"stylelint-scss": "^6.12.1",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.9.2",
|
||||||
"typescript-eslint": "^8.43.0",
|
"typescript-eslint": "^8.43.0",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vite": "^7.1.5",
|
"vite": "^7.1.5",
|
||||||
@@ -2805,6 +2806,53 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@stylistic/eslint-plugin": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-Ykums1VYonM0TgkD0VteVq9mrlO2FhF48MDJnPyv3MktIB2ydtuhlO0AfWm7xnW1kyf5bjOqA6xc7JjviuVTxg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
|
"@typescript-eslint/types": "^8.41.0",
|
||||||
|
"eslint-visitor-keys": "^4.2.1",
|
||||||
|
"espree": "^10.4.0",
|
||||||
|
"estraverse": "^5.3.0",
|
||||||
|
"picomatch": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -12708,9 +12756,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@
|
|||||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||||
"@rollup/plugin-replace": "^6.0.2",
|
"@rollup/plugin-replace": "^6.0.2",
|
||||||
"@shellygo/cypress-test-utils": "^6.0.1",
|
"@shellygo/cypress-test-utils": "^6.0.1",
|
||||||
|
"@stylistic/eslint-plugin": "^5.3.1",
|
||||||
"@types/codemirror": "^5.60.16",
|
"@types/codemirror": "^5.60.16",
|
||||||
"@types/color": "^4.2.0",
|
"@types/color": "^4.2.0",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
@@ -139,7 +140,7 @@
|
|||||||
"stylelint": "^16.24.0",
|
"stylelint": "^16.24.0",
|
||||||
"stylelint-config-recommended-scss": "^16.0.1",
|
"stylelint-config-recommended-scss": "^16.0.1",
|
||||||
"stylelint-scss": "^6.12.1",
|
"stylelint-scss": "^6.12.1",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.9.2",
|
||||||
"typescript-eslint": "^8.43.0",
|
"typescript-eslint": "^8.43.0",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vite": "^7.1.5",
|
"vite": "^7.1.5",
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import cloneDeep from 'lodash.clonedeep'
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import clamp from 'lodash.clamp'
|
import clamp from "lodash.clamp";
|
||||||
import buffer from 'buffer'
|
import buffer from "buffer";
|
||||||
import get from 'lodash.get'
|
import get from "lodash.get";
|
||||||
import {unset} from 'lodash'
|
import {unset} from "lodash";
|
||||||
import {arrayMoveMutable} from 'array-move'
|
import {arrayMoveMutable} from "array-move";
|
||||||
import hash from "string-hash";
|
import hash from "string-hash";
|
||||||
import { PMTiles } from "pmtiles";
|
import { PMTiles } from "pmtiles";
|
||||||
import {Map, LayerSpecification, StyleSpecification, ValidationError, SourceSpecification} from 'maplibre-gl'
|
import {type Map, type LayerSpecification, type StyleSpecification, type ValidationError, type SourceSpecification} from "maplibre-gl";
|
||||||
import {validateStyleMin} from '@maplibre/maplibre-gl-style-spec'
|
import {validateStyleMin} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
|
|
||||||
import MapMaplibreGl from './MapMaplibreGl'
|
import MapMaplibreGl from "./MapMaplibreGl";
|
||||||
import MapOpenLayers from './MapOpenLayers'
|
import MapOpenLayers from "./MapOpenLayers";
|
||||||
import LayerList from './LayerList'
|
import LayerList from "./LayerList";
|
||||||
import LayerEditor from './LayerEditor'
|
import LayerEditor from "./LayerEditor";
|
||||||
import AppToolbar, { MapState } from './AppToolbar'
|
import AppToolbar, { type MapState } from "./AppToolbar";
|
||||||
import AppLayout from './AppLayout'
|
import AppLayout from "./AppLayout";
|
||||||
import MessagePanel from './AppMessagePanel'
|
import MessagePanel from "./AppMessagePanel";
|
||||||
|
|
||||||
import ModalSettings from './modals/ModalSettings'
|
import ModalSettings from "./modals/ModalSettings";
|
||||||
import ModalExport from './modals/ModalExport'
|
import ModalExport from "./modals/ModalExport";
|
||||||
import ModalSources from './modals/ModalSources'
|
import ModalSources from "./modals/ModalSources";
|
||||||
import ModalOpen from './modals/ModalOpen'
|
import ModalOpen from "./modals/ModalOpen";
|
||||||
import ModalShortcuts from './modals/ModalShortcuts'
|
import ModalShortcuts from "./modals/ModalShortcuts";
|
||||||
import ModalDebug from './modals/ModalDebug'
|
import ModalDebug from "./modals/ModalDebug";
|
||||||
|
|
||||||
import {downloadGlyphsMetadata, downloadSpriteMetadata} from '../libs/metadata'
|
import {downloadGlyphsMetadata, downloadSpriteMetadata} from "../libs/metadata";
|
||||||
import style from '../libs/style'
|
import style from "../libs/style";
|
||||||
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
import { undoMessages, redoMessages } from "../libs/diffmessage";
|
||||||
import { createStyleStore, type IStyleStore } from '../libs/store/style-store-factory'
|
import { createStyleStore, type IStyleStore } from "../libs/store/style-store-factory";
|
||||||
import { RevisionStore } from '../libs/revisions'
|
import { RevisionStore } from "../libs/revisions";
|
||||||
import LayerWatcher from '../libs/layerwatcher'
|
import LayerWatcher from "../libs/layerwatcher";
|
||||||
import tokens from '../config/tokens.json'
|
import tokens from "../config/tokens.json";
|
||||||
import isEqual from 'lodash.isequal'
|
import isEqual from "lodash.isequal";
|
||||||
import { MapOptions } from 'maplibre-gl';
|
import { type MapOptions } from "maplibre-gl";
|
||||||
import { OnStyleChangedOpts, StyleSpecificationWithId } from '../libs/definitions'
|
import { type OnStyleChangedOpts, type StyleSpecificationWithId } from "../libs/definitions";
|
||||||
|
|
||||||
// Buffer must be defined globally for @maplibre/maplibre-gl-style-spec validate() function to succeed.
|
// Buffer must be defined globally for @maplibre/maplibre-gl-style-spec validate() function to succeed.
|
||||||
window.Buffer = buffer.Buffer;
|
window.Buffer = buffer.Buffer;
|
||||||
@@ -46,21 +46,21 @@ function setFetchAccessToken(url: string, mapStyle: StyleSpecification) {
|
|||||||
const matchesThunderforest = url.match(/\.thunderforest\.com/);
|
const matchesThunderforest = url.match(/\.thunderforest\.com/);
|
||||||
const matchesLocationIQ = url.match(/\.locationiq\.com/);
|
const matchesLocationIQ = url.match(/\.locationiq\.com/);
|
||||||
if (matchesTilehosting || matchesMaptiler) {
|
if (matchesTilehosting || matchesMaptiler) {
|
||||||
const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true})
|
const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true});
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
return url.replace('{key}', accessToken)
|
return url.replace("{key}", accessToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (matchesThunderforest) {
|
else if (matchesThunderforest) {
|
||||||
const accessToken = style.getAccessToken("thunderforest", mapStyle, {allowFallback: true})
|
const accessToken = style.getAccessToken("thunderforest", mapStyle, {allowFallback: true});
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
return url.replace('{key}', accessToken)
|
return url.replace("{key}", accessToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (matchesLocationIQ) {
|
else if (matchesLocationIQ) {
|
||||||
const accessToken = style.getAccessToken("locationiq", mapStyle, {allowFallback: true})
|
const accessToken = style.getAccessToken("locationiq", mapStyle, {allowFallback: true});
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
return url.replace('{key}', accessToken)
|
return url.replace("{key}", accessToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -78,7 +78,7 @@ function updateRootSpec(spec: any, fieldName: string, newValues: any) {
|
|||||||
values: newValues
|
values: newValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type MappedErrors = {
|
type MappedErrors = {
|
||||||
@@ -91,7 +91,7 @@ type MappedErrors = {
|
|||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
errors: MappedErrors[],
|
errors: MappedErrors[],
|
||||||
@@ -128,7 +128,7 @@ type AppState = {
|
|||||||
debug: boolean
|
debug: boolean
|
||||||
}
|
}
|
||||||
fileHandle: FileSystemFileHandle | null
|
fileHandle: FileSystemFileHandle | null
|
||||||
}
|
};
|
||||||
|
|
||||||
export default class App extends React.Component<any, AppState> {
|
export default class App extends React.Component<any, AppState> {
|
||||||
revisionStore: RevisionStore;
|
revisionStore: RevisionStore;
|
||||||
@@ -136,7 +136,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
layerWatcher: LayerWatcher;
|
layerWatcher: LayerWatcher;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props);
|
||||||
|
|
||||||
this.revisionStore = new RevisionStore();
|
this.revisionStore = new RevisionStore();
|
||||||
this.configureKeyboardShortcuts();
|
this.configureKeyboardShortcuts();
|
||||||
@@ -174,11 +174,11 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
debugToolbox: false,
|
debugToolbox: false,
|
||||||
},
|
},
|
||||||
fileHandle: null,
|
fileHandle: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
this.layerWatcher = new LayerWatcher({
|
this.layerWatcher = new LayerWatcher({
|
||||||
onVectorLayersChange: v => this.setState({ vectorLayers: v })
|
onVectorLayersChange: v => this.setState({ vectorLayers: v })
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
configureKeyboardShortcuts = () => {
|
configureKeyboardShortcuts = () => {
|
||||||
@@ -233,7 +233,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
this.toggleModal("debug");
|
this.toggleModal("debug");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
document.body.addEventListener("keyup", (e) => {
|
document.body.addEventListener("keyup", (e) => {
|
||||||
if(e.key === "Escape") {
|
if(e.key === "Escape") {
|
||||||
@@ -242,19 +242,19 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
|
else if(this.state.isOpen.shortcuts || document.activeElement === document.body) {
|
||||||
const shortcut = shortcuts.find((shortcut) => {
|
const shortcut = shortcuts.find((shortcut) => {
|
||||||
return (shortcut.key === e.key)
|
return (shortcut.key === e.key);
|
||||||
})
|
});
|
||||||
|
|
||||||
if(shortcut) {
|
if(shortcut) {
|
||||||
this.setModal("shortcuts", false);
|
this.setModal("shortcuts", false);
|
||||||
shortcut.handler();
|
shortcut.handler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
handleKeyPress = (e: KeyboardEvent) => {
|
handleKeyPress = (e: KeyboardEvent) => {
|
||||||
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
|
if(navigator.platform.toUpperCase().indexOf("MAC") >= 0) {
|
||||||
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
|
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onRedo();
|
this.onRedo();
|
||||||
@@ -274,7 +274,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
this.onRedo();
|
this.onRedo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
this.styleStore = await createStyleStore((mapStyle, opts) => this.onStyleChanged(mapStyle, opts));
|
this.styleStore = await createStyleStore((mapStyle, opts) => this.onStyleChanged(mapStyle, opts));
|
||||||
@@ -286,33 +286,33 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveStyle(snapshotStyle: StyleSpecificationWithId) {
|
saveStyle(snapshotStyle: StyleSpecificationWithId) {
|
||||||
this.styleStore?.save(snapshotStyle)
|
this.styleStore?.save(snapshotStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFonts(urlTemplate: string) {
|
updateFonts(urlTemplate: string) {
|
||||||
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;
|
||||||
|
|
||||||
const 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)});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIcons(baseUrl: string) {
|
updateIcons(baseUrl: string) {
|
||||||
downloadSpriteMetadata(baseUrl, icons => {
|
downloadSpriteMetadata(baseUrl, icons => {
|
||||||
this.setState({ spec: updateRootSpec(this.state.spec, 'sprite', icons)})
|
this.setState({ spec: updateRootSpec(this.state.spec, "sprite", icons)});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeMetadataProperty = (property: string, value: any) => {
|
onChangeMetadataProperty = (property: string, value: any) => {
|
||||||
// If we're changing renderer reset the map state.
|
// If we're changing renderer reset the map state.
|
||||||
if (
|
if (
|
||||||
property === 'maputnik:renderer' &&
|
property === "maputnik:renderer" &&
|
||||||
value !== get(this.state.mapStyle, ['metadata', 'maputnik:renderer'], 'mlgljs')
|
value !== get(this.state.mapStyle, ["metadata", "maputnik:renderer"], "mlgljs")
|
||||||
) {
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
mapState: 'map'
|
mapState: "map"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,10 +322,10 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
...(this.state.mapStyle as any).metadata,
|
...(this.state.mapStyle as any).metadata,
|
||||||
[property]: value
|
[property]: value
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
this.onStyleChanged(changedStyle)
|
this.onStyleChanged(changedStyle);
|
||||||
}
|
};
|
||||||
|
|
||||||
onStyleChanged = (newStyle: StyleSpecificationWithId, opts: OnStyleChangedOpts={}): void => {
|
onStyleChanged = (newStyle: StyleSpecificationWithId, opts: OnStyleChangedOpts={}): void => {
|
||||||
opts = {
|
opts = {
|
||||||
@@ -338,16 +338,16 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
// For the style object, find the urls that has "{key}" and insert the correct API keys
|
// For the style object, find the urls that has "{key}" and insert the correct API keys
|
||||||
// Without this, going from e.g. MapTiler to OpenLayers and back will lose the maptlier key.
|
// Without this, going from e.g. MapTiler to OpenLayers and back will lose the maptlier key.
|
||||||
|
|
||||||
if (newStyle.glyphs && typeof newStyle.glyphs === 'string') {
|
if (newStyle.glyphs && typeof newStyle.glyphs === "string") {
|
||||||
newStyle.glyphs = setFetchAccessToken(newStyle.glyphs, newStyle);
|
newStyle.glyphs = setFetchAccessToken(newStyle.glyphs, newStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newStyle.sprite && typeof newStyle.sprite === 'string') {
|
if (newStyle.sprite && typeof newStyle.sprite === "string") {
|
||||||
newStyle.sprite = setFetchAccessToken(newStyle.sprite, newStyle);
|
newStyle.sprite = setFetchAccessToken(newStyle.sprite, newStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [_sourceId, source] of Object.entries(newStyle.sources)) {
|
for (const [_sourceId, source] of Object.entries(newStyle.sources)) {
|
||||||
if (source && 'url' in source && typeof source.url === 'string') {
|
if (source && "url" in source && typeof source.url === "string") {
|
||||||
source.url = setFetchAccessToken(source.url, newStyle);
|
source.url = setFetchAccessToken(source.url, newStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,7 +389,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
message,
|
message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case: Invalid source
|
// Special case: Invalid source
|
||||||
@@ -406,7 +406,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
message,
|
message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
const layerMatch = error.message.match(/layers\[(\d+)\]\.(?:(\S+)\.)?(\S+): (.*)/);
|
||||||
@@ -423,7 +423,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
message
|
message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return {
|
return {
|
||||||
@@ -453,10 +453,10 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
|
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
|
||||||
this.updateFonts(newStyle.glyphs as string)
|
this.updateFonts(newStyle.glyphs as string);
|
||||||
}
|
}
|
||||||
if(newStyle.sprite !== this.state.mapStyle.sprite) {
|
if(newStyle.sprite !== this.state.mapStyle.sprite) {
|
||||||
this.updateIcons(newStyle.sprite as string)
|
this.updateIcons(newStyle.sprite as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.addRevision) {
|
if (opts.addRevision) {
|
||||||
@@ -473,28 +473,28 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}, () => {
|
}, () => {
|
||||||
this.fetchSources();
|
this.fetchSources();
|
||||||
this.setStateInUrl();
|
this.setStateInUrl();
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
onUndo = () => {
|
onUndo = () => {
|
||||||
const activeStyle = this.revisionStore.undo()
|
const activeStyle = this.revisionStore.undo();
|
||||||
|
|
||||||
const messages = undoMessages(this.state.mapStyle, activeStyle)
|
const messages = undoMessages(this.state.mapStyle, activeStyle);
|
||||||
this.onStyleChanged(activeStyle, {addRevision: false});
|
this.onStyleChanged(activeStyle, {addRevision: false});
|
||||||
this.setState({
|
this.setState({
|
||||||
infos: messages,
|
infos: messages,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onRedo = () => {
|
onRedo = () => {
|
||||||
const activeStyle = this.revisionStore.redo()
|
const activeStyle = this.revisionStore.redo();
|
||||||
const messages = redoMessages(this.state.mapStyle, activeStyle)
|
const messages = redoMessages(this.state.mapStyle, activeStyle);
|
||||||
this.onStyleChanged(activeStyle, {addRevision: false});
|
this.onStyleChanged(activeStyle, {addRevision: false});
|
||||||
this.setState({
|
this.setState({
|
||||||
infos: messages,
|
infos: messages,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onMoveLayer = (move: {oldIndex: number; newIndex: number}) => {
|
onMoveLayer = (move: {oldIndex: number; newIndex: number}) => {
|
||||||
let { oldIndex, newIndex } = move;
|
let { oldIndex, newIndex } = move;
|
||||||
@@ -512,97 +512,97 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
layers = layers.slice(0);
|
layers = layers.slice(0);
|
||||||
arrayMoveMutable(layers, oldIndex, newIndex);
|
arrayMoveMutable(layers, oldIndex, newIndex);
|
||||||
this.onLayersChange(layers);
|
this.onLayersChange(layers);
|
||||||
}
|
};
|
||||||
|
|
||||||
onLayersChange = (changedLayers: LayerSpecification[]) => {
|
onLayersChange = (changedLayers: LayerSpecification[]) => {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.state.mapStyle,
|
...this.state.mapStyle,
|
||||||
layers: changedLayers
|
layers: changedLayers
|
||||||
}
|
};
|
||||||
this.onStyleChanged(changedStyle)
|
this.onStyleChanged(changedStyle);
|
||||||
}
|
};
|
||||||
|
|
||||||
onLayerDestroy = (index: number) => {
|
onLayerDestroy = (index: number) => {
|
||||||
const 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) => {
|
||||||
const 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]);
|
||||||
clonedLayer.id = clonedLayer.id + "-copy"
|
clonedLayer.id = clonedLayer.id + "-copy";
|
||||||
changedLayers.splice(index, 0, clonedLayer)
|
changedLayers.splice(index, 0, clonedLayer);
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers);
|
||||||
}
|
};
|
||||||
|
|
||||||
onLayerVisibilityToggle = (index: number) => {
|
onLayerVisibilityToggle = (index: number) => {
|
||||||
const 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] };
|
||||||
const changedLayout = 'layout' in layer ? {...layer.layout} : {}
|
const changedLayout = "layout" in layer ? {...layer.layout} : {};
|
||||||
changedLayout.visibility = changedLayout.visibility === 'none' ? 'visible' : 'none'
|
changedLayout.visibility = changedLayout.visibility === "none" ? "visible" : "none";
|
||||||
|
|
||||||
layer.layout = changedLayout
|
layer.layout = changedLayout;
|
||||||
changedLayers[index] = layer
|
changedLayers[index] = layer;
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
onLayerIdChange = (index: number, _oldId: string, newId: string) => {
|
onLayerIdChange = (index: number, _oldId: string, newId: string) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0);
|
||||||
changedLayers[index] = {
|
changedLayers[index] = {
|
||||||
...changedLayers[index],
|
...changedLayers[index],
|
||||||
id: newId
|
id: newId
|
||||||
}
|
};
|
||||||
|
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers);
|
||||||
}
|
};
|
||||||
|
|
||||||
onLayerChanged = (index: number, layer: LayerSpecification) => {
|
onLayerChanged = (index: number, layer: LayerSpecification) => {
|
||||||
const changedLayers = this.state.mapStyle.layers.slice(0)
|
const changedLayers = this.state.mapStyle.layers.slice(0);
|
||||||
changedLayers[index] = layer
|
changedLayers[index] = layer;
|
||||||
|
|
||||||
this.onLayersChange(changedLayers)
|
this.onLayersChange(changedLayers);
|
||||||
}
|
};
|
||||||
|
|
||||||
setMapState = (newState: MapState) => {
|
setMapState = (newState: MapState) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
mapState: newState
|
mapState: newState
|
||||||
}, this.setStateInUrl);
|
}, this.setStateInUrl);
|
||||||
}
|
};
|
||||||
|
|
||||||
setDefaultValues = (styleObj: StyleSpecificationWithId) => {
|
setDefaultValues = (styleObj: StyleSpecificationWithId) => {
|
||||||
const metadata: {[key: string]: string} = styleObj.metadata || {} as any
|
const metadata: {[key: string]: string} = styleObj.metadata || {} as any;
|
||||||
if(metadata['maputnik:renderer'] === undefined) {
|
if(metadata["maputnik:renderer"] === undefined) {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...styleObj,
|
...styleObj,
|
||||||
metadata: {
|
metadata: {
|
||||||
...styleObj.metadata as any,
|
...styleObj.metadata as any,
|
||||||
'maputnik:renderer': 'mlgljs'
|
"maputnik:renderer": "mlgljs"
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
return changedStyle
|
return changedStyle;
|
||||||
} else {
|
} else {
|
||||||
return styleObj
|
return styleObj;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
openStyle = (styleObj: StyleSpecificationWithId, fileHandle: FileSystemFileHandle | null) => {
|
openStyle = (styleObj: StyleSpecificationWithId, fileHandle: FileSystemFileHandle | null) => {
|
||||||
this.setState({fileHandle: fileHandle});
|
this.setState({fileHandle: fileHandle});
|
||||||
styleObj = this.setDefaultValues(styleObj)
|
styleObj = this.setDefaultValues(styleObj);
|
||||||
this.onStyleChanged(styleObj)
|
this.onStyleChanged(styleObj);
|
||||||
}
|
};
|
||||||
|
|
||||||
async fetchSources() {
|
async fetchSources() {
|
||||||
const sourceList: {[key: string]: SourceSpecification & {layers: string[]}} = {};
|
const sourceList: {[key: string]: SourceSpecification & {layers: string[]}} = {};
|
||||||
for(const key of Object.keys(this.state.mapStyle.sources)) {
|
for(const key of Object.keys(this.state.mapStyle.sources)) {
|
||||||
const source = this.state.mapStyle.sources[key];
|
const source = this.state.mapStyle.sources[key];
|
||||||
if(source.type !== "vector" || !('url' in source)) {
|
if(source.type !== "vector" || !("url" in source)) {
|
||||||
sourceList[key] = this.state.sources[key] || {...this.state.mapStyle.sources[key]};
|
sourceList[key] = this.state.sources[key] || {...this.state.mapStyle.sources[key]};
|
||||||
if (sourceList[key].layers === undefined) {
|
if (sourceList[key].layers === undefined) {
|
||||||
sourceList[key].layers = [];
|
sourceList[key].layers = [];
|
||||||
@@ -616,7 +616,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
let url = source.url;
|
let url = source.url;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
url = setFetchAccessToken(url!, this.state.mapStyle)
|
url = setFetchAccessToken(url!, this.state.mapStyle);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.warn("Failed to setFetchAccessToken: ", err);
|
console.warn("Failed to setFetchAccessToken: ", err);
|
||||||
}
|
}
|
||||||
@@ -627,7 +627,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(const layer of json.vector_layers) {
|
for(const layer of json.vector_layers) {
|
||||||
sourceList[key].layers.push(layer.id)
|
sourceList[key].layers.push(layer.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -636,7 +636,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
const json = await (new PMTiles(url!.substring(10))).getTileJson("");
|
const json = await (new PMTiles(url!.substring(10))).getTileJson("");
|
||||||
setVectorLayers(json);
|
setVectorLayers(json);
|
||||||
} else {
|
} else {
|
||||||
const response = await fetch(url!, { mode: 'cors' });
|
const response = await fetch(url!, { mode: "cors" });
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
setVectorLayers(json);
|
setVectorLayers(json);
|
||||||
}
|
}
|
||||||
@@ -650,13 +650,13 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
console.debug("Setting sources", sourceList);
|
console.debug("Setting sources", sourceList);
|
||||||
this.setState({
|
this.setState({
|
||||||
sources: sourceList
|
sources: sourceList
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getRenderer () {
|
_getRenderer () {
|
||||||
const metadata: {[key:string]: string} = this.state.mapStyle.metadata || {} as any;
|
const metadata: {[key:string]: string} = this.state.mapStyle.metadata || {} as any;
|
||||||
return metadata['maputnik:renderer'] || 'mlgljs';
|
return metadata["maputnik:renderer"] || "mlgljs";
|
||||||
}
|
}
|
||||||
|
|
||||||
onMapChange = (mapView: {
|
onMapChange = (mapView: {
|
||||||
@@ -669,7 +669,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
mapView,
|
mapView,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
mapRenderer() {
|
mapRenderer() {
|
||||||
const {mapStyle, dirtyMapStyle} = this.state;
|
const {mapStyle, dirtyMapStyle} = this.state;
|
||||||
@@ -682,23 +682,23 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDataChange: (e: {map: Map}) => {
|
onDataChange: (e: {map: Map}) => {
|
||||||
this.layerWatcher.analyzeMap(e.map)
|
this.layerWatcher.analyzeMap(e.map);
|
||||||
this.fetchSources();
|
this.fetchSources();
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const renderer = this._getRenderer();
|
const renderer = this._getRenderer();
|
||||||
|
|
||||||
let mapElement;
|
let mapElement;
|
||||||
|
|
||||||
// Check if OL code has been loaded?
|
// Check if OL code has been loaded?
|
||||||
if(renderer === 'ol') {
|
if(renderer === "ol") {
|
||||||
mapElement = <MapOpenLayers
|
mapElement = <MapOpenLayers
|
||||||
{...mapProps}
|
{...mapProps}
|
||||||
onChange={this.onMapChange}
|
onChange={this.onMapChange}
|
||||||
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
|
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
|
||||||
onLayerSelect={(layerId) => this.onLayerSelect(+layerId)}
|
onLayerSelect={(layerId) => this.onLayerSelect(+layerId)}
|
||||||
/>
|
/>;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
mapElement = <MapMaplibreGl {...mapProps}
|
mapElement = <MapMaplibreGl {...mapProps}
|
||||||
@@ -706,7 +706,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
options={this.state.maplibreGlDebugOptions}
|
options={this.state.maplibreGlDebugOptions}
|
||||||
inspectModeEnabled={this.state.mapState === "inspect"}
|
inspectModeEnabled={this.state.mapState === "inspect"}
|
||||||
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
|
||||||
onLayerSelect={this.onLayerSelect} />
|
onLayerSelect={this.onLayerSelect} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let filterName;
|
let filterName;
|
||||||
@@ -720,7 +720,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
return <div style={elementStyle} className="maputnik-map__container" data-wd-key="maplibre:container">
|
return <div style={elementStyle} className="maputnik-map__container" data-wd-key="maplibre:container">
|
||||||
{mapElement}
|
{mapElement}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateInUrl = () => {
|
setStateInUrl = () => {
|
||||||
@@ -749,7 +749,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
history.replaceState({selectedLayerIndex}, "Maputnik", url.href);
|
history.replaceState({selectedLayerIndex}, "Maputnik", url.href);
|
||||||
}
|
};
|
||||||
|
|
||||||
getInitialStateFromUrl = (mapStyle: StyleSpecification) => {
|
getInitialStateFromUrl = (mapStyle: StyleSpecification) => {
|
||||||
const url = new URL(location.href);
|
const url = new URL(location.href);
|
||||||
@@ -802,14 +802,14 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
console.warn(err);
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onLayerSelect = (index: number) => {
|
onLayerSelect = (index: number) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedLayerIndex: index,
|
selectedLayerIndex: index,
|
||||||
selectedLayerOriginalId: this.state.mapStyle.layers[index].id,
|
selectedLayerOriginalId: this.state.mapStyle.layers[index].id,
|
||||||
}, this.setStateInUrl);
|
}, this.setStateInUrl);
|
||||||
}
|
};
|
||||||
|
|
||||||
setModal(modalName: keyof AppState["isOpen"], value: boolean) {
|
setModal(modalName: keyof AppState["isOpen"], value: boolean) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -817,7 +817,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
...this.state.isOpen,
|
...this.state.isOpen,
|
||||||
[modalName]: value
|
[modalName]: value
|
||||||
}
|
}
|
||||||
}, this.setStateInUrl)
|
}, this.setStateInUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModal(modalName: keyof AppState["isOpen"]) {
|
toggleModal(modalName: keyof AppState["isOpen"]) {
|
||||||
@@ -826,7 +826,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
onSetFileHandle = (fileHandle: FileSystemFileHandle | null) => {
|
onSetFileHandle = (fileHandle: FileSystemFileHandle | null) => {
|
||||||
this.setState({ fileHandle });
|
this.setState({ fileHandle });
|
||||||
}
|
};
|
||||||
|
|
||||||
onChangeOpenlayersDebug = (key: keyof AppState["openlayersDebugOptions"], value: boolean) => {
|
onChangeOpenlayersDebug = (key: keyof AppState["openlayersDebugOptions"], value: boolean) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -835,7 +835,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
[key]: value,
|
[key]: value,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onChangeMaplibreGlDebug = (key: keyof AppState["maplibreGlDebugOptions"], value: any) => {
|
onChangeMaplibreGlDebug = (key: keyof AppState["maplibreGlDebugOptions"], value: any) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -844,11 +844,11 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
[key]: value,
|
[key]: value,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const layers = this.state.mapStyle.layers || []
|
const layers = this.state.mapStyle.layers || [];
|
||||||
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : undefined
|
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : undefined;
|
||||||
|
|
||||||
const toolbar = <AppToolbar
|
const toolbar = <AppToolbar
|
||||||
renderer={this._getRenderer()}
|
renderer={this._getRenderer()}
|
||||||
@@ -860,7 +860,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
onStyleOpen={this.onStyleChanged}
|
onStyleOpen={this.onStyleChanged}
|
||||||
onSetMapState={this.setMapState}
|
onSetMapState={this.setMapState}
|
||||||
onToggleModal={this.toggleModal.bind(this)}
|
onToggleModal={this.toggleModal.bind(this)}
|
||||||
/>
|
/>;
|
||||||
|
|
||||||
const layerList = <LayerList
|
const layerList = <LayerList
|
||||||
onMoveLayer={this.onMoveLayer}
|
onMoveLayer={this.onMoveLayer}
|
||||||
@@ -873,7 +873,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
layers={layers}
|
layers={layers}
|
||||||
sources={this.state.sources}
|
sources={this.state.sources}
|
||||||
errors={this.state.errors}
|
errors={this.state.errors}
|
||||||
/>
|
/>;
|
||||||
|
|
||||||
const layerEditor = selectedLayer ? <LayerEditor
|
const layerEditor = selectedLayer ? <LayerEditor
|
||||||
key={this.state.selectedLayerOriginalId}
|
key={this.state.selectedLayerOriginalId}
|
||||||
@@ -891,7 +891,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
onLayerVisibilityToggle={this.onLayerVisibilityToggle}
|
||||||
onLayerIdChange={this.onLayerIdChange}
|
onLayerIdChange={this.onLayerIdChange}
|
||||||
errors={this.state.errors}
|
errors={this.state.errors}
|
||||||
/> : undefined
|
/> : undefined;
|
||||||
|
|
||||||
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
|
const bottomPanel = (this.state.errors.length + this.state.infos.length) > 0 ? <MessagePanel
|
||||||
currentLayer={selectedLayer}
|
currentLayer={selectedLayer}
|
||||||
@@ -900,7 +900,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
errors={this.state.errors}
|
errors={this.state.errors}
|
||||||
infos={this.state.infos}
|
infos={this.state.infos}
|
||||||
/> : undefined
|
/> : undefined;
|
||||||
|
|
||||||
|
|
||||||
const modals = <div>
|
const modals = <div>
|
||||||
@@ -911,41 +911,41 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
onChangeMaplibreGlDebug={this.onChangeMaplibreGlDebug}
|
onChangeMaplibreGlDebug={this.onChangeMaplibreGlDebug}
|
||||||
onChangeOpenlayersDebug={this.onChangeOpenlayersDebug}
|
onChangeOpenlayersDebug={this.onChangeOpenlayersDebug}
|
||||||
isOpen={this.state.isOpen.debug}
|
isOpen={this.state.isOpen.debug}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'debug')}
|
onOpenToggle={this.toggleModal.bind(this, "debug")}
|
||||||
mapView={this.state.mapView}
|
mapView={this.state.mapView}
|
||||||
/>
|
/>
|
||||||
<ModalShortcuts
|
<ModalShortcuts
|
||||||
isOpen={this.state.isOpen.shortcuts}
|
isOpen={this.state.isOpen.shortcuts}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'shortcuts')}
|
onOpenToggle={this.toggleModal.bind(this, "shortcuts")}
|
||||||
/>
|
/>
|
||||||
<ModalSettings
|
<ModalSettings
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
onStyleChanged={this.onStyleChanged}
|
onStyleChanged={this.onStyleChanged}
|
||||||
onChangeMetadataProperty={this.onChangeMetadataProperty}
|
onChangeMetadataProperty={this.onChangeMetadataProperty}
|
||||||
isOpen={this.state.isOpen.settings}
|
isOpen={this.state.isOpen.settings}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'settings')}
|
onOpenToggle={this.toggleModal.bind(this, "settings")}
|
||||||
/>
|
/>
|
||||||
<ModalExport
|
<ModalExport
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
onStyleChanged={this.onStyleChanged}
|
onStyleChanged={this.onStyleChanged}
|
||||||
isOpen={this.state.isOpen.export}
|
isOpen={this.state.isOpen.export}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'export')}
|
onOpenToggle={this.toggleModal.bind(this, "export")}
|
||||||
fileHandle={this.state.fileHandle}
|
fileHandle={this.state.fileHandle}
|
||||||
onSetFileHandle={this.onSetFileHandle}
|
onSetFileHandle={this.onSetFileHandle}
|
||||||
/>
|
/>
|
||||||
<ModalOpen
|
<ModalOpen
|
||||||
isOpen={this.state.isOpen.open}
|
isOpen={this.state.isOpen.open}
|
||||||
onStyleOpen={this.openStyle}
|
onStyleOpen={this.openStyle}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'open')}
|
onOpenToggle={this.toggleModal.bind(this, "open")}
|
||||||
fileHandle={this.state.fileHandle}
|
fileHandle={this.state.fileHandle}
|
||||||
/>
|
/>
|
||||||
<ModalSources
|
<ModalSources
|
||||||
mapStyle={this.state.mapStyle}
|
mapStyle={this.state.mapStyle}
|
||||||
onStyleChanged={this.onStyleChanged}
|
onStyleChanged={this.onStyleChanged}
|
||||||
isOpen={this.state.isOpen.sources}
|
isOpen={this.state.isOpen.sources}
|
||||||
onOpenToggle={this.toggleModal.bind(this, 'sources')}
|
onOpenToggle={this.toggleModal.bind(this, "sources")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
return <AppLayout
|
return <AppLayout
|
||||||
toolbar={toolbar}
|
toolbar={toolbar}
|
||||||
@@ -954,6 +954,6 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
map={this.mapRenderer()}
|
map={this.mapRenderer()}
|
||||||
bottom={bottomPanel}
|
bottom={bottomPanel}
|
||||||
modals={modals}
|
modals={modals}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import ScrollContainer from './ScrollContainer'
|
import ScrollContainer from "./ScrollContainer";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { IconContext } from 'react-icons';
|
import { IconContext } from "react-icons";
|
||||||
|
|
||||||
type AppLayoutInternalProps = {
|
type AppLayoutInternalProps = {
|
||||||
toolbar: React.ReactElement
|
toolbar: React.ReactElement
|
||||||
@@ -17,7 +17,7 @@ class AppLayoutInternal extends React.Component<AppLayoutInternalProps> {
|
|||||||
render() {
|
render() {
|
||||||
document.body.dir = this.props.i18n.dir();
|
document.body.dir = this.props.i18n.dir();
|
||||||
|
|
||||||
return <IconContext.Provider value={{size: '14px'}}>
|
return <IconContext.Provider value={{size: "14px"}}>
|
||||||
<div className="maputnik-layout">
|
<div className="maputnik-layout">
|
||||||
{this.props.toolbar}
|
{this.props.toolbar}
|
||||||
<div className="maputnik-layout-main">
|
<div className="maputnik-layout-main">
|
||||||
@@ -37,7 +37,7 @@ class AppLayoutInternal extends React.Component<AppLayoutInternalProps> {
|
|||||||
}
|
}
|
||||||
{this.props.modals}
|
{this.props.modals}
|
||||||
</div>
|
</div>
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {formatLayerId} from '../libs/format';
|
import {formatLayerId} from "../libs/format";
|
||||||
import {LayerSpecification, StyleSpecification} from 'maplibre-gl';
|
import {type LayerSpecification, type StyleSpecification} from "maplibre-gl";
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type AppMessagePanelInternalProps = {
|
type AppMessagePanelInternalProps = {
|
||||||
errors?: unknown[]
|
errors?: unknown[]
|
||||||
@@ -15,7 +15,7 @@ type AppMessagePanelInternalProps = {
|
|||||||
class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalProps> {
|
class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {t, selectedLayerIndex} = this.props;
|
const {t, selectedLayerIndex} = this.props;
|
||||||
@@ -48,17 +48,17 @@ class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalPro
|
|||||||
}
|
}
|
||||||
return <p key={"error-"+idx} className="maputnik-message-panel-error">
|
return <p key={"error-"+idx} className="maputnik-message-panel-error">
|
||||||
{content}
|
{content}
|
||||||
</p>
|
</p>;
|
||||||
})
|
});
|
||||||
|
|
||||||
const infos = this.props.infos?.map((m, i) => {
|
const infos = this.props.infos?.map((m, i) => {
|
||||||
return <p key={"info-"+i}>{m}</p>
|
return <p key={"info-"+i}>{m}</p>;
|
||||||
})
|
});
|
||||||
|
|
||||||
return <div className="maputnik-message-panel">
|
return <div className="maputnik-message-panel">
|
||||||
{errors}
|
{errors}
|
||||||
{infos}
|
{infos}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames";
|
||||||
import {detect} from 'detect-browser';
|
import {detect} from "detect-browser";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MdOpenInBrowser,
|
MdOpenInBrowser,
|
||||||
@@ -10,17 +10,17 @@ import {
|
|||||||
MdFindInPage,
|
MdFindInPage,
|
||||||
MdLanguage,
|
MdLanguage,
|
||||||
MdSave
|
MdSave
|
||||||
} from 'react-icons/md'
|
} from "react-icons/md";
|
||||||
import pkgJson from '../../package.json'
|
import pkgJson from "../../package.json";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline'
|
import maputnikLogo from "maputnik-design/logos/logo-color.svg?inline";
|
||||||
import { withTranslation, WithTranslation } from 'react-i18next';
|
import { withTranslation, type WithTranslation } from "react-i18next";
|
||||||
import { supportedLanguages } from '../i18n';
|
import { supportedLanguages } from "../i18n";
|
||||||
import type { OnStyleChangedCallback } from '../libs/definitions';
|
import type { OnStyleChangedCallback } from "../libs/definitions";
|
||||||
|
|
||||||
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
||||||
const browser = detect();
|
const browser = detect();
|
||||||
const colorAccessibilityFiltersEnabled = ['chrome', 'firefox'].indexOf(browser!.name) > -1;
|
const colorAccessibilityFiltersEnabled = ["chrome", "firefox"].indexOf(browser!.name) > -1;
|
||||||
|
|
||||||
|
|
||||||
type IconTextProps = {
|
type IconTextProps = {
|
||||||
@@ -30,7 +30,7 @@ type IconTextProps = {
|
|||||||
|
|
||||||
class IconText extends React.Component<IconTextProps> {
|
class IconText extends React.Component<IconTextProps> {
|
||||||
render() {
|
render() {
|
||||||
return <span className="maputnik-icon-text">{this.props.children}</span>
|
return <span className="maputnik-icon-text">{this.props.children}</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,14 +44,14 @@ type ToolbarLinkProps = {
|
|||||||
class ToolbarLink extends React.Component<ToolbarLinkProps> {
|
class ToolbarLink extends React.Component<ToolbarLinkProps> {
|
||||||
render() {
|
render() {
|
||||||
return <a
|
return <a
|
||||||
className={classnames('maputnik-toolbar-link', this.props.className)}
|
className={classnames("maputnik-toolbar-link", this.props.className)}
|
||||||
href={this.props.href}
|
href={this.props.href}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
data-wd-key="toolbar:link"
|
data-wd-key="toolbar:link"
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</a>
|
</a>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ class ToolbarSelect extends React.Component<ToolbarSelectProps> {
|
|||||||
data-wd-key={this.props.wdKey}
|
data-wd-key={this.props.wdKey}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ class ToolbarAction extends React.Component<ToolbarActionProps> {
|
|||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</button>
|
</button>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
add: false,
|
add: false,
|
||||||
export: false,
|
export: false,
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
handleSelection(val: MapState) {
|
handleSelection(val: MapState) {
|
||||||
this.props.onSetMapState(val);
|
this.props.onSetMapState(val);
|
||||||
@@ -133,7 +133,7 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
const el = document.querySelector("#skip-target-"+target) as HTMLButtonElement;
|
const el = document.querySelector("#skip-target-"+target) as HTMLButtonElement;
|
||||||
el.focus();
|
el.focus();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
@@ -147,7 +147,7 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
id: "inspect",
|
id: "inspect",
|
||||||
group: "general",
|
group: "general",
|
||||||
title: t("Inspect"),
|
title: t("Inspect"),
|
||||||
disabled: this.props.renderer === 'ol',
|
disabled: this.props.renderer === "ol",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "filter-deuteranopia",
|
id: "filter-deuteranopia",
|
||||||
@@ -220,19 +220,19 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
|
<div className="maputnik-toolbar__actions" role="navigation" aria-label="Toolbar">
|
||||||
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, 'open')}>
|
<ToolbarAction wdKey="nav:open" onClick={this.props.onToggleModal.bind(this, "open")}>
|
||||||
<MdOpenInBrowser />
|
<MdOpenInBrowser />
|
||||||
<IconText>{t("Open")}</IconText>
|
<IconText>{t("Open")}</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, 'export')}>
|
<ToolbarAction wdKey="nav:export" onClick={this.props.onToggleModal.bind(this, "export")}>
|
||||||
<MdSave />
|
<MdSave />
|
||||||
<IconText>{t("Save")}</IconText>
|
<IconText>{t("Save")}</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, 'sources')}>
|
<ToolbarAction wdKey="nav:sources" onClick={this.props.onToggleModal.bind(this, "sources")}>
|
||||||
<MdLayers />
|
<MdLayers />
|
||||||
<IconText>{t("Data Sources")}</IconText>
|
<IconText>{t("Data Sources")}</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, 'settings')}>
|
<ToolbarAction wdKey="nav:settings" onClick={this.props.onToggleModal.bind(this, "settings")}>
|
||||||
<MdSettings />
|
<MdSettings />
|
||||||
<IconText>{t("Style Settings")}</IconText>
|
<IconText>{t("Style Settings")}</IconText>
|
||||||
</ToolbarAction>
|
</ToolbarAction>
|
||||||
@@ -292,7 +292,7 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
|
|||||||
</ToolbarLink>
|
</ToolbarLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, {PropsWithChildren, SyntheticEvent} from 'react'
|
import React, {type PropsWithChildren, type SyntheticEvent} from "react";
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames";
|
||||||
import FieldDocLabel from './FieldDocLabel'
|
import FieldDocLabel from "./FieldDocLabel";
|
||||||
import Doc from './Doc'
|
import Doc from "./Doc";
|
||||||
|
|
||||||
type BlockProps = PropsWithChildren & {
|
type BlockProps = PropsWithChildren & {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
@@ -26,13 +26,13 @@ export default class Block extends React.Component<BlockProps, BlockState> {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
showDoc: false,
|
showDoc: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) {
|
onChange(e: React.BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) {
|
||||||
const value = e.target.value
|
const value = e.target.value;
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
return this.props.onChange(value === "" ? undefined : value)
|
return this.props.onChange(value === "" ? undefined : value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export default class Block extends React.Component<BlockProps, BlockState> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
showDoc: val
|
showDoc: val
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some fields for example <InputColor/> bind click events inside the element
|
* Some fields for example <InputColor/> bind click events inside the element
|
||||||
@@ -58,7 +58,7 @@ export default class Block extends React.Component<BlockProps, BlockState> {
|
|||||||
if (event.nativeEvent.target.nodeName !== "A") {
|
if (event.nativeEvent.target.nodeName !== "A") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <label style={this.props.style}
|
return <label style={this.props.style}
|
||||||
@@ -87,17 +87,17 @@ export default class Block extends React.Component<BlockProps, BlockState> {
|
|||||||
<div className="maputnik-input-block-action">
|
<div className="maputnik-input-block-action">
|
||||||
{this.props.action}
|
{this.props.action}
|
||||||
</div>
|
</div>
|
||||||
<div className="maputnik-input-block-content" ref={el => {this._blockEl = el}}>
|
<div className="maputnik-input-block-content" ref={el => {this._blockEl = el;}}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
{this.props.fieldSpec &&
|
{this.props.fieldSpec &&
|
||||||
<div
|
<div
|
||||||
className="maputnik-doc-inline"
|
className="maputnik-doc-inline"
|
||||||
style={{display: this.state.showDoc ? '' : 'none'}}
|
style={{display: this.state.showDoc ? "" : "none"}}
|
||||||
>
|
>
|
||||||
<Doc fieldSpec={this.props.fieldSpec} />
|
<Doc fieldSpec={this.props.fieldSpec} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</label>
|
</label>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { Collapse as ReactCollapse } from 'react-collapse'
|
import { Collapse as ReactCollapse } from "react-collapse";
|
||||||
import {reducedMotionEnabled} from '../libs/accessibility'
|
import {reducedMotionEnabled} from "../libs/accessibility";
|
||||||
|
|
||||||
|
|
||||||
type CollapseProps = {
|
type CollapseProps = {
|
||||||
@@ -12,7 +12,7 @@ type CollapseProps = {
|
|||||||
export default class Collapse extends React.Component<CollapseProps> {
|
export default class Collapse extends React.Component<CollapseProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
isActive: true
|
isActive: true
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (reducedMotionEnabled()) {
|
if (reducedMotionEnabled()) {
|
||||||
@@ -20,14 +20,14 @@ export default class Collapse extends React.Component<CollapseProps> {
|
|||||||
<div style={{display: this.props.isActive ? "block" : "none"}}>
|
<div style={{display: this.props.isActive ? "block" : "none"}}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return (
|
return (
|
||||||
<ReactCollapse isOpened={this.props.isActive}>
|
<ReactCollapse isOpened={this.props.isActive}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</ReactCollapse>
|
</ReactCollapse>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {MdArrowDropDown, MdArrowDropUp} from 'react-icons/md'
|
import {MdArrowDropDown, MdArrowDropUp} from "react-icons/md";
|
||||||
|
|
||||||
type CollapserProps = {
|
type CollapserProps = {
|
||||||
isCollapsed: boolean
|
isCollapsed: boolean
|
||||||
@@ -12,7 +12,7 @@ export default class Collapser extends React.Component<CollapserProps> {
|
|||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
...this.props.style,
|
...this.props.style,
|
||||||
}
|
};
|
||||||
return this.props.isCollapsed ? <MdArrowDropUp style={iconStyle}/> : <MdArrowDropDown style={iconStyle} />
|
return this.props.isCollapsed ? <MdArrowDropUp style={iconStyle}/> : <MdArrowDropDown style={iconStyle} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
js: "JS",
|
js: "JS",
|
||||||
@@ -15,7 +15,7 @@ type DocProps = {
|
|||||||
doc?: string
|
doc?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'sdk-support'?: {
|
"sdk-support"?: {
|
||||||
[key: string]: typeof headers
|
[key: string]: typeof headers
|
||||||
}
|
}
|
||||||
docUrl?: string,
|
docUrl?: string,
|
||||||
@@ -28,7 +28,7 @@ export default class Doc extends React.Component<DocProps> {
|
|||||||
const {fieldSpec} = this.props;
|
const {fieldSpec} = this.props;
|
||||||
|
|
||||||
const {doc, values, docUrl, docUrlLinkText} = fieldSpec;
|
const {doc, values, docUrl, docUrlLinkText} = fieldSpec;
|
||||||
const sdkSupport = fieldSpec['sdk-support'];
|
const sdkSupport = fieldSpec["sdk-support"];
|
||||||
|
|
||||||
const renderValues = (
|
const renderValues = (
|
||||||
!!values &&
|
!!values &&
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InputArray, { InputArrayProps } from './InputArray'
|
import InputArray, { type InputArrayProps } from "./InputArray";
|
||||||
import Fieldset from './Fieldset'
|
import Fieldset from "./Fieldset";
|
||||||
|
|
||||||
type FieldArrayProps = InputArrayProps & {
|
type FieldArrayProps = InputArrayProps & {
|
||||||
name?: string
|
name?: string
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputAutocomplete, { InputAutocompleteProps } from './InputAutocomplete'
|
import InputAutocomplete, { type InputAutocompleteProps } from "./InputAutocomplete";
|
||||||
|
|
||||||
|
|
||||||
type FieldAutocompleteProps = InputAutocompleteProps & {
|
type FieldAutocompleteProps = InputAutocompleteProps & {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputCheckbox, {InputCheckboxProps} from './InputCheckbox'
|
import InputCheckbox, {type InputCheckboxProps} from "./InputCheckbox";
|
||||||
|
|
||||||
|
|
||||||
type FieldCheckboxProps = InputCheckboxProps & {
|
type FieldCheckboxProps = InputCheckboxProps & {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputColor, {InputColorProps} from './InputColor'
|
import InputColor, {type InputColorProps} from "./InputColor";
|
||||||
|
|
||||||
|
|
||||||
type FieldColorProps = InputColorProps & {
|
type FieldColorProps = InputColorProps & {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FieldCommentInternalProps = {
|
type FieldCommentInternalProps = {
|
||||||
value?: string
|
value?: string
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { JSX } from 'react'
|
import React, { type JSX } from "react";
|
||||||
import {MdInfoOutline, MdHighlightOff} from 'react-icons/md'
|
import {MdInfoOutline, MdHighlightOff} from "react-icons/md";
|
||||||
|
|
||||||
type FieldDocLabelProps = {
|
type FieldDocLabelProps = {
|
||||||
label: JSX.Element | string | undefined
|
label: JSX.Element | string | undefined
|
||||||
@@ -28,12 +28,12 @@ const FieldDocLabel: React.FC<FieldDocLabelProps> = (props) => {
|
|||||||
<label className="maputnik-doc-wrapper">
|
<label className="maputnik-doc-wrapper">
|
||||||
<div className="maputnik-doc-target">
|
<div className="maputnik-doc-target">
|
||||||
{label}
|
{label}
|
||||||
{'\xa0'}
|
{"\xa0"}
|
||||||
<button
|
<button
|
||||||
aria-label={open ? 'close property documentation' : 'open property documentation'}
|
aria-label={open ? "close property documentation" : "open property documentation"}
|
||||||
className={`maputnik-doc-button maputnik-doc-button--${open ? 'open' : 'closed'}`}
|
className={`maputnik-doc-button maputnik-doc-button--${open ? "open" : "closed"}`}
|
||||||
onClick={() => onToggleDoc(!open)}
|
onClick={() => onToggleDoc(!open)}
|
||||||
data-wd-key={'field-doc-button-' + label}
|
data-wd-key={"field-doc-button-" + label}
|
||||||
>
|
>
|
||||||
{open ? <MdHighlightOff /> : <MdInfoOutline />}
|
{open ? <MdHighlightOff /> : <MdInfoOutline />}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InputDynamicArray, {InputDynamicArrayProps} from './InputDynamicArray'
|
import InputDynamicArray, {type InputDynamicArrayProps} from "./InputDynamicArray";
|
||||||
import Fieldset from './Fieldset'
|
import Fieldset from "./Fieldset";
|
||||||
|
|
||||||
type FieldDynamicArrayProps = InputDynamicArrayProps & {
|
type FieldDynamicArrayProps = InputDynamicArrayProps & {
|
||||||
name?: string
|
name?: string
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InputEnum, {InputEnumProps} from './InputEnum'
|
import InputEnum, {type InputEnumProps} from "./InputEnum";
|
||||||
import Fieldset from './Fieldset';
|
import Fieldset from "./Fieldset";
|
||||||
|
|
||||||
|
|
||||||
type FieldEnumProps = InputEnumProps & {
|
type FieldEnumProps = InputEnumProps & {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import SpecProperty from './_SpecProperty'
|
import SpecProperty from "./_SpecProperty";
|
||||||
import DataProperty, { Stop } from './_DataProperty'
|
import DataProperty, { type Stop } from "./_DataProperty";
|
||||||
import ZoomProperty from './_ZoomProperty'
|
import ZoomProperty from "./_ZoomProperty";
|
||||||
import ExpressionProperty from './_ExpressionProperty'
|
import ExpressionProperty from "./_ExpressionProperty";
|
||||||
import {function as styleFunction} from '@maplibre/maplibre-gl-style-spec';
|
import {function as styleFunction} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import {findDefaultFromSpec} from '../libs/spec-helper';
|
import {findDefaultFromSpec} from "../libs/spec-helper";
|
||||||
|
|
||||||
|
|
||||||
function isLiteralExpression(value: any) {
|
function isLiteralExpression(value: any) {
|
||||||
@@ -22,9 +22,9 @@ function isGetExpression(value: any) {
|
|||||||
|
|
||||||
function isZoomField(value: any) {
|
function isZoomField(value: any) {
|
||||||
return (
|
return (
|
||||||
typeof(value) === 'object' &&
|
typeof(value) === "object" &&
|
||||||
value.stops &&
|
value.stops &&
|
||||||
typeof(value.property) === 'undefined' &&
|
typeof(value.property) === "undefined" &&
|
||||||
Array.isArray(value.stops) &&
|
Array.isArray(value.stops) &&
|
||||||
value.stops.length > 1 &&
|
value.stops.length > 1 &&
|
||||||
value.stops.every((stop: Stop) => {
|
value.stops.every((stop: Stop) => {
|
||||||
@@ -38,7 +38,7 @@ function isZoomField(value: any) {
|
|||||||
|
|
||||||
function isIdentityProperty(value: any) {
|
function isIdentityProperty(value: any) {
|
||||||
return (
|
return (
|
||||||
typeof(value) === 'object' &&
|
typeof(value) === "object" &&
|
||||||
value.type === "identity" &&
|
value.type === "identity" &&
|
||||||
Object.prototype.hasOwnProperty.call(value, "property")
|
Object.prototype.hasOwnProperty.call(value, "property")
|
||||||
);
|
);
|
||||||
@@ -46,16 +46,16 @@ function isIdentityProperty(value: any) {
|
|||||||
|
|
||||||
function isDataStopProperty(value: any) {
|
function isDataStopProperty(value: any) {
|
||||||
return (
|
return (
|
||||||
typeof(value) === 'object' &&
|
typeof(value) === "object" &&
|
||||||
value.stops &&
|
value.stops &&
|
||||||
typeof(value.property) !== 'undefined' &&
|
typeof(value.property) !== "undefined" &&
|
||||||
value.stops.length > 1 &&
|
value.stops.length > 1 &&
|
||||||
Array.isArray(value.stops) &&
|
Array.isArray(value.stops) &&
|
||||||
value.stops.every((stop: Stop) => {
|
value.stops.every((stop: Stop) => {
|
||||||
return (
|
return (
|
||||||
Array.isArray(stop) &&
|
Array.isArray(stop) &&
|
||||||
stop.length === 2 &&
|
stop.length === 2 &&
|
||||||
typeof(stop[0]) === 'object'
|
typeof(stop[0]) === "object"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -141,18 +141,18 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
|
|
||||||
const getFieldFunctionType = (fieldSpec: any) => {
|
const getFieldFunctionType = (fieldSpec: any) => {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return 'exponential';
|
return "exponential";
|
||||||
}
|
}
|
||||||
if (fieldSpec.type === 'number') {
|
if (fieldSpec.type === "number") {
|
||||||
return 'interval';
|
return "interval";
|
||||||
}
|
}
|
||||||
return 'categorical';
|
return "categorical";
|
||||||
};
|
};
|
||||||
|
|
||||||
const addStop = () => {
|
const addStop = () => {
|
||||||
const stops = props.value.stops.slice(0);
|
const stops = props.value.stops.slice(0);
|
||||||
const lastStop = stops[stops.length - 1];
|
const lastStop = stops[stops.length - 1];
|
||||||
if (typeof lastStop[0] === 'object') {
|
if (typeof lastStop[0] === "object") {
|
||||||
stops.push([
|
stops.push([
|
||||||
{ zoom: lastStop[0].zoom + 1, value: lastStop[0].value },
|
{ zoom: lastStop[0].zoom + 1, value: lastStop[0].value },
|
||||||
lastStop[1],
|
lastStop[1],
|
||||||
@@ -172,7 +172,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
const deleteExpression = () => {
|
const deleteExpression = () => {
|
||||||
const { fieldSpec, fieldName } = props;
|
const { fieldSpec, fieldName } = props;
|
||||||
props.onChange(fieldName, fieldSpec.default);
|
props.onChange(fieldName, fieldSpec.default);
|
||||||
setDataType('value');
|
setDataType("value");
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteStop = (stopIdx: number) => {
|
const deleteStop = (stopIdx: number) => {
|
||||||
@@ -195,7 +195,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
const { value } = props;
|
const { value } = props;
|
||||||
|
|
||||||
let zoomFunc: any;
|
let zoomFunc: any;
|
||||||
if (typeof value === 'object') {
|
if (typeof value === "object") {
|
||||||
if (value.stops) {
|
if (value.stops) {
|
||||||
zoomFunc = {
|
zoomFunc = {
|
||||||
base: value.base,
|
base: value.base,
|
||||||
@@ -229,13 +229,13 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
|
|
||||||
if (isGetExpression(value)) {
|
if (isGetExpression(value)) {
|
||||||
props.onChange(fieldName, {
|
props.onChange(fieldName, {
|
||||||
type: 'identity',
|
type: "identity",
|
||||||
property: value[1],
|
property: value[1],
|
||||||
});
|
});
|
||||||
setDataType('value');
|
setDataType("value");
|
||||||
} else if (isLiteralExpression(value)) {
|
} else if (isLiteralExpression(value)) {
|
||||||
props.onChange(fieldName, value[1]);
|
props.onChange(fieldName, value[1]);
|
||||||
setDataType('value');
|
setDataType("value");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
isGetExpression(value) ||
|
isGetExpression(value) ||
|
||||||
isLiteralExpression(value) ||
|
isLiteralExpression(value) ||
|
||||||
isPrimative(value) ||
|
isPrimative(value) ||
|
||||||
(Array.isArray(value) && fieldSpec.type === 'array')
|
(Array.isArray(value) && fieldSpec.type === "array")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,26 +253,26 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
const { value, fieldSpec } = props;
|
const { value, fieldSpec } = props;
|
||||||
let expression;
|
let expression;
|
||||||
|
|
||||||
if (typeof value === 'object' && 'stops' in value) {
|
if (typeof value === "object" && "stops" in value) {
|
||||||
expression = styleFunction.convertFunction(value, fieldSpec);
|
expression = styleFunction.convertFunction(value, fieldSpec);
|
||||||
} else if (isIdentityProperty(value)) {
|
} else if (isIdentityProperty(value)) {
|
||||||
expression = ['get', value.property];
|
expression = ["get", value.property];
|
||||||
} else {
|
} else {
|
||||||
expression = ['literal', value || props.fieldSpec.default];
|
expression = ["literal", value || props.fieldSpec.default];
|
||||||
}
|
}
|
||||||
props.onChange(props.fieldName, expression);
|
props.onChange(props.fieldName, expression);
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeDataFunction = () => {
|
const makeDataFunction = () => {
|
||||||
const functionType = getFieldFunctionType(props.fieldSpec);
|
const functionType = getFieldFunctionType(props.fieldSpec);
|
||||||
const stopValue = functionType === 'categorical' ? '' : 0;
|
const stopValue = functionType === "categorical" ? "" : 0;
|
||||||
const { value } = props;
|
const { value } = props;
|
||||||
let dataFunc;
|
let dataFunc;
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
if (typeof value === "object") {
|
||||||
if (value.stops) {
|
if (value.stops) {
|
||||||
dataFunc = {
|
dataFunc = {
|
||||||
property: '',
|
property: "",
|
||||||
type: functionType,
|
type: functionType,
|
||||||
base: value.base,
|
base: value.base,
|
||||||
stops: value.stops.map((stop: Stop) => {
|
stops: value.stops.map((stop: Stop) => {
|
||||||
@@ -281,7 +281,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
dataFunc = {
|
dataFunc = {
|
||||||
property: '',
|
property: "",
|
||||||
type: functionType,
|
type: functionType,
|
||||||
base: value.base,
|
base: value.base,
|
||||||
stops: [
|
stops: [
|
||||||
@@ -292,7 +292,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dataFunc = {
|
dataFunc = {
|
||||||
property: '',
|
property: "",
|
||||||
type: functionType,
|
type: functionType,
|
||||||
base: value.base,
|
base: value.base,
|
||||||
stops: [
|
stops: [
|
||||||
@@ -328,11 +328,11 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const propClass =
|
const propClass =
|
||||||
props.fieldSpec.default === props.value ? 'maputnik-default-property' : 'maputnik-modified-property';
|
props.fieldSpec.default === props.value ? "maputnik-default-property" : "maputnik-modified-property";
|
||||||
|
|
||||||
let specField;
|
let specField;
|
||||||
|
|
||||||
if (dataType === 'expression') {
|
if (dataType === "expression") {
|
||||||
specField = (
|
specField = (
|
||||||
<ExpressionProperty
|
<ExpressionProperty
|
||||||
errors={props.errors}
|
errors={props.errors}
|
||||||
@@ -348,7 +348,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
onBlur={onUnmarkEditing}
|
onBlur={onUnmarkEditing}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (dataType === 'zoom_function') {
|
} else if (dataType === "zoom_function") {
|
||||||
specField = (
|
specField = (
|
||||||
<ZoomProperty
|
<ZoomProperty
|
||||||
errors={props.errors}
|
errors={props.errors}
|
||||||
@@ -363,7 +363,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
onExpressionClick={makeExpression}
|
onExpressionClick={makeExpression}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (dataType === 'data_function') {
|
} else if (dataType === "data_function") {
|
||||||
specField = (
|
specField = (
|
||||||
<DataProperty
|
<DataProperty
|
||||||
errors={props.errors}
|
errors={props.errors}
|
||||||
@@ -396,7 +396,7 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={propClass} data-wd-key={'spec-field-container:' + props.fieldName}>
|
<div className={propClass} data-wd-key={"spec-field-container:" + props.fieldName}>
|
||||||
{specField}
|
{specField}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
|
|
||||||
type FieldIdProps = {
|
type FieldIdProps = {
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import InputJson, {InputJsonProps} from './InputJson'
|
import InputJson, {type InputJsonProps} from "./InputJson";
|
||||||
|
|
||||||
|
|
||||||
type FieldJsonProps = InputJsonProps & {};
|
type FieldJsonProps = InputJsonProps & {};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from "./InputNumber";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FieldMaxZoomInternalProps = {
|
type FieldMaxZoomInternalProps = {
|
||||||
value?: number
|
value?: number
|
||||||
@@ -14,7 +14,7 @@ type FieldMaxZoomInternalProps = {
|
|||||||
const FieldMaxZoomInternal: React.FC<FieldMaxZoomInternalProps> = (props) => {
|
const FieldMaxZoomInternal: React.FC<FieldMaxZoomInternalProps> = (props) => {
|
||||||
const t = props.t;
|
const t = props.t;
|
||||||
return (
|
return (
|
||||||
<Block label={t('Max Zoom')} fieldSpec={latest.layer.maxzoom}
|
<Block label={t("Max Zoom")} fieldSpec={latest.layer.maxzoom}
|
||||||
error={props.error}
|
error={props.error}
|
||||||
data-wd-key="max-zoom"
|
data-wd-key="max-zoom"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from "./InputNumber";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FieldMinZoomInternalProps = {
|
type FieldMinZoomInternalProps = {
|
||||||
value?: number
|
value?: number
|
||||||
@@ -14,7 +14,7 @@ type FieldMinZoomInternalProps = {
|
|||||||
const FieldMinZoomInternal: React.FC<FieldMinZoomInternalProps> = (props) => {
|
const FieldMinZoomInternal: React.FC<FieldMinZoomInternalProps> = (props) => {
|
||||||
const t = props.t;
|
const t = props.t;
|
||||||
return (
|
return (
|
||||||
<Block label={t('Min Zoom')} fieldSpec={latest.layer.minzoom}
|
<Block label={t("Min Zoom")} fieldSpec={latest.layer.minzoom}
|
||||||
error={props.error}
|
error={props.error}
|
||||||
data-wd-key="min-zoom"
|
data-wd-key="min-zoom"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InputMultiInput, {InputMultiInputProps} from './InputMultiInput'
|
import InputMultiInput, {type InputMultiInputProps} from "./InputMultiInput";
|
||||||
import Fieldset from './Fieldset'
|
import Fieldset from "./Fieldset";
|
||||||
|
|
||||||
|
|
||||||
type FieldMultiInputProps = InputMultiInputProps & {
|
type FieldMultiInputProps = InputMultiInputProps & {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InputNumber, {InputNumberProps} from './InputNumber'
|
import InputNumber, {type InputNumberProps} from "./InputNumber";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
|
|
||||||
|
|
||||||
type FieldNumberProps = InputNumberProps & {
|
type FieldNumberProps = InputNumberProps & {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputSelect, {InputSelectProps} from './InputSelect'
|
import InputSelect, {type InputSelectProps} from "./InputSelect";
|
||||||
|
|
||||||
|
|
||||||
type FieldSelectProps = InputSelectProps & {
|
type FieldSelectProps = InputSelectProps & {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from "./InputAutocomplete";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FieldSourceInternalProps = {
|
type FieldSourceInternalProps = {
|
||||||
value?: string
|
value?: string
|
||||||
@@ -23,7 +23,7 @@ const FieldSourceInternal: React.FC<FieldSourceInternalProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Block
|
<Block
|
||||||
label={t('Source')}
|
label={t("Source")}
|
||||||
fieldSpec={latest.layer.source}
|
fieldSpec={latest.layer.source}
|
||||||
error={error}
|
error={error}
|
||||||
data-wd-key={wdKey}
|
data-wd-key={wdKey}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import {latest} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from "./InputAutocomplete";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FieldSourceLayerInternalProps = {
|
type FieldSourceLayerInternalProps = {
|
||||||
value?: string
|
value?: string
|
||||||
@@ -21,8 +21,8 @@ const FieldSourceLayerInternal: React.FC<FieldSourceLayerInternalProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Block
|
<Block
|
||||||
label={t('Source Layer')}
|
label={t("Source Layer")}
|
||||||
fieldSpec={latest.layer['source-layer']}
|
fieldSpec={latest.layer["source-layer"]}
|
||||||
data-wd-key="layer-source-layer"
|
data-wd-key="layer-source-layer"
|
||||||
error={error}
|
error={error}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputSpec, { FieldSpecType, InputSpecProps } from './InputSpec'
|
import InputSpec, { type FieldSpecType, type InputSpecProps } from "./InputSpec";
|
||||||
import Fieldset from './Fieldset'
|
import Fieldset from "./Fieldset";
|
||||||
|
|
||||||
function getElementFromType(fieldSpec: { type?: FieldSpecType, values?: unknown[] }): typeof Fieldset | typeof Block {
|
function getElementFromType(fieldSpec: { type?: FieldSpecType, values?: unknown[] }): typeof Fieldset | typeof Block {
|
||||||
switch(fieldSpec.type) {
|
switch(fieldSpec.type) {
|
||||||
case 'color':
|
case "color":
|
||||||
return Block;
|
return Block;
|
||||||
case 'enum':
|
case "enum":
|
||||||
return (Object.keys(fieldSpec.values!).length <= 3 ? Fieldset : Block)
|
return (Object.keys(fieldSpec.values!).length <= 3 ? Fieldset : Block);
|
||||||
case 'boolean':
|
case "boolean":
|
||||||
return Block;
|
return Block;
|
||||||
case 'array':
|
case "array":
|
||||||
return Fieldset;
|
return Fieldset;
|
||||||
case 'resolvedImage':
|
case "resolvedImage":
|
||||||
return Block;
|
return Block;
|
||||||
case 'number':
|
case "number":
|
||||||
return Block;
|
return Block;
|
||||||
case 'string':
|
case "string":
|
||||||
return Block;
|
return Block;
|
||||||
case 'formatted':
|
case "formatted":
|
||||||
return Block;
|
return Block;
|
||||||
case 'padding':
|
case "padding":
|
||||||
return Block;
|
return Block;
|
||||||
case 'numberArray':
|
case "numberArray":
|
||||||
return Fieldset;
|
return Fieldset;
|
||||||
case 'colorArray':
|
case "colorArray":
|
||||||
return Fieldset;
|
return Fieldset;
|
||||||
case 'variableAnchorOffsetCollection':
|
case "variableAnchorOffsetCollection":
|
||||||
return Fieldset;
|
return Fieldset;
|
||||||
default:
|
default:
|
||||||
console.warn("No such type for: " + fieldSpec.type);
|
console.warn("No such type for: " + fieldSpec.type);
|
||||||
return Block;
|
return Block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputString, {InputStringProps} from './InputString'
|
import InputString, {type InputStringProps} from "./InputString";
|
||||||
|
|
||||||
type FieldStringProps = InputStringProps & {
|
type FieldStringProps = InputStringProps & {
|
||||||
name?: string
|
name?: string
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {v8} from '@maplibre/maplibre-gl-style-spec'
|
import {v8} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from "./InputSelect";
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { startCase } from 'lodash'
|
import { startCase } from "lodash";
|
||||||
|
|
||||||
type FieldTypeInternalProps = {
|
type FieldTypeInternalProps = {
|
||||||
value: string
|
value: string
|
||||||
@@ -22,9 +22,9 @@ const FieldTypeInternal: React.FC<FieldTypeInternalProps> = ({
|
|||||||
error,
|
error,
|
||||||
disabled = false
|
disabled = false
|
||||||
}) => {
|
}) => {
|
||||||
const layerstypes: [string, string][] = Object.keys(v8.layer.type.values || {}).map(v => [v, startCase(v.replace(/-/g, ' '))]);
|
const layerstypes: [string, string][] = Object.keys(v8.layer.type.values || {}).map(v => [v, startCase(v.replace(/-/g, " "))]);
|
||||||
return (
|
return (
|
||||||
<Block label={t('Type')} fieldSpec={v8.layer.type}
|
<Block label={t("Type")} fieldSpec={v8.layer.type}
|
||||||
data-wd-key={wdKey}
|
data-wd-key={wdKey}
|
||||||
error={error}
|
error={error}
|
||||||
>
|
>
|
||||||
@@ -36,7 +36,7 @@ const FieldTypeInternal: React.FC<FieldTypeInternalProps> = ({
|
|||||||
options={layerstypes}
|
options={layerstypes}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={value}
|
value={value}
|
||||||
data-wd-key={wdKey + '.select'}
|
data-wd-key={wdKey + ".select"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Block>
|
</Block>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InputUrl, {FieldUrlProps as InputUrlProps} from './InputUrl'
|
import InputUrl, {type FieldUrlProps as InputUrlProps} from "./InputUrl";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
|
|
||||||
|
|
||||||
type FieldUrlProps = InputUrlProps & {
|
type FieldUrlProps = InputUrlProps & {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { PropsWithChildren, ReactElement } from 'react'
|
import React, { type PropsWithChildren, type 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';
|
import generateUniqueId from "../libs/document-uid";
|
||||||
|
|
||||||
type FieldsetProps = PropsWithChildren & {
|
type FieldsetProps = PropsWithChildren & {
|
||||||
label?: string,
|
label?: string,
|
||||||
@@ -12,7 +12,7 @@ type FieldsetProps = PropsWithChildren & {
|
|||||||
|
|
||||||
const Fieldset: React.FC<FieldsetProps> = (props) => {
|
const Fieldset: React.FC<FieldsetProps> = (props) => {
|
||||||
const [showDoc, setShowDoc] = React.useState(false);
|
const [showDoc, setShowDoc] = React.useState(false);
|
||||||
const labelId = React.useRef(generateUniqueId('fieldset_label_'));
|
const labelId = React.useRef(generateUniqueId("fieldset_label_"));
|
||||||
|
|
||||||
const onToggleDoc = (val: boolean) => {
|
const onToggleDoc = (val: boolean) => {
|
||||||
setShowDoc(val);
|
setShowDoc(val);
|
||||||
@@ -37,7 +37,7 @@ const Fieldset: React.FC<FieldsetProps> = (props) => {
|
|||||||
<div className="maputnik-input-block-action">{props.action}</div>
|
<div className="maputnik-input-block-action">{props.action}</div>
|
||||||
<div className="maputnik-input-block-content">{props.children}</div>
|
<div className="maputnik-input-block-content">{props.children}</div>
|
||||||
{props.fieldSpec && (
|
{props.fieldSpec && (
|
||||||
<div className="maputnik-doc-inline" style={{ display: showDoc ? '' : 'none' }}>
|
<div className="maputnik-doc-inline" style={{ display: showDoc ? "" : "none" }}>
|
||||||
<Doc fieldSpec={props.fieldSpec} />
|
<Doc fieldSpec={props.fieldSpec} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {mdiTableRowPlusAfter} from '@mdi/js';
|
import {mdiTableRowPlusAfter} from "@mdi/js";
|
||||||
import {isEqual} from 'lodash';
|
import {isEqual} from "lodash";
|
||||||
import {ExpressionSpecification, LegacyFilterSpecification} from 'maplibre-gl'
|
import {type ExpressionSpecification, type LegacyFilterSpecification} from "maplibre-gl";
|
||||||
import {latest, migrate, convertFilter} from '@maplibre/maplibre-gl-style-spec'
|
import {latest, migrate, convertFilter} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import {mdiFunctionVariant} from '@mdi/js';
|
import {mdiFunctionVariant} from "@mdi/js";
|
||||||
|
|
||||||
import {combiningFilterOps} from '../libs/filterops'
|
import {combiningFilterOps} from "../libs/filterops";
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from "./InputSelect";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import SingleFilterEditor from './SingleFilterEditor'
|
import SingleFilterEditor from "./SingleFilterEditor";
|
||||||
import FilterEditorBlock from './FilterEditorBlock'
|
import FilterEditorBlock from "./FilterEditorBlock";
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import Doc from './Doc'
|
import Doc from "./Doc";
|
||||||
import ExpressionProperty from './_ExpressionProperty';
|
import ExpressionProperty from "./_ExpressionProperty";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import type { StyleSpecificationWithId } from '../libs/definitions';
|
import type { StyleSpecificationWithId } from "../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
function combiningFilter(props: FilterEditorInternalProps): LegacyFilterSpecification | ExpressionSpecification {
|
function combiningFilter(props: FilterEditorInternalProps): LegacyFilterSpecification | ExpressionSpecification {
|
||||||
const filter = props.filter || ['all'];
|
const filter = props.filter || ["all"];
|
||||||
|
|
||||||
if (!Array.isArray(filter)) {
|
if (!Array.isArray(filter)) {
|
||||||
return filter;
|
return filter;
|
||||||
@@ -28,7 +28,7 @@ function combiningFilter(props: FilterEditorInternalProps): LegacyFilterSpecific
|
|||||||
let filters = filter.slice(1);
|
let filters = filter.slice(1);
|
||||||
|
|
||||||
if(combiningFilterOps.indexOf(combiningOp) < 0) {
|
if(combiningFilterOps.indexOf(combiningOp) < 0) {
|
||||||
combiningOp = 'all';
|
combiningOp = "all";
|
||||||
filters = [filter.slice(0)];
|
filters = [filter.slice(0)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ function createStyleFromFilter(filter: LegacyFilterSpecification | ExpressionSpe
|
|||||||
"sources": {
|
"sources": {
|
||||||
"tmp": {
|
"tmp": {
|
||||||
"type": "geojson",
|
"type": "geojson",
|
||||||
"data": ''
|
"data": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sprite": "",
|
"sprite": "",
|
||||||
@@ -81,14 +81,14 @@ function checkIfSimpleFilter (filter: LegacyFilterSpecification | ExpressionSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hasCombiningFilter(filter: LegacyFilterSpecification | ExpressionSpecification) {
|
function hasCombiningFilter(filter: LegacyFilterSpecification | ExpressionSpecification) {
|
||||||
return combiningFilterOps.indexOf(filter[0]) >= 0
|
return combiningFilterOps.indexOf(filter[0]) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasNestedCombiningFilter(filter: LegacyFilterSpecification | ExpressionSpecification) {
|
function hasNestedCombiningFilter(filter: LegacyFilterSpecification | ExpressionSpecification) {
|
||||||
if(hasCombiningFilter(filter)) {
|
if(hasCombiningFilter(filter)) {
|
||||||
return filter.slice(1).map(f => hasCombiningFilter(f as any)).filter(f => f == true).length > 0
|
return filter.slice(1).map(f => hasCombiningFilter(f as any)).filter(f => f == true).length > 0;
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterEditorInternalProps = {
|
type FilterEditorInternalProps = {
|
||||||
@@ -108,7 +108,7 @@ type FilterEditorState = {
|
|||||||
class FilterEditorInternal extends React.Component<FilterEditorInternalProps, FilterEditorState> {
|
class FilterEditorInternal extends React.Component<FilterEditorInternalProps, FilterEditorState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
filter: ["all"],
|
filter: ["all"],
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor (props: FilterEditorInternalProps) {
|
constructor (props: FilterEditorInternalProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -120,42 +120,42 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
|
|
||||||
// Convert filter to combining filter
|
// Convert filter to combining filter
|
||||||
onFilterPartChanged(filterIdx: number, newPart: any[]) {
|
onFilterPartChanged(filterIdx: number, newPart: any[]) {
|
||||||
const newFilter = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification
|
const newFilter = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification;
|
||||||
newFilter[filterIdx] = newPart
|
newFilter[filterIdx] = newPart;
|
||||||
this.props.onChange(newFilter)
|
this.props.onChange(newFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteFilterItem(filterIdx: number) {
|
deleteFilterItem(filterIdx: number) {
|
||||||
const newFilter = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification
|
const newFilter = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification;
|
||||||
newFilter.splice(filterIdx + 1, 1)
|
newFilter.splice(filterIdx + 1, 1);
|
||||||
this.props.onChange(newFilter)
|
this.props.onChange(newFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
addFilterItem = () => {
|
addFilterItem = () => {
|
||||||
const newFilterItem = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification
|
const newFilterItem = combiningFilter(this.props).slice(0) as LegacyFilterSpecification | ExpressionSpecification;
|
||||||
(newFilterItem as any[]).push(['==', 'name', ''])
|
(newFilterItem as any[]).push(["==", "name", ""]);
|
||||||
this.props.onChange(newFilterItem)
|
this.props.onChange(newFilterItem);
|
||||||
}
|
};
|
||||||
|
|
||||||
onToggleDoc = (val: boolean) => {
|
onToggleDoc = (val: boolean) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showDoc: val
|
showDoc: val
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
makeFilter = () => {
|
makeFilter = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
displaySimpleFilter: true,
|
displaySimpleFilter: true,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
makeExpression = () => {
|
makeExpression = () => {
|
||||||
const 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,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<FilterEditorInternalProps>, state: FilterEditorState) {
|
static getDerivedStateFromProps(props: Readonly<FilterEditorInternalProps>, state: FilterEditorState) {
|
||||||
const displaySimpleFilter = checkIfSimpleFilter(combiningFilter(props));
|
const displaySimpleFilter = checkIfSimpleFilter(combiningFilter(props));
|
||||||
@@ -170,7 +170,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
else if (displaySimpleFilter && state.displaySimpleFilter === false) {
|
else if (displaySimpleFilter && state.displaySimpleFilter === false) {
|
||||||
return {
|
return {
|
||||||
valueIsSimpleFilter: true,
|
valueIsSimpleFilter: true,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return {
|
return {
|
||||||
@@ -203,7 +203,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
</svg>
|
</svg>
|
||||||
{t("Upgrade to expression")}
|
{t("Upgrade to expression")}
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
else if (displaySimpleFilter) {
|
else if (displaySimpleFilter) {
|
||||||
const filter = combiningFilter(this.props);
|
const filter = combiningFilter(this.props);
|
||||||
@@ -241,7 +241,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -280,7 +280,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
<div
|
<div
|
||||||
key="doc"
|
key="doc"
|
||||||
className="maputnik-doc-inline"
|
className="maputnik-doc-inline"
|
||||||
style={{display: this.state.showDoc ? '' : 'none'}}
|
style={{display: this.state.showDoc ? "" : "none"}}
|
||||||
>
|
>
|
||||||
<Doc fieldSpec={fieldSpec} />
|
<Doc fieldSpec={fieldSpec} />
|
||||||
</div>
|
</div>
|
||||||
@@ -306,7 +306,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
{this.state.valueIsSimpleFilter &&
|
{this.state.valueIsSimpleFilter &&
|
||||||
<div className="maputnik-expr-infobox">
|
<div className="maputnik-expr-infobox">
|
||||||
{t("You've entered an old style filter.")}
|
{t("You've entered an old style filter.")}
|
||||||
{' '}
|
{" "}
|
||||||
<button
|
<button
|
||||||
onClick={this.makeFilter}
|
onClick={this.makeFilter}
|
||||||
className="maputnik-expr-infobox__button"
|
className="maputnik-expr-infobox__button"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { PropsWithChildren } from 'react'
|
import React, { type PropsWithChildren } from "react";
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import {MdDelete} from 'react-icons/md'
|
import {MdDelete} from "react-icons/md";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FilterEditorBlockInternalProps = PropsWithChildren & {
|
type FilterEditorBlockInternalProps = PropsWithChildren & {
|
||||||
onDelete(...args: unknown[]): unknown
|
onDelete(...args: unknown[]): unknown
|
||||||
@@ -23,7 +23,7 @@ class FilterEditorBlockInternal extends React.Component<FilterEditorBlockInterna
|
|||||||
<MdDelete />
|
<MdDelete />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import type {CSSProperties} from 'react'
|
import type {CSSProperties} from "react";
|
||||||
import { BsDiamond, BsDiamondFill, BsFonts, BsSun } from 'react-icons/bs'
|
import { BsDiamond, BsDiamondFill, BsFonts, BsSun } from "react-icons/bs";
|
||||||
import { MdBubbleChart, MdOutlineCircle, MdPriorityHigh } from 'react-icons/md'
|
import { MdBubbleChart, MdOutlineCircle, MdPriorityHigh } from "react-icons/md";
|
||||||
import { IoAnalyticsOutline } from 'react-icons/io5'
|
import { IoAnalyticsOutline } from "react-icons/io5";
|
||||||
|
|
||||||
type IconLayerProps = {
|
type IconLayerProps = {
|
||||||
type: string
|
type: string
|
||||||
@@ -12,20 +12,20 @@ type IconLayerProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const IconLayer: React.FC<IconLayerProps> = (props) => {
|
const IconLayer: React.FC<IconLayerProps> = (props) => {
|
||||||
const iconProps = { style: props.style }
|
const iconProps = { style: props.style };
|
||||||
switch(props.type) {
|
switch(props.type) {
|
||||||
case 'fill-extrusion': return <BsDiamondFill {...iconProps} />
|
case "fill-extrusion": return <BsDiamondFill {...iconProps} />;
|
||||||
case 'raster': return <BsDiamond {...iconProps} />
|
case "raster": return <BsDiamond {...iconProps} />;
|
||||||
case 'hillshade': return <BsSun {...iconProps} />
|
case "hillshade": return <BsSun {...iconProps} />;
|
||||||
case 'color-relief': return <MdBubbleChart {...iconProps} />
|
case "color-relief": return <MdBubbleChart {...iconProps} />;
|
||||||
case 'heatmap': return <BsDiamond {...iconProps} />
|
case "heatmap": return <BsDiamond {...iconProps} />;
|
||||||
case 'fill': return <BsDiamond {...iconProps} />
|
case "fill": return <BsDiamond {...iconProps} />;
|
||||||
case 'background': return <BsDiamondFill {...iconProps} />
|
case "background": return <BsDiamondFill {...iconProps} />;
|
||||||
case 'line': return <IoAnalyticsOutline {...iconProps} />
|
case "line": return <IoAnalyticsOutline {...iconProps} />;
|
||||||
case 'symbol': return <BsFonts {...iconProps} />
|
case "symbol": return <BsFonts {...iconProps} />;
|
||||||
case 'circle': return <MdOutlineCircle {...iconProps} />
|
case "circle": return <MdOutlineCircle {...iconProps} />;
|
||||||
default: return <MdPriorityHigh {...iconProps} />
|
default: return <MdPriorityHigh {...iconProps} />;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default IconLayer;
|
export default IconLayer;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from "./InputNumber";
|
||||||
|
|
||||||
export type InputArrayProps = {
|
export type InputArrayProps = {
|
||||||
value: (string | number | undefined)[]
|
value: (string | number | undefined)[]
|
||||||
@@ -8,20 +8,20 @@ export type InputArrayProps = {
|
|||||||
length?: number
|
length?: number
|
||||||
default?: (string | number | undefined)[]
|
default?: (string | number | undefined)[]
|
||||||
onChange?(value: (string | number | undefined)[] | undefined): unknown
|
onChange?(value: (string | number | undefined)[] | undefined): unknown
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
label?: string
|
label?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
type InputArrayState = {
|
type InputArrayState = {
|
||||||
value: (string | number | undefined)[]
|
value: (string | number | undefined)[]
|
||||||
initialPropsValue: unknown[]
|
initialPropsValue: unknown[]
|
||||||
}
|
};
|
||||||
|
|
||||||
export default class InputArray extends React.Component<InputArrayProps, InputArrayState> {
|
export default class InputArray extends React.Component<InputArrayProps, InputArrayState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
value: [],
|
value: [],
|
||||||
default: [],
|
default: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor (props: InputArrayProps) {
|
constructor (props: InputArrayProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -44,7 +44,7 @@ export default class InputArray extends React.Component<InputArrayProps, InputAr
|
|||||||
value[i] = state.value[i];
|
value[i] = state.value[i];
|
||||||
initialPropsValue[i] = state.value[i];
|
initialPropsValue[i] = state.value[i];
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
@@ -54,7 +54,7 @@ export default class InputArray extends React.Component<InputArrayProps, InputAr
|
|||||||
|
|
||||||
isComplete(value: unknown[]) {
|
isComplete(value: unknown[]) {
|
||||||
return Array(this.props.length).fill(null).every((_, i) => {
|
return Array(this.props.length).fill(null).every((_, i) => {
|
||||||
const val = value[i]
|
const val = value[i];
|
||||||
return !(val === undefined || val === "");
|
return !(val === undefined || val === "");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -82,20 +82,20 @@ export default class InputArray extends React.Component<InputArrayProps, InputAr
|
|||||||
const containsValues = (
|
const containsValues = (
|
||||||
value.length > 0 &&
|
value.length > 0 &&
|
||||||
!value.every(val => {
|
!value.every(val => {
|
||||||
return (val === "" || val === undefined)
|
return (val === "" || val === undefined);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputs = Array(this.props.length).fill(null).map((_, i) => {
|
const inputs = Array(this.props.length).fill(null).map((_, i) => {
|
||||||
if(this.props.type === 'number') {
|
if(this.props.type === "number") {
|
||||||
return <InputNumber
|
return <InputNumber
|
||||||
key={i}
|
key={i}
|
||||||
default={containsValues || !this.props.default ? undefined : this.props.default[i] as number}
|
default={containsValues || !this.props.default ? undefined : this.props.default[i] as number}
|
||||||
value={value[i] as number}
|
value={value[i] as number}
|
||||||
required={containsValues ? true : false}
|
required={containsValues ? true : false}
|
||||||
onChange={(v) => this.changeValue(i, v)}
|
onChange={(v) => this.changeValue(i, v)}
|
||||||
aria-label={this.props['aria-label'] || this.props.label}
|
aria-label={this.props["aria-label"] || this.props.label}
|
||||||
/>
|
/>;
|
||||||
} else {
|
} else {
|
||||||
return <InputString
|
return <InputString
|
||||||
key={i}
|
key={i}
|
||||||
@@ -103,15 +103,15 @@ export default class InputArray extends React.Component<InputArrayProps, InputAr
|
|||||||
value={value[i] as string}
|
value={value[i] as string}
|
||||||
required={containsValues ? true : false}
|
required={containsValues ? true : false}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
aria-label={this.props['aria-label'] || this.props.label}
|
aria-label={this.props["aria-label"] || this.props.label}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="maputnik-array">
|
<div className="maputnik-array">
|
||||||
{inputs}
|
{inputs}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from "./InputAutocomplete";
|
||||||
import { mount } from 'cypress/react'
|
import { mount } from "cypress/react";
|
||||||
|
|
||||||
const fruits = ['apple', 'banana', 'cherry'];
|
const fruits = ["apple", "banana", "cherry"];
|
||||||
|
|
||||||
describe('<InputAutocomplete />', () => {
|
describe("<InputAutocomplete />", () => {
|
||||||
it('filters options when typing', () => {
|
it("filters options when typing", () => {
|
||||||
mount(
|
mount(
|
||||||
<InputAutocomplete aria-label="Fruit" options={fruits.map(f => [f, f])} />
|
<InputAutocomplete aria-label="Fruit" options={fruits.map(f => [f, f])} />
|
||||||
);
|
);
|
||||||
cy.get('input').focus();
|
cy.get("input").focus();
|
||||||
cy.get('.maputnik-autocomplete-menu-item').should('have.length', 3);
|
cy.get(".maputnik-autocomplete-menu-item").should("have.length", 3);
|
||||||
cy.get('input').type('ch');
|
cy.get("input").type("ch");
|
||||||
cy.get('.maputnik-autocomplete-menu-item').should('have.length', 1).and('contain', 'cherry');
|
cy.get(".maputnik-autocomplete-menu-item").should("have.length", 1).and("contain", "cherry");
|
||||||
cy.get('.maputnik-autocomplete-menu-item').click();
|
cy.get(".maputnik-autocomplete-menu-item").click();
|
||||||
cy.get('input').should('have.value', 'cherry');
|
cy.get("input").should("have.value", "cherry");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames";
|
||||||
import {useCombobox} from 'downshift'
|
import {useCombobox} from "downshift";
|
||||||
|
|
||||||
const MAX_HEIGHT = 140
|
const MAX_HEIGHT = 140;
|
||||||
|
|
||||||
export type InputAutocompleteProps = {
|
export type InputAutocompleteProps = {
|
||||||
value?: string
|
value?: string
|
||||||
options?: any[]
|
options?: any[]
|
||||||
onChange?(value: string | undefined): unknown
|
onChange?(value: string | undefined): unknown
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InputAutocomplete({
|
export default function InputAutocomplete({
|
||||||
value,
|
value,
|
||||||
options = [],
|
options = [],
|
||||||
onChange = () => {},
|
onChange = () => {},
|
||||||
'aria-label': ariaLabel,
|
"aria-label": ariaLabel,
|
||||||
}: InputAutocompleteProps) {
|
}: InputAutocompleteProps) {
|
||||||
const [input, setInput] = React.useState(value || '')
|
const [input, setInput] = React.useState(value || "");
|
||||||
const menuRef = React.useRef<HTMLDivElement>(null)
|
const menuRef = React.useRef<HTMLDivElement>(null);
|
||||||
const [maxHeight, setMaxHeight] = React.useState(MAX_HEIGHT)
|
const [maxHeight, setMaxHeight] = React.useState(MAX_HEIGHT);
|
||||||
|
|
||||||
const filteredItems = React.useMemo(() => {
|
const filteredItems = React.useMemo(() => {
|
||||||
const lv = input.toLowerCase()
|
const lv = input.toLowerCase();
|
||||||
return options.filter((item) => item[0].toLowerCase().includes(lv))
|
return options.filter((item) => item[0].toLowerCase().includes(lv));
|
||||||
}, [options, input])
|
}, [options, input]);
|
||||||
|
|
||||||
const calcMaxHeight = React.useCallback(() => {
|
const calcMaxHeight = React.useCallback(() => {
|
||||||
if (menuRef.current) {
|
if (menuRef.current) {
|
||||||
const space = window.innerHeight - menuRef.current.getBoundingClientRect().top
|
const space = window.innerHeight - menuRef.current.getBoundingClientRect().top;
|
||||||
setMaxHeight(Math.min(space, MAX_HEIGHT))
|
setMaxHeight(Math.min(space, MAX_HEIGHT));
|
||||||
}
|
}
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -43,48 +43,48 @@ export default function InputAutocomplete({
|
|||||||
} = useCombobox({
|
} = useCombobox({
|
||||||
items: filteredItems,
|
items: filteredItems,
|
||||||
inputValue: input,
|
inputValue: input,
|
||||||
itemToString: (item) => (item ? item[0] : ''),
|
itemToString: (item) => (item ? item[0] : ""),
|
||||||
stateReducer: (_state, action) => {
|
stateReducer: (_state, action) => {
|
||||||
if (action.type === useCombobox.stateChangeTypes.InputClick) {
|
if (action.type === useCombobox.stateChangeTypes.InputClick) {
|
||||||
return {...action.changes, isOpen: true}
|
return {...action.changes, isOpen: true};
|
||||||
}
|
}
|
||||||
return action.changes
|
return action.changes;
|
||||||
},
|
},
|
||||||
onSelectedItemChange: ({selectedItem}) => {
|
onSelectedItemChange: ({selectedItem}) => {
|
||||||
const v = selectedItem ? selectedItem[0] : ''
|
const v = selectedItem ? selectedItem[0] : "";
|
||||||
setInput(v)
|
setInput(v);
|
||||||
onChange(selectedItem ? selectedItem[0] : undefined)
|
onChange(selectedItem ? selectedItem[0] : undefined);
|
||||||
},
|
},
|
||||||
onInputValueChange: ({inputValue: v}) => {
|
onInputValueChange: ({inputValue: v}) => {
|
||||||
if (typeof v === 'string') {
|
if (typeof v === "string") {
|
||||||
setInput(v)
|
setInput(v);
|
||||||
onChange(v === '' ? undefined : v)
|
onChange(v === "" ? undefined : v);
|
||||||
openMenu()
|
openMenu();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
calcMaxHeight()
|
calcMaxHeight();
|
||||||
}
|
}
|
||||||
}, [isOpen, calcMaxHeight])
|
}, [isOpen, calcMaxHeight]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.addEventListener('resize', calcMaxHeight)
|
window.addEventListener("resize", calcMaxHeight);
|
||||||
return () => window.removeEventListener('resize', calcMaxHeight)
|
return () => window.removeEventListener("resize", calcMaxHeight);
|
||||||
}, [calcMaxHeight])
|
}, [calcMaxHeight]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setInput(value || '')
|
setInput(value || "");
|
||||||
}, [value])
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="maputnik-autocomplete">
|
<div className="maputnik-autocomplete">
|
||||||
<input
|
<input
|
||||||
{...getInputProps({
|
{...getInputProps({
|
||||||
'aria-label': ariaLabel,
|
"aria-label": ariaLabel,
|
||||||
className: 'maputnik-string',
|
className: "maputnik-string",
|
||||||
spellCheck: false,
|
spellCheck: false,
|
||||||
onFocus: () => openMenu(),
|
onFocus: () => openMenu(),
|
||||||
})}
|
})}
|
||||||
@@ -92,7 +92,7 @@ export default function InputAutocomplete({
|
|||||||
<div
|
<div
|
||||||
{...getMenuProps({}, {suppressRefError: true})}
|
{...getMenuProps({}, {suppressRefError: true})}
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
style={{position: 'fixed', overflow: 'auto', maxHeight, zIndex: 998}}
|
style={{position: "fixed", overflow: "auto", maxHeight, zIndex: 998}}
|
||||||
className="maputnik-autocomplete-menu"
|
className="maputnik-autocomplete-menu"
|
||||||
>
|
>
|
||||||
{isOpen &&
|
{isOpen &&
|
||||||
@@ -102,8 +102,8 @@ export default function InputAutocomplete({
|
|||||||
{...getItemProps({
|
{...getItemProps({
|
||||||
item,
|
item,
|
||||||
index,
|
index,
|
||||||
className: classnames('maputnik-autocomplete-menu-item', {
|
className: classnames("maputnik-autocomplete-menu-item", {
|
||||||
'maputnik-autocomplete-menu-item-selected': highlightedIndex === index,
|
"maputnik-autocomplete-menu-item-selected": highlightedIndex === index,
|
||||||
}),
|
}),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -112,5 +112,5 @@ export default function InputAutocomplete({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames";
|
||||||
|
|
||||||
type InputButtonProps = {
|
type InputButtonProps = {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
@@ -28,6 +28,6 @@ export default class InputButton extends React.Component<InputButtonProps> {
|
|||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</button>
|
</button>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
export type InputCheckboxProps = {
|
export type InputCheckboxProps = {
|
||||||
value?: boolean
|
value?: boolean
|
||||||
@@ -9,11 +9,11 @@ export type InputCheckboxProps = {
|
|||||||
export default class InputCheckbox extends React.Component<InputCheckboxProps> {
|
export default class InputCheckbox extends React.Component<InputCheckboxProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
value: false,
|
value: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
onChange = () => {
|
onChange = () => {
|
||||||
this.props.onChange(!this.props.value);
|
this.props.onChange(!this.props.value);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="maputnik-checkbox-wrapper">
|
return <div className="maputnik-checkbox-wrapper">
|
||||||
@@ -27,11 +27,11 @@ 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>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import Color from 'color'
|
import Color from "color";
|
||||||
import ChromePicker from 'react-color/lib/components/chrome/Chrome'
|
import ChromePicker from "react-color/lib/components/chrome/Chrome";
|
||||||
import {ColorResult} from 'react-color';
|
import {type ColorResult} from "react-color";
|
||||||
import lodash from 'lodash';
|
import lodash from "lodash";
|
||||||
|
|
||||||
function formatColor(color: ColorResult): string {
|
function formatColor(color: ColorResult): string {
|
||||||
const rgb = color.rgb
|
const rgb = color.rgb;
|
||||||
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`
|
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InputColorProps = {
|
export type InputColorProps = {
|
||||||
@@ -16,14 +16,14 @@ export type InputColorProps = {
|
|||||||
doc?: string
|
doc?: string
|
||||||
style?: object
|
style?: object
|
||||||
default?: string
|
default?: string
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
/*** Number fields with support for min, max and units and documentation*/
|
/*** Number fields with support for min, max and units and documentation*/
|
||||||
export default class InputColor extends React.Component<InputColorProps> {
|
export default class InputColor extends React.Component<InputColorProps> {
|
||||||
state = {
|
state = {
|
||||||
pickerOpened: false
|
pickerOpened: false
|
||||||
}
|
};
|
||||||
colorInput: HTMLInputElement | null = null;
|
colorInput: HTMLInputElement | null = null;
|
||||||
|
|
||||||
constructor (props: InputColorProps) {
|
constructor (props: InputColorProps) {
|
||||||
@@ -39,29 +39,29 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
//but I am too stupid to get it to work together with fixed position
|
//but I am too stupid to get it to work together with fixed position
|
||||||
//and scrollbars so I have to fallback to JavaScript
|
//and scrollbars so I have to fallback to JavaScript
|
||||||
calcPickerOffset = () => {
|
calcPickerOffset = () => {
|
||||||
const elem = this.colorInput
|
const elem = this.colorInput;
|
||||||
if(elem) {
|
if(elem) {
|
||||||
const pos = elem.getBoundingClientRect()
|
const pos = elem.getBoundingClientRect();
|
||||||
return {
|
return {
|
||||||
top: pos.top,
|
top: pos.top,
|
||||||
left: pos.left + 196,
|
left: pos.left + 196,
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
top: 160,
|
top: 160,
|
||||||
left: 555,
|
left: 555,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
togglePicker = () => {
|
togglePicker = () => {
|
||||||
this.setState({ pickerOpened: !this.state.pickerOpened })
|
this.setState({ pickerOpened: !this.state.pickerOpened });
|
||||||
}
|
};
|
||||||
|
|
||||||
get color() {
|
get color() {
|
||||||
// Catch invalid color.
|
// Catch invalid color.
|
||||||
try {
|
try {
|
||||||
return Color(this.props.value).rgb()
|
return Color(this.props.value).rgb();
|
||||||
}
|
}
|
||||||
catch(err) {
|
catch(err) {
|
||||||
console.warn("Error parsing color: ", err);
|
console.warn("Error parsing color: ", err);
|
||||||
@@ -74,7 +74,7 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const offset = this.calcPickerOffset()
|
const offset = this.calcPickerOffset();
|
||||||
const currentColor = this.color.object();
|
const currentColor = this.color.object();
|
||||||
const currentChromeColor = {
|
const currentChromeColor = {
|
||||||
r: currentColor.r,
|
r: currentColor.r,
|
||||||
@@ -82,12 +82,12 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
b: currentColor.b,
|
b: currentColor.b,
|
||||||
// Rename alpha -> a for ChromePicker
|
// Rename alpha -> a for ChromePicker
|
||||||
a: currentColor.alpha!
|
a: currentColor.alpha!
|
||||||
}
|
};
|
||||||
|
|
||||||
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,
|
||||||
@@ -101,14 +101,14 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
onClick={this.togglePicker}
|
onClick={this.togglePicker}
|
||||||
style={{
|
style={{
|
||||||
zIndex: -1,
|
zIndex: -1,
|
||||||
position: 'fixed',
|
position: "fixed",
|
||||||
top: '0px',
|
top: "0px",
|
||||||
right: '0px',
|
right: "0px",
|
||||||
bottom: '0px',
|
bottom: "0px",
|
||||||
left: '0px',
|
left: "0px",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
const swatchStyle = {
|
const swatchStyle = {
|
||||||
backgroundColor: this.props.value
|
backgroundColor: this.props.value
|
||||||
@@ -118,11 +118,11 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
{this.state.pickerOpened && picker}
|
{this.state.pickerOpened && picker}
|
||||||
<div className="maputnik-color-swatch" style={swatchStyle}></div>
|
<div className="maputnik-color-swatch" style={swatchStyle}></div>
|
||||||
<input
|
<input
|
||||||
aria-label={this.props['aria-label']}
|
aria-label={this.props["aria-label"]}
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className="maputnik-color"
|
className="maputnik-color"
|
||||||
ref={(input) => {this.colorInput = input}}
|
ref={(input) => {this.colorInput = input;}}
|
||||||
onClick={this.togglePicker}
|
onClick={this.togglePicker}
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
name={this.props.name}
|
name={this.props.name}
|
||||||
@@ -130,6 +130,6 @@ export default class InputColor extends React.Component<InputColorProps> {
|
|||||||
value={this.props.value ? this.props.value : ""}
|
value={this.props.value ? this.props.value : ""}
|
||||||
onChange={(e) => this.onChange(e.target.value)}
|
onChange={(e) => this.onChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,67 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import capitalize from 'lodash.capitalize'
|
import capitalize from "lodash.capitalize";
|
||||||
import {MdDelete} from 'react-icons/md'
|
import {MdDelete} from "react-icons/md";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from "./InputNumber";
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import FieldDocLabel from './FieldDocLabel'
|
import FieldDocLabel from "./FieldDocLabel";
|
||||||
import InputEnum from './InputEnum'
|
import InputEnum from "./InputEnum";
|
||||||
import InputUrl from './InputUrl'
|
import InputUrl from "./InputUrl";
|
||||||
import InputColor from './InputColor';
|
import InputColor from "./InputColor";
|
||||||
|
|
||||||
|
|
||||||
export type InputDynamicArrayProps = {
|
export type InputDynamicArrayProps = {
|
||||||
value?: (string | number | undefined)[]
|
value?: (string | number | undefined)[]
|
||||||
type?: 'url' | 'number' | 'enum' | 'string' | 'color'
|
type?: "url" | "number" | "enum" | "string" | "color"
|
||||||
default?: (string | number | undefined)[]
|
default?: (string | number | undefined)[]
|
||||||
onChange?(values: (string | number | undefined)[] | undefined): unknown
|
onChange?(values: (string | number | undefined)[] | undefined): unknown
|
||||||
style?: object
|
style?: object
|
||||||
fieldSpec?: {
|
fieldSpec?: {
|
||||||
values?: any
|
values?: any
|
||||||
}
|
}
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
label: string
|
label: string
|
||||||
}
|
};
|
||||||
|
|
||||||
type InputDynamicArrayInternalProps = InputDynamicArrayProps & WithTranslation;
|
type InputDynamicArrayInternalProps = InputDynamicArrayProps & WithTranslation;
|
||||||
|
|
||||||
class InputDynamicArrayInternal extends React.Component<InputDynamicArrayInternalProps> {
|
class InputDynamicArrayInternal extends React.Component<InputDynamicArrayInternalProps> {
|
||||||
changeValue(idx: number, newValue: string | number | undefined) {
|
changeValue(idx: number, newValue: string | number | undefined) {
|
||||||
const values = this.values.slice(0)
|
const values = this.values.slice(0);
|
||||||
values[idx] = newValue
|
values[idx] = newValue;
|
||||||
if (this.props.onChange) this.props.onChange(values)
|
if (this.props.onChange) this.props.onChange(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
get values() {
|
get values() {
|
||||||
return this.props.value || this.props.default || []
|
return this.props.value || this.props.default || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addValue = () => {
|
addValue = () => {
|
||||||
const values = this.values.slice(0)
|
const values = this.values.slice(0);
|
||||||
if (this.props.type === 'number') {
|
if (this.props.type === "number") {
|
||||||
values.push(0)
|
values.push(0);
|
||||||
}
|
}
|
||||||
else if (this.props.type === 'url') {
|
else if (this.props.type === "url") {
|
||||||
values.push("");
|
values.push("");
|
||||||
}
|
}
|
||||||
else if (this.props.type === 'enum') {
|
else if (this.props.type === "enum") {
|
||||||
const {fieldSpec} = this.props;
|
const {fieldSpec} = this.props;
|
||||||
const defaultValue = Object.keys(fieldSpec!.values)[0];
|
const defaultValue = Object.keys(fieldSpec!.values)[0];
|
||||||
values.push(defaultValue);
|
values.push(defaultValue);
|
||||||
} else if (this.props.type === 'color') {
|
} else if (this.props.type === "color") {
|
||||||
values.push("#000000");
|
values.push("#000000");
|
||||||
} else {
|
} else {
|
||||||
values.push("")
|
values.push("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange(values)
|
if (this.props.onChange) this.props.onChange(values);
|
||||||
}
|
};
|
||||||
|
|
||||||
deleteValue(valueIdx: number) {
|
deleteValue(valueIdx: number) {
|
||||||
const values = this.values.slice(0)
|
const values = this.values.slice(0);
|
||||||
values.splice(valueIdx, 1)
|
values.splice(valueIdx, 1);
|
||||||
|
|
||||||
if (this.props.onChange) this.props.onChange(values.length > 0 ? values : undefined);
|
if (this.props.onChange) this.props.onChange(values.length > 0 ? values : undefined);
|
||||||
}
|
}
|
||||||
@@ -75,42 +75,42 @@ class InputDynamicArrayInternal extends React.Component<InputDynamicArrayInterna
|
|||||||
{...i18nProps}
|
{...i18nProps}
|
||||||
/>;
|
/>;
|
||||||
let input;
|
let input;
|
||||||
if(this.props.type === 'url') {
|
if(this.props.type === "url") {
|
||||||
input = <InputUrl
|
input = <InputUrl
|
||||||
value={v as string}
|
value={v as string}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
aria-label={this.props['aria-label'] || this.props.label}
|
aria-label={this.props["aria-label"] || this.props.label}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
else if (this.props.type === 'number') {
|
else if (this.props.type === "number") {
|
||||||
input = <InputNumber
|
input = <InputNumber
|
||||||
value={v as number}
|
value={v as number}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
aria-label={this.props['aria-label'] || this.props.label}
|
aria-label={this.props["aria-label"] || this.props.label}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
else if (this.props.type === 'enum') {
|
else if (this.props.type === "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)]);
|
||||||
input = <InputEnum
|
input = <InputEnum
|
||||||
options={options}
|
options={options}
|
||||||
value={v as string}
|
value={v as string}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
aria-label={this.props['aria-label'] || this.props.label}
|
aria-label={this.props["aria-label"] || this.props.label}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
else if (this.props.type === 'color') {
|
else if (this.props.type === "color") {
|
||||||
input = <InputColor
|
input = <InputColor
|
||||||
value={v as string}
|
value={v as string}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
aria-label={this.props['aria-label'] || this.props.label}
|
aria-label={this.props["aria-label"] || this.props.label}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
input = <InputString
|
input = <InputString
|
||||||
value={v as string}
|
value={v as string}
|
||||||
onChange={this.changeValue.bind(this, i)}
|
onChange={this.changeValue.bind(this, i)}
|
||||||
aria-label={this.props['aria-label'] || this.props.label}
|
aria-label={this.props["aria-label"] || this.props.label}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
@@ -124,8 +124,8 @@ class InputDynamicArrayInternal extends React.Component<InputDynamicArrayInterna
|
|||||||
<div className="maputnik-array-block-content">
|
<div className="maputnik-array-block-content">
|
||||||
{input}
|
{input}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="maputnik-array">
|
<div className="maputnik-array">
|
||||||
@@ -159,6 +159,6 @@ class DeleteValueInputButton extends React.Component<DeleteValueInputButtonProps
|
|||||||
<FieldDocLabel
|
<FieldDocLabel
|
||||||
label={<MdDelete />}
|
label={<MdDelete />}
|
||||||
/>
|
/>
|
||||||
</InputButton>
|
</InputButton>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from "./InputSelect";
|
||||||
import InputMultiInput from './InputMultiInput'
|
import InputMultiInput from "./InputMultiInput";
|
||||||
|
|
||||||
|
|
||||||
function optionsLabelLength(options: any[]) {
|
function optionsLabelLength(options: any[]) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
options.forEach(([_, label]) => {
|
options.forEach(([_, label]) => {
|
||||||
sum += label.length
|
sum += label.length;
|
||||||
})
|
});
|
||||||
return sum
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export type InputEnumProps = {
|
|||||||
name?: string
|
name?: string
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
options: any[]
|
options: any[]
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
label?: string
|
label?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,15 +35,15 @@ export default class InputEnum extends React.Component<InputEnumProps> {
|
|||||||
options={options}
|
options={options}
|
||||||
value={(value || this.props.default)!}
|
value={(value || this.props.default)!}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
aria-label={this.props['aria-label'] || label}
|
aria-label={this.props["aria-label"] || label}
|
||||||
/>
|
/>;
|
||||||
} else {
|
} else {
|
||||||
return <InputSelect
|
return <InputSelect
|
||||||
options={options}
|
options={options}
|
||||||
value={(value || this.props.default)!}
|
value={(value || this.props.default)!}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
aria-label={this.props['aria-label'] || label}
|
aria-label={this.props["aria-label"] || label}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from "./InputAutocomplete";
|
||||||
|
|
||||||
export type InputFontProps = {
|
export type InputFontProps = {
|
||||||
name: string
|
name: string
|
||||||
@@ -8,13 +8,13 @@ export type InputFontProps = {
|
|||||||
fonts?: unknown[]
|
fonts?: unknown[]
|
||||||
style?: object
|
style?: object
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class InputFont extends React.Component<InputFontProps> {
|
export default class InputFont extends React.Component<InputFontProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
fonts: []
|
fonts: []
|
||||||
}
|
};
|
||||||
|
|
||||||
get values() {
|
get values() {
|
||||||
const out = this.props.value || this.props.default || [];
|
const out = this.props.value || this.props.default || [];
|
||||||
@@ -29,11 +29,11 @@ export default class InputFont extends React.Component<InputFontProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
changeFont(idx: number, newValue: string) {
|
changeFont(idx: number, newValue: string) {
|
||||||
const changedValues = this.values.slice(0)
|
const changedValues = this.values.slice(0);
|
||||||
changedValues[idx] = newValue
|
changedValues[idx] = newValue;
|
||||||
const filteredValues = changedValues
|
const filteredValues = changedValues
|
||||||
.filter(v => v !== undefined)
|
.filter(v => v !== undefined)
|
||||||
.filter(v => v !== "")
|
.filter(v => v !== "");
|
||||||
|
|
||||||
this.props.onChange(filteredValues);
|
this.props.onChange(filteredValues);
|
||||||
}
|
}
|
||||||
@@ -44,13 +44,13 @@ export default class InputFont extends React.Component<InputFontProps> {
|
|||||||
key={i}
|
key={i}
|
||||||
>
|
>
|
||||||
<InputAutocomplete
|
<InputAutocomplete
|
||||||
aria-label={this.props['aria-label'] || this.props.name}
|
aria-label={this.props["aria-label"] || this.props.name}
|
||||||
value={value}
|
value={value}
|
||||||
options={this.props.fonts?.map(f => [f, f])}
|
options={this.props.fonts?.map(f => [f, f])}
|
||||||
onChange={this.changeFont.bind(this, i)}
|
onChange={this.changeFont.bind(this, i)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>;
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="maputnik-font">
|
<ul className="maputnik-font">
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import classnames from 'classnames';
|
import classnames from "classnames";
|
||||||
import CodeMirror, { ModeSpec } from 'codemirror';
|
import CodeMirror, { type ModeSpec } from "codemirror";
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import 'codemirror/mode/javascript/javascript'
|
import "codemirror/mode/javascript/javascript";
|
||||||
import 'codemirror/addon/lint/lint'
|
import "codemirror/addon/lint/lint";
|
||||||
import 'codemirror/addon/edit/matchbrackets'
|
import "codemirror/addon/edit/matchbrackets";
|
||||||
import 'codemirror/lib/codemirror.css'
|
import "codemirror/lib/codemirror.css";
|
||||||
import 'codemirror/addon/lint/lint.css'
|
import "codemirror/addon/lint/lint.css";
|
||||||
import stringifyPretty from 'json-stringify-pretty-compact'
|
import stringifyPretty from "json-stringify-pretty-compact";
|
||||||
import '../libs/codemirror-mgl';
|
import "../libs/codemirror-mgl";
|
||||||
import type { LayerSpecification } from 'maplibre-gl';
|
import type { LayerSpecification } from "maplibre-gl";
|
||||||
|
|
||||||
|
|
||||||
export type InputJsonProps = {
|
export type InputJsonProps = {
|
||||||
@@ -49,7 +49,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
onJSONInvalid: () => {},
|
onJSONInvalid: () => {},
|
||||||
onJSONValid: () => {},
|
onJSONValid: () => {},
|
||||||
}
|
};
|
||||||
_keyEvent: string;
|
_keyEvent: string;
|
||||||
_doc: CodeMirror.Editor | undefined;
|
_doc: CodeMirror.Editor | undefined;
|
||||||
_el: HTMLDivElement | null = null;
|
_el: HTMLDivElement | null = null;
|
||||||
@@ -73,7 +73,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
},
|
},
|
||||||
lineWrapping: this.props.lineWrapping,
|
lineWrapping: this.props.lineWrapping,
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
theme: 'maputnik',
|
theme: "maputnik",
|
||||||
viewportMargin: Infinity,
|
viewportMargin: Infinity,
|
||||||
lineNumbers: this.props.lineNumbers,
|
lineNumbers: this.props.lineNumbers,
|
||||||
lint: this.props.lint || {
|
lint: this.props.lint || {
|
||||||
@@ -84,14 +84,14 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
scrollbarStyle: "null",
|
scrollbarStyle: "null",
|
||||||
});
|
});
|
||||||
|
|
||||||
this._doc.on('change', this.onChange);
|
this._doc.on("change", this.onChange);
|
||||||
this._doc.on('focus', this.onFocus);
|
this._doc.on("focus", this.onFocus);
|
||||||
this._doc.on('blur', this.onBlur);
|
this._doc.on("blur", this.onBlur);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPointerDown = () => {
|
onPointerDown = () => {
|
||||||
this._keyEvent = "pointer";
|
this._keyEvent = "pointer";
|
||||||
}
|
};
|
||||||
|
|
||||||
onFocus = () => {
|
onFocus = () => {
|
||||||
if (this.props.onFocus) this.props.onFocus();
|
if (this.props.onFocus) this.props.onFocus();
|
||||||
@@ -99,7 +99,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
isEditing: true,
|
isEditing: true,
|
||||||
showMessage: (this._keyEvent === "keyboard"),
|
showMessage: (this._keyEvent === "keyboard"),
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onBlur = () => {
|
onBlur = () => {
|
||||||
this._keyEvent = "keyboard";
|
this._keyEvent = "keyboard";
|
||||||
@@ -108,12 +108,12 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
isEditing: false,
|
isEditing: false,
|
||||||
showMessage: false,
|
showMessage: false,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
componentWillUnMount () {
|
componentWillUnMount () {
|
||||||
this._doc!.off('change', this.onChange);
|
this._doc!.off("change", this.onChange);
|
||||||
this._doc!.off('focus', this.onFocus);
|
this._doc!.off("focus", this.onFocus);
|
||||||
this._doc!.off('blur', this.onBlur);
|
this._doc!.off("blur", this.onBlur);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: InputJsonProps) {
|
componentDidUpdate(prevProps: InputJsonProps) {
|
||||||
@@ -121,7 +121,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
this._cancelNextChange = true;
|
this._cancelNextChange = true;
|
||||||
this._doc!.setValue(
|
this._doc!.setValue(
|
||||||
this.props.getValue!(this.props.layer),
|
this.props.getValue!(this.props.layer),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
this._cancelNextChange = false;
|
this._cancelNextChange = false;
|
||||||
this.setState({
|
this.setState({
|
||||||
prevValue: this._doc!.getValue(),
|
prevValue: this._doc!.getValue(),
|
||||||
})
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newCode = this._doc!.getValue();
|
const newCode = this._doc!.getValue();
|
||||||
@@ -141,14 +141,14 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
parsedLayer = JSON.parse(newCode);
|
parsedLayer = JSON.parse(newCode);
|
||||||
} catch(_err) {
|
} catch(_err) {
|
||||||
err = _err;
|
err = _err;
|
||||||
console.warn(_err)
|
console.warn(_err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err && this.props.onJSONInvalid) {
|
if (err && this.props.onJSONInvalid) {
|
||||||
this.props.onJSONInvalid();
|
this.props.onJSONInvalid();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this.props.onChange) this.props.onChange(parsedLayer)
|
if (this.props.onChange) this.props.onChange(parsedLayer);
|
||||||
if (this.props.onJSONValid) this.props.onJSONValid();
|
if (this.props.onJSONValid) this.props.onJSONValid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
this.setState({
|
this.setState({
|
||||||
prevValue: newCode,
|
prevValue: newCode,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
@@ -174,10 +174,10 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classnames("codemirror-container", this.props.className)}
|
className={classnames("codemirror-container", this.props.className)}
|
||||||
ref={(el) => {this._el = el}}
|
ref={(el) => {this._el = el;}}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames";
|
||||||
|
|
||||||
export type InputMultiInputProps = {
|
export type InputMultiInputProps = {
|
||||||
name?: string
|
name?: string
|
||||||
value: string
|
value: string
|
||||||
options: any[]
|
options: any[]
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class InputMultiInput extends React.Component<InputMultiInputProps> {
|
export default class InputMultiInput extends React.Component<InputMultiInputProps> {
|
||||||
render() {
|
render() {
|
||||||
let options = this.props.options
|
let options = this.props.options;
|
||||||
if(options.length > 0 && !Array.isArray(options[0])) {
|
if(options.length > 0 && !Array.isArray(options[0])) {
|
||||||
options = options.map(v => [v, v])
|
options = options.map(v => [v, v]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedValue = this.props.value || options[0][0]
|
const selectedValue = this.props.value || options[0][0];
|
||||||
const radios = options.map(([val, label])=> {
|
const radios = options.map(([val, label])=> {
|
||||||
return <label
|
return <label
|
||||||
key={val}
|
key={val}
|
||||||
@@ -29,11 +29,11 @@ export default class InputMultiInput extends React.Component<InputMultiInputProp
|
|||||||
checked={val === selectedValue}
|
checked={val === selectedValue}
|
||||||
/>
|
/>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>;
|
||||||
})
|
});
|
||||||
|
|
||||||
return <fieldset className="maputnik-multibutton" aria-label={this.props['aria-label']}>
|
return <fieldset className="maputnik-multibutton" aria-label={this.props["aria-label"]}>
|
||||||
{radios}
|
{radios}
|
||||||
</fieldset>
|
</fieldset>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { BaseSyntheticEvent } from 'react'
|
import React, { type BaseSyntheticEvent } from "react";
|
||||||
import generateUniqueId from '../libs/document-uid';
|
import generateUniqueId from "../libs/document-uid";
|
||||||
|
|
||||||
export type InputNumberProps = {
|
export type InputNumberProps = {
|
||||||
value?: number
|
value?: number
|
||||||
@@ -23,22 +23,22 @@ type InputNumberState = {
|
|||||||
* This is the value that is currently being edited. It can be an invalid value.
|
* This is the value that is currently being edited. It can be an invalid value.
|
||||||
*/
|
*/
|
||||||
dirtyValue?: number | string | undefined
|
dirtyValue?: number | string | undefined
|
||||||
}
|
};
|
||||||
|
|
||||||
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
|
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
rangeStep: 1
|
rangeStep: 1
|
||||||
}
|
};
|
||||||
_keyboardEvent: boolean = false;
|
_keyboardEvent: boolean = false;
|
||||||
|
|
||||||
constructor(props: InputNumberProps) {
|
constructor(props: InputNumberProps) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
uuid: +generateUniqueId(),
|
uuid: +generateUniqueId(),
|
||||||
editing: false,
|
editing: false,
|
||||||
value: props.value,
|
value: props.value,
|
||||||
dirtyValue: props.value,
|
dirtyValue: props.value,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<InputNumberProps>, state: InputNumberState) {
|
static getDerivedStateFromProps(props: Readonly<InputNumberProps>, state: InputNumberState) {
|
||||||
@@ -57,7 +57,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
|
|
||||||
const hasChanged = this.props.value !== value;
|
const hasChanged = this.props.value !== value;
|
||||||
if(this.isValid(value) && hasChanged) {
|
if(this.isValid(value) && hasChanged) {
|
||||||
if (this.props.onChange) this.props.onChange(value)
|
if (this.props.onChange) this.props.onChange(value);
|
||||||
this.setState({
|
this.setState({
|
||||||
value: value,
|
value: value,
|
||||||
});
|
});
|
||||||
@@ -70,7 +70,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
dirtyValue: newValue === "" ? undefined : newValue,
|
dirtyValue: newValue === "" ? undefined : newValue,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid(v: number | string | undefined) {
|
isValid(v: number | string | undefined) {
|
||||||
@@ -80,18 +80,18 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
|
|
||||||
const value = +v;
|
const value = +v;
|
||||||
if(isNaN(value)) {
|
if(isNaN(value)) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isNaN(this.props.min!) && value < this.props.min!) {
|
if(!isNaN(this.props.min!) && value < this.props.min!) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isNaN(this.props.max!) && value > this.props.max!) {
|
if(!isNaN(this.props.max!) && value > this.props.max!) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetValue = () => {
|
resetValue = () => {
|
||||||
@@ -104,14 +104,14 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
// If set value is invalid fall back to the last valid value from props or at last resort the default value
|
// If set value is invalid fall back to the last valid value from props or at last resort the default value
|
||||||
if (!this.isValid(this.state.value)) {
|
if (!this.isValid(this.state.value)) {
|
||||||
if(this.isValid(this.props.value)) {
|
if(this.isValid(this.props.value)) {
|
||||||
this.changeValue(this.props.value)
|
this.changeValue(this.props.value);
|
||||||
this.setState({dirtyValue: this.props.value});
|
this.setState({dirtyValue: this.props.value});
|
||||||
} else {
|
} else {
|
||||||
this.changeValue(undefined);
|
this.changeValue(undefined);
|
||||||
this.setState({dirtyValue: undefined});
|
this.setState({dirtyValue: undefined});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onChangeRange = (e: BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => {
|
onChangeRange = (e: BaseSyntheticEvent<Event, HTMLInputElement, HTMLInputElement>) => {
|
||||||
let value = parseFloat(e.target.value);
|
let value = parseFloat(e.target.value);
|
||||||
@@ -132,7 +132,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
value = this.state.value! - step;
|
value = this.state.value! - step;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
value = this.state.value! + step
|
value = this.state.value! + step;
|
||||||
}
|
}
|
||||||
dirtyValue = value;
|
dirtyValue = value;
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
|
|
||||||
this.setState({value, dirtyValue});
|
this.setState({value, dirtyValue});
|
||||||
if (this.props.onChange) this.props.onChange(value);
|
if (this.props.onChange) this.props.onChange(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(
|
if(
|
||||||
@@ -217,18 +217,18 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
}}
|
}}
|
||||||
onBlur={_e => {
|
onBlur={_e => {
|
||||||
this.setState({editing: false});
|
this.setState({editing: false});
|
||||||
this.resetValue()
|
this.resetValue();
|
||||||
}}
|
}}
|
||||||
data-wd-key={this.props["data-wd-key"] + "-text"}
|
data-wd-key={this.props["data-wd-key"] + "-text"}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const value = this.state.editing ? this.state.dirtyValue : this.state.value;
|
const value = this.state.editing ? this.state.dirtyValue : this.state.value;
|
||||||
|
|
||||||
return <input
|
return <input
|
||||||
aria-label={this.props['aria-label']}
|
aria-label={this.props["aria-label"]}
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="maputnik-number"
|
className="maputnik-number"
|
||||||
placeholder={this.props.default?.toString()}
|
placeholder={this.props.default?.toString()}
|
||||||
@@ -240,7 +240,7 @@ export default class InputNumber extends React.Component<InputNumberProps, Input
|
|||||||
onBlur={this.resetValue}
|
onBlur={this.resetValue}
|
||||||
required={this.props.required}
|
required={this.props.required}
|
||||||
data-wd-key={this.props["data-wd-key"]}
|
data-wd-key={this.props["data-wd-key"]}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
export type InputSelectProps = {
|
export type InputSelectProps = {
|
||||||
value: string
|
value: string
|
||||||
@@ -7,7 +7,7 @@ export type InputSelectProps = {
|
|||||||
style?: object
|
style?: object
|
||||||
onChange(value: string | [string, any]): unknown
|
onChange(value: string | [string, any]): unknown
|
||||||
title?: string
|
title?: string
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class InputSelect extends React.Component<InputSelectProps> {
|
export default class InputSelect extends React.Component<InputSelectProps> {
|
||||||
@@ -24,9 +24,9 @@ export default class InputSelect extends React.Component<InputSelectProps> {
|
|||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onChange={e => this.props.onChange(e.target.value)}
|
onChange={e => this.props.onChange(e.target.value)}
|
||||||
aria-label={this.props['aria-label']}
|
aria-label={this.props["aria-label"]}
|
||||||
>
|
>
|
||||||
{ options.map(([val, label]) => <option key={val} value={val}>{label}</option>) }
|
{ options.map(([val, label]) => <option key={val} value={val}>{label}</option>) }
|
||||||
</select>
|
</select>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { type ReactElement } from "react";
|
||||||
|
|
||||||
import InputColor, { InputColorProps } from './InputColor'
|
import InputColor, { type InputColorProps } from "./InputColor";
|
||||||
import InputNumber, { InputNumberProps } from './InputNumber'
|
import InputNumber, { type InputNumberProps } from "./InputNumber";
|
||||||
import InputCheckbox, { InputCheckboxProps } from './InputCheckbox'
|
import InputCheckbox, { type InputCheckboxProps } from "./InputCheckbox";
|
||||||
import InputString, { InputStringProps } from './InputString'
|
import InputString, { type InputStringProps } from "./InputString";
|
||||||
import InputArray, { InputArrayProps } from './InputArray'
|
import InputArray, { type InputArrayProps } from "./InputArray";
|
||||||
import InputDynamicArray, { InputDynamicArrayProps } from './InputDynamicArray'
|
import InputDynamicArray, { type InputDynamicArrayProps } from "./InputDynamicArray";
|
||||||
import InputFont, { InputFontProps } from './InputFont'
|
import InputFont, { type InputFontProps } from "./InputFont";
|
||||||
import InputAutocomplete, { InputAutocompleteProps } from './InputAutocomplete'
|
import InputAutocomplete, { type InputAutocompleteProps } from "./InputAutocomplete";
|
||||||
import InputEnum, { InputEnumProps } from './InputEnum'
|
import InputEnum, { type InputEnumProps } from "./InputEnum";
|
||||||
import capitalize from 'lodash.capitalize'
|
import capitalize from "lodash.capitalize";
|
||||||
|
|
||||||
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
const iconProperties = ["background-pattern", "fill-pattern", "line-pattern", "fill-extrusion-pattern", "icon-image"];
|
||||||
|
|
||||||
export type FieldSpecType = 'number' | 'enum' | 'resolvedImage' | 'formatted' | 'string' | 'color' | 'boolean' | 'array' | 'numberArray' | 'padding' | 'colorArray' | 'variableAnchorOffsetCollection';
|
export type FieldSpecType = "number" | "enum" | "resolvedImage" | "formatted" | "string" | "color" | "boolean" | "array" | "numberArray" | "padding" | "colorArray" | "variableAnchorOffsetCollection";
|
||||||
|
|
||||||
export type InputSpecProps = {
|
export type InputSpecProps = {
|
||||||
onChange?(fieldName: string | undefined, value: number | undefined | (string | number | undefined)[]): unknown
|
onChange?(fieldName: string | undefined, value: number | undefined | (string | number | undefined)[]): unknown
|
||||||
@@ -30,7 +30,7 @@ export type InputSpecProps = {
|
|||||||
value?: string | number | unknown[] | boolean
|
value?: string | number | unknown[] | boolean
|
||||||
/** Override the style of the field */
|
/** Override the style of the field */
|
||||||
style?: object
|
style?: object
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
error?: unknown[]
|
error?: unknown[]
|
||||||
label?: string
|
label?: string
|
||||||
action?: ReactElement
|
action?: ReactElement
|
||||||
@@ -53,96 +53,96 @@ export default class InputSpec extends React.Component<InputSpecProps> {
|
|||||||
name: this.props.fieldName,
|
name: this.props.fieldName,
|
||||||
"data-wd-key": "spec-field-input:" + this.props.fieldName,
|
"data-wd-key": "spec-field-input:" + this.props.fieldName,
|
||||||
onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
|
onChange: (newValue: number | undefined | (string | number | undefined)[]) => this.props.onChange!(this.props.fieldName, newValue),
|
||||||
'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])}
|
|
||||||
/>
|
|
||||||
} else {
|
|
||||||
return <InputString
|
|
||||||
{...commonProps as InputStringProps}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
case 'color': return (
|
case "resolvedImage":
|
||||||
<InputColor
|
case "formatted":
|
||||||
{...commonProps as InputColorProps}
|
case "string":
|
||||||
/>
|
if (iconProperties.indexOf(this.props.fieldName!) >= 0) {
|
||||||
)
|
const options = this.props.fieldSpec.values || [];
|
||||||
case 'boolean': return (
|
return <InputAutocomplete
|
||||||
<InputCheckbox
|
{...commonProps as Omit<InputAutocompleteProps, "options">}
|
||||||
{...commonProps as InputCheckboxProps}
|
options={options.map(f => [f, f])}
|
||||||
/>
|
/>;
|
||||||
)
|
|
||||||
case 'array':
|
|
||||||
if(this.props.fieldName === 'text-font') {
|
|
||||||
return <InputFont
|
|
||||||
{...commonProps as InputFontProps}
|
|
||||||
fonts={this.props.fieldSpec.values}
|
|
||||||
/>
|
|
||||||
} else {
|
|
||||||
if (this.props.fieldSpec.length) {
|
|
||||||
return <InputArray
|
|
||||||
{...commonProps as InputArrayProps}
|
|
||||||
type={this.props.fieldSpec.value}
|
|
||||||
length={this.props.fieldSpec.length}
|
|
||||||
/>
|
|
||||||
} else {
|
} else {
|
||||||
return <InputDynamicArray
|
return <InputString
|
||||||
{...commonProps as InputDynamicArrayProps}
|
{...commonProps as InputStringProps}
|
||||||
fieldSpec={this.props.fieldSpec}
|
/>;
|
||||||
type={this.props.fieldSpec.value as InputDynamicArrayProps['type']}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
}
|
case "color": return (
|
||||||
case 'numberArray': return (
|
<InputColor
|
||||||
<InputDynamicArray
|
{...commonProps as InputColorProps}
|
||||||
{...commonProps as InputDynamicArrayProps}
|
/>
|
||||||
fieldSpec={this.props.fieldSpec}
|
);
|
||||||
type="number"
|
case "boolean": return (
|
||||||
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
|
<InputCheckbox
|
||||||
/>
|
{...commonProps as InputCheckboxProps}
|
||||||
)
|
/>
|
||||||
case 'colorArray': return (
|
);
|
||||||
<InputDynamicArray
|
case "array":
|
||||||
{...commonProps as InputDynamicArrayProps}
|
if(this.props.fieldName === "text-font") {
|
||||||
fieldSpec={this.props.fieldSpec}
|
return <InputFont
|
||||||
type="color"
|
{...commonProps as InputFontProps}
|
||||||
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
|
fonts={this.props.fieldSpec.values}
|
||||||
/>
|
/>;
|
||||||
)
|
} else {
|
||||||
case 'padding': return (
|
if (this.props.fieldSpec.length) {
|
||||||
<InputArray
|
return <InputArray
|
||||||
{...commonProps as InputArrayProps}
|
{...commonProps as InputArrayProps}
|
||||||
type="number"
|
type={this.props.fieldSpec.value}
|
||||||
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
|
length={this.props.fieldSpec.length}
|
||||||
length={4}
|
/>;
|
||||||
/>
|
} else {
|
||||||
)
|
return <InputDynamicArray
|
||||||
default:
|
{...commonProps as InputDynamicArrayProps}
|
||||||
console.warn(`No proper field input for ${this.props.fieldName} type: ${this.props.fieldSpec?.type}`);
|
fieldSpec={this.props.fieldSpec}
|
||||||
return null
|
type={this.props.fieldSpec.value as InputDynamicArrayProps["type"]}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "numberArray": return (
|
||||||
|
<InputDynamicArray
|
||||||
|
{...commonProps as InputDynamicArrayProps}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
type="number"
|
||||||
|
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "colorArray": return (
|
||||||
|
<InputDynamicArray
|
||||||
|
{...commonProps as InputDynamicArrayProps}
|
||||||
|
fieldSpec={this.props.fieldSpec}
|
||||||
|
type="color"
|
||||||
|
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "padding": return (
|
||||||
|
<InputArray
|
||||||
|
{...commonProps as InputArrayProps}
|
||||||
|
type="number"
|
||||||
|
value={(Array.isArray(this.props.value) ? this.props.value : [this.props.value]) as (string | number | undefined)[]}
|
||||||
|
length={4}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
console.warn(`No proper field input for ${this.props.fieldName} type: ${this.props.fieldSpec?.type}`);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
export type InputStringProps = {
|
export type InputStringProps = {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
@@ -11,26 +11,26 @@ export type InputStringProps = {
|
|||||||
required?: boolean
|
required?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
spellCheck?: boolean
|
spellCheck?: boolean
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
title?: string
|
title?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
type InputStringState = {
|
type InputStringState = {
|
||||||
editing: boolean
|
editing: boolean
|
||||||
value?: string
|
value?: string
|
||||||
}
|
};
|
||||||
|
|
||||||
export default class InputString extends React.Component<InputStringProps, InputStringState> {
|
export default class InputString extends React.Component<InputStringProps, InputStringState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onInput: () => {},
|
onInput: () => {},
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props: InputStringProps) {
|
constructor(props: InputStringProps) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
editing: false,
|
editing: false,
|
||||||
value: props.value || ''
|
value: props.value || ""
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<InputStringProps>, state: InputStringState) {
|
static getDerivedStateFromProps(props: Readonly<InputStringProps>, state: InputStringState) {
|
||||||
@@ -47,17 +47,17 @@ export default class InputString extends React.Component<InputStringProps, Input
|
|||||||
let classes;
|
let classes;
|
||||||
|
|
||||||
if(this.props.multi) {
|
if(this.props.multi) {
|
||||||
tag = "textarea"
|
tag = "textarea";
|
||||||
classes = [
|
classes = [
|
||||||
"maputnik-string",
|
"maputnik-string",
|
||||||
"maputnik-string--multi"
|
"maputnik-string--multi"
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tag = "input"
|
tag = "input";
|
||||||
classes = [
|
classes = [
|
||||||
"maputnik-string"
|
"maputnik-string"
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.disabled) {
|
if(this.props.disabled) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { JSX } from 'react'
|
import React, { type JSX } from "react";
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
import SmallError from './SmallError'
|
import SmallError from "./SmallError";
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { TFunction } from 'i18next';
|
import { type TFunction } from "i18next";
|
||||||
|
|
||||||
function validate(url: string, t: TFunction): JSX.Element | undefined {
|
function validate(url: string, t: TFunction): JSX.Element | undefined {
|
||||||
if (url === "") {
|
if (url === "") {
|
||||||
@@ -63,7 +63,7 @@ export type FieldUrlProps = {
|
|||||||
onInput?(...args: unknown[]): unknown
|
onInput?(...args: unknown[]): unknown
|
||||||
multi?: boolean
|
multi?: boolean
|
||||||
required?: boolean
|
required?: boolean
|
||||||
'aria-label'?: string
|
"aria-label"?: string
|
||||||
type?: string
|
type?: string
|
||||||
className?: string
|
className?: string
|
||||||
};
|
};
|
||||||
@@ -72,12 +72,12 @@ type InputUrlInternalProps = FieldUrlProps & WithTranslation;
|
|||||||
|
|
||||||
type InputUrlState = {
|
type InputUrlState = {
|
||||||
error?: React.ReactNode
|
error?: React.ReactNode
|
||||||
}
|
};
|
||||||
|
|
||||||
class InputUrlInternal extends React.Component<InputUrlInternalProps, InputUrlState> {
|
class InputUrlInternal extends React.Component<InputUrlInternalProps, InputUrlState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onInput: () => {},
|
onInput: () => {},
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor (props: InputUrlInternalProps) {
|
constructor (props: InputUrlInternalProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -91,14 +91,14 @@ class InputUrlInternal extends React.Component<InputUrlInternalProps, InputUrlSt
|
|||||||
error: validate(url, this.props.t),
|
error: validate(url, this.props.t),
|
||||||
});
|
});
|
||||||
if (this.props.onInput) this.props.onInput(url);
|
if (this.props.onInput) this.props.onInput(url);
|
||||||
}
|
};
|
||||||
|
|
||||||
onChange = (url: string) => {
|
onChange = (url: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: validate(url, this.props.t),
|
error: validate(url, this.props.t),
|
||||||
});
|
});
|
||||||
this.props.onChange(url);
|
this.props.onChange(url);
|
||||||
}
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
@@ -107,7 +107,7 @@ class InputUrlInternal extends React.Component<InputUrlInternalProps, InputUrlSt
|
|||||||
{...this.props}
|
{...this.props}
|
||||||
onInput={this.onInput}
|
onInput={this.onInput}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
aria-label={this.props['aria-label']}
|
aria-label={this.props["aria-label"]}
|
||||||
/>
|
/>
|
||||||
{this.state.error}
|
{this.state.error}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import React, {type JSX} from 'react'
|
import React, {type JSX} from "react";
|
||||||
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";
|
||||||
import {MdMoreVert} from 'react-icons/md'
|
import {MdMoreVert} from "react-icons/md";
|
||||||
import { IconContext } from 'react-icons'
|
import { IconContext } from "react-icons";
|
||||||
import {BackgroundLayerSpecification, LayerSpecification, SourceSpecification} from 'maplibre-gl';
|
import {type BackgroundLayerSpecification, type LayerSpecification, type SourceSpecification} from "maplibre-gl";
|
||||||
import {v8} from '@maplibre/maplibre-gl-style-spec';
|
import {v8} from "@maplibre/maplibre-gl-style-spec";
|
||||||
|
|
||||||
import FieldJson from './FieldJson'
|
import FieldJson from "./FieldJson";
|
||||||
import FilterEditor from './FilterEditor'
|
import FilterEditor from "./FilterEditor";
|
||||||
import PropertyGroup from './PropertyGroup'
|
import PropertyGroup from "./PropertyGroup";
|
||||||
import LayerEditorGroup from './LayerEditorGroup'
|
import LayerEditorGroup from "./LayerEditorGroup";
|
||||||
import FieldType from './FieldType'
|
import FieldType from "./FieldType";
|
||||||
import FieldId from './FieldId'
|
import FieldId from "./FieldId";
|
||||||
import FieldMinZoom from './FieldMinZoom'
|
import FieldMinZoom from "./FieldMinZoom";
|
||||||
import FieldMaxZoom from './FieldMaxZoom'
|
import FieldMaxZoom from "./FieldMaxZoom";
|
||||||
import FieldComment from './FieldComment'
|
import FieldComment from "./FieldComment";
|
||||||
import FieldSource from './FieldSource'
|
import FieldSource from "./FieldSource";
|
||||||
import FieldSourceLayer from './FieldSourceLayer'
|
import FieldSourceLayer from "./FieldSourceLayer";
|
||||||
import { changeType, changeProperty } from '../libs/layer'
|
import { changeType, changeProperty } from "../libs/layer";
|
||||||
import {formatLayerId} from '../libs/format';
|
import {formatLayerId} from "../libs/format";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { TFunction } from 'i18next';
|
import { type TFunction } from "i18next";
|
||||||
import { NON_SOURCE_LAYERS } from '../libs/non-source-layers';
|
import { NON_SOURCE_LAYERS } from "../libs/non-source-layers";
|
||||||
import { OnMoveLayerCallback } from '../libs/definitions';
|
import { type OnMoveLayerCallback } from "../libs/definitions";
|
||||||
|
|
||||||
type MaputnikLayoutGroup = {
|
type MaputnikLayoutGroup = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
type: string;
|
type: string;
|
||||||
fields: string[];
|
fields: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
function getLayoutForSymbolType(t: TFunction): MaputnikLayoutGroup[] {
|
function getLayoutForSymbolType(t: TFunction): MaputnikLayoutGroup[] {
|
||||||
const groups: MaputnikLayoutGroup[] = [];
|
const groups: MaputnikLayoutGroup[] = [];
|
||||||
@@ -68,7 +68,7 @@ function getLayoutForSymbolType(t: TFunction): MaputnikLayoutGroup[] {
|
|||||||
|
|
||||||
function getLayoutForType(type: LayerSpecification["type"], t: TFunction): MaputnikLayoutGroup[] {
|
function getLayoutForType(type: LayerSpecification["type"], t: TFunction): MaputnikLayoutGroup[] {
|
||||||
if (Object.keys(v8.layer.type.values).indexOf(type) < 0) {
|
if (Object.keys(v8.layer.type.values).indexOf(type) < 0) {
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
if (type === "symbol") {
|
if (type === "symbol") {
|
||||||
return getLayoutForSymbolType(t);
|
return getLayoutForSymbolType(t);
|
||||||
@@ -95,23 +95,23 @@ function getLayoutForType(type: LayerSpecification["type"], t: TFunction): Maput
|
|||||||
|
|
||||||
function layoutGroups(layerType: LayerSpecification["type"], t: TFunction): {id: string, title: string, type: string, fields?: string[]}[] {
|
function layoutGroups(layerType: LayerSpecification["type"], t: TFunction): {id: string, title: string, type: string, fields?: string[]}[] {
|
||||||
const layerGroup = {
|
const layerGroup = {
|
||||||
id: 'layer',
|
id: "layer",
|
||||||
title: t('Layer'),
|
title: t("Layer"),
|
||||||
type: 'layer'
|
type: "layer"
|
||||||
}
|
};
|
||||||
const filterGroup = {
|
const filterGroup = {
|
||||||
id: 'filter',
|
id: "filter",
|
||||||
title: t('Filter'),
|
title: t("Filter"),
|
||||||
type: 'filter'
|
type: "filter"
|
||||||
}
|
};
|
||||||
const editorGroup = {
|
const editorGroup = {
|
||||||
id: 'jsoneditor',
|
id: "jsoneditor",
|
||||||
title: t('JSON Editor'),
|
title: t("JSON Editor"),
|
||||||
type: 'jsoneditor'
|
type: "jsoneditor"
|
||||||
}
|
};
|
||||||
return [layerGroup, filterGroup]
|
return [layerGroup, filterGroup]
|
||||||
.concat(getLayoutForType(layerType, t))
|
.concat(getLayoutForType(layerType, t))
|
||||||
.concat([editorGroup])
|
.concat([editorGroup]);
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayerEditorInternalProps = {
|
type LayerEditorInternalProps = {
|
||||||
@@ -141,25 +141,25 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
onLayerChanged: () => {},
|
onLayerChanged: () => {},
|
||||||
onLayerIdChange: () => {},
|
onLayerIdChange: () => {},
|
||||||
onLayerDestroyed: () => {},
|
onLayerDestroyed: () => {},
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props: LayerEditorInternalProps) {
|
constructor(props: LayerEditorInternalProps) {
|
||||||
super(props)
|
super(props);
|
||||||
|
|
||||||
const editorGroups: {[keys:string]: boolean} = {}
|
const editorGroups: {[keys:string]: boolean} = {};
|
||||||
for (const group of layoutGroups(this.props.layer.type, props.t)) {
|
for (const group of layoutGroups(this.props.layer.type, props.t)) {
|
||||||
editorGroups[group.title] = true
|
editorGroups[group.title] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = { editorGroups }
|
this.state = { editorGroups };
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: Readonly<LayerEditorInternalProps>, state: LayerEditorState) {
|
static getDerivedStateFromProps(props: Readonly<LayerEditorInternalProps>, state: LayerEditorState) {
|
||||||
const additionalGroups = { ...state.editorGroups }
|
const additionalGroups = { ...state.editorGroups };
|
||||||
|
|
||||||
for (const group of getLayoutForType(props.layer.type, props.t)) {
|
for (const group of getLayoutForType(props.layer.type, props.t)) {
|
||||||
if(!(group.title in additionalGroups)) {
|
if(!(group.title in additionalGroups)) {
|
||||||
additionalGroups[group.title] = true
|
additionalGroups[group.title] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,23 +173,23 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
this.props.onLayerChanged(
|
this.props.onLayerChanged(
|
||||||
this.props.layerIndex,
|
this.props.layerIndex,
|
||||||
changeProperty(this.props.layer, group, property, newValue)
|
changeProperty(this.props.layer, group, property, newValue)
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onGroupToggle(groupTitle: string, active: boolean) {
|
onGroupToggle(groupTitle: string, active: boolean) {
|
||||||
const changedActiveGroups = {
|
const changedActiveGroups = {
|
||||||
...this.state.editorGroups,
|
...this.state.editorGroups,
|
||||||
[groupTitle]: active,
|
[groupTitle]: active,
|
||||||
}
|
};
|
||||||
this.setState({
|
this.setState({
|
||||||
editorGroups: changedActiveGroups
|
editorGroups: changedActiveGroups
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGroupType(type: string, fields?: string[]): JSX.Element {
|
renderGroupType(type: string, fields?: string[]): JSX.Element {
|
||||||
let comment = ""
|
let comment = "";
|
||||||
if(this.props.layer.metadata) {
|
if(this.props.layer.metadata) {
|
||||||
comment = (this.props.layer.metadata as any)['maputnik:comment']
|
comment = (this.props.layer.metadata as any)["maputnik:comment"];
|
||||||
}
|
}
|
||||||
const {errors, layerIndex} = this.props;
|
const {errors, layerIndex} = this.props;
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
message: error.parsed.data.message
|
message: error.parsed.data.message
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
let sourceLayerIds;
|
let sourceLayerIds;
|
||||||
const layer = this.props.layer as Exclude<LayerSpecification, BackgroundLayerSpecification>;
|
const layer = this.props.layer as Exclude<LayerSpecification, BackgroundLayerSpecification>;
|
||||||
@@ -213,82 +213,82 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
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)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{!NON_SOURCE_LAYERS.includes(this.props.layer.type) &&
|
|
||||||
<FieldSourceLayer
|
|
||||||
error={errorData['source-layer']}
|
|
||||||
sourceLayerIds={sourceLayerIds}
|
|
||||||
value={(this.props.layer as any)['source-layer']}
|
|
||||||
onChange={v => this.changeProperty(null, 'source-layer', v)}
|
|
||||||
/>
|
/>
|
||||||
}
|
<FieldType
|
||||||
<FieldMinZoom
|
disabled={true}
|
||||||
error={errorData.minzoom}
|
error={errorData.type}
|
||||||
value={this.props.layer.minzoom}
|
value={this.props.layer.type}
|
||||||
onChange={v => this.changeProperty(null, 'minzoom', v)}
|
onChange={newType => this.props.onLayerChanged(
|
||||||
/>
|
|
||||||
<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
|
|
||||||
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,
|
this.props.layerIndex,
|
||||||
layer
|
changeType(this.props.layer, newType)
|
||||||
);
|
)}
|
||||||
}}
|
/>
|
||||||
/>
|
{this.props.layer.type !== "background" && <FieldSource
|
||||||
default: return <></>
|
error={errorData.source}
|
||||||
|
sourceIds={Object.keys(this.props.sources!)}
|
||||||
|
value={this.props.layer.source}
|
||||||
|
onChange={v => this.changeProperty(null, "source", v)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{!NON_SOURCE_LAYERS.includes(this.props.layer.type) &&
|
||||||
|
<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
|
||||||
|
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 <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,16 +296,16 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
this.props.onMoveLayer({
|
this.props.onMoveLayer({
|
||||||
oldIndex: this.props.layerIndex,
|
oldIndex: this.props.layerIndex,
|
||||||
newIndex: this.props.layerIndex+offset
|
newIndex: this.props.layerIndex+offset
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
|
|
||||||
const groupIds: string[] = [];
|
const groupIds: string[] = [];
|
||||||
const layerType = this.props.layer.type
|
const layerType = this.props.layer.type;
|
||||||
const groups = layoutGroups(layerType, t).filter(group => {
|
const groups = layoutGroups(layerType, t).filter(group => {
|
||||||
return !(layerType === 'background' && group.type === 'source')
|
return !(layerType === "background" && group.type === "source");
|
||||||
}).map(group => {
|
}).map(group => {
|
||||||
const groupId = group.id;
|
const groupId = group.id;
|
||||||
groupIds.push(groupId);
|
groupIds.push(groupId);
|
||||||
@@ -318,10 +318,10 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
onActiveToggle={this.onGroupToggle.bind(this, group.title)}
|
onActiveToggle={this.onGroupToggle.bind(this, group.title)}
|
||||||
>
|
>
|
||||||
{this.renderGroupType(group.type, group.fields)}
|
{this.renderGroupType(group.type, group.fields)}
|
||||||
</LayerEditorGroup>
|
</LayerEditorGroup>;
|
||||||
})
|
});
|
||||||
|
|
||||||
const layout = this.props.layer.layout || {}
|
const layout = this.props.layer.layout || {};
|
||||||
|
|
||||||
const items: {[key: string]: {
|
const items: {[key: string]: {
|
||||||
text: string,
|
text: string,
|
||||||
@@ -356,14 +356,14 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
handler: () => this.moveLayer(+1),
|
handler: () => this.moveLayer(+1),
|
||||||
wdKey: "menu-move-layer-down"
|
wdKey: "menu-move-layer-down"
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleSelection(id: string, event: React.SyntheticEvent) {
|
function handleSelection(id: string, event: React.SyntheticEvent) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
items[id].handler();
|
items[id].handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
return <IconContext.Provider value={{size: '14px', color: '#8e8e8e'}}>
|
return <IconContext.Provider value={{size: "14px", color: "#8e8e8e"}}>
|
||||||
<section className="maputnik-layer-editor"
|
<section className="maputnik-layer-editor"
|
||||||
role="main"
|
role="main"
|
||||||
aria-label={t("Layer editor")}
|
aria-label={t("Layer editor")}
|
||||||
@@ -395,7 +395,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
<MenuItem value={id} className='more-menu__menu__item' data-wd-key={item.wdKey}>
|
<MenuItem value={id} className='more-menu__menu__item' data-wd-key={item.wdKey}>
|
||||||
{item.text}
|
{item.text}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</li>
|
</li>;
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</Menu>
|
</Menu>
|
||||||
@@ -412,7 +412,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
{groups}
|
{groups}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</section>
|
</section>
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import Icon from '@mdi/react'
|
import Icon from "@mdi/react";
|
||||||
import {
|
import {
|
||||||
mdiMenuDown,
|
mdiMenuDown,
|
||||||
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";
|
||||||
|
|
||||||
|
|
||||||
type LayerEditorGroupProps = {
|
type LayerEditorGroupProps = {
|
||||||
@@ -46,6 +46,6 @@ export default class LayerEditorGroup extends React.Component<LayerEditorGroupPr
|
|||||||
<AccordionItemPanel>
|
<AccordionItemPanel>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</AccordionItemPanel>
|
</AccordionItemPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import React, {type JSX} from 'react'
|
import React, {type JSX} from "react";
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames";
|
||||||
import lodash from 'lodash';
|
import lodash from "lodash";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
PointerSensor,
|
PointerSensor,
|
||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
closestCenter,
|
closestCenter,
|
||||||
DragEndEvent,
|
type DragEndEvent,
|
||||||
} from '@dnd-kit/core';
|
} from "@dnd-kit/core";
|
||||||
import {
|
import {
|
||||||
SortableContext,
|
SortableContext,
|
||||||
verticalListSortingStrategy,
|
verticalListSortingStrategy,
|
||||||
} from '@dnd-kit/sortable';
|
} from "@dnd-kit/sortable";
|
||||||
|
|
||||||
import LayerListGroup from './LayerListGroup'
|
import LayerListGroup from "./LayerListGroup";
|
||||||
import LayerListItem from './LayerListItem'
|
import LayerListItem from "./LayerListItem";
|
||||||
import ModalAdd from './modals/ModalAdd'
|
import ModalAdd from "./modals/ModalAdd";
|
||||||
|
|
||||||
import type {LayerSpecification, SourceSpecification} from 'maplibre-gl';
|
import type {LayerSpecification, SourceSpecification} from "maplibre-gl";
|
||||||
import generateUniqueId from '../libs/document-uid';
|
import generateUniqueId from "../libs/document-uid";
|
||||||
import { findClosestCommonPrefix, layerPrefix } from '../libs/layer';
|
import { findClosestCommonPrefix, layerPrefix } from "../libs/layer";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { OnMoveLayerCallback } from '../libs/definitions';
|
import { type OnMoveLayerCallback } from "../libs/definitions";
|
||||||
|
|
||||||
type LayerListContainerProps = {
|
type LayerListContainerProps = {
|
||||||
layers: LayerSpecification[]
|
layers: LayerSpecification[]
|
||||||
@@ -48,7 +48,7 @@ type LayerListContainerState = {
|
|||||||
class LayerListContainerInternal extends React.Component<LayerListContainerInternalProps, LayerListContainerState> {
|
class LayerListContainerInternal extends React.Component<LayerListContainerInternalProps, LayerListContainerState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
}
|
};
|
||||||
selectedItemRef: React.RefObject<any>;
|
selectedItemRef: React.RefObject<any>;
|
||||||
scrollContainerRef: React.RefObject<HTMLElement | null>;
|
scrollContainerRef: React.RefObject<HTMLElement | null>;
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
isOpen: {
|
isOpen: {
|
||||||
add: false,
|
add: false,
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModal(modalName: string) {
|
toggleModal(modalName: string) {
|
||||||
@@ -78,74 +78,74 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
...this.state.isOpen,
|
...this.state.isOpen,
|
||||||
[modalName]: !this.state.isOpen[modalName]
|
[modalName]: !this.state.isOpen[modalName]
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLayers = () => {
|
toggleLayers = () => {
|
||||||
let idx = 0
|
let idx = 0;
|
||||||
|
|
||||||
const 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);
|
||||||
const lookupKey = [groupPrefix, idx].join('-')
|
const lookupKey = [groupPrefix, idx].join("-");
|
||||||
|
|
||||||
|
|
||||||
if (layers.length > 1) {
|
if (layers.length > 1) {
|
||||||
newGroups[lookupKey] = this.state.areAllGroupsExpanded
|
newGroups[lookupKey] = this.state.areAllGroupsExpanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
layers.forEach((_layer) => {
|
layers.forEach((_layer) => {
|
||||||
idx += 1
|
idx += 1;
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
collapsedGroups: newGroups,
|
collapsedGroups: newGroups,
|
||||||
areAllGroupsExpanded: !this.state.areAllGroupsExpanded
|
areAllGroupsExpanded: !this.state.areAllGroupsExpanded
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
groupedLayers(): (LayerSpecification & {key: string})[][] {
|
groupedLayers(): (LayerSpecification & {key: string})[][] {
|
||||||
const groups = []
|
const groups = [];
|
||||||
const layerIdCount = new Map();
|
const layerIdCount = new Map();
|
||||||
|
|
||||||
for (let i = 0; i < this.props.layers.length; i++) {
|
for (let i = 0; i < this.props.layers.length; i++) {
|
||||||
const origLayer = this.props.layers[i];
|
const origLayer = this.props.layers[i];
|
||||||
const previousLayer = this.props.layers[i-1]
|
const previousLayer = this.props.layers[i-1];
|
||||||
layerIdCount.set(origLayer.id,
|
layerIdCount.set(origLayer.id,
|
||||||
layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0
|
layerIdCount.has(origLayer.id) ? layerIdCount.get(origLayer.id) + 1 : 0
|
||||||
);
|
);
|
||||||
const layer = {
|
const layer = {
|
||||||
...origLayer,
|
...origLayer,
|
||||||
key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`,
|
key: `layers-list-${origLayer.id}-${layerIdCount.get(origLayer.id)}`,
|
||||||
}
|
};
|
||||||
if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
|
if(previousLayer && layerPrefix(previousLayer.id) == layerPrefix(layer.id)) {
|
||||||
const lastGroup = groups[groups.length - 1]
|
const lastGroup = groups[groups.length - 1];
|
||||||
lastGroup.push(layer)
|
lastGroup.push(layer);
|
||||||
} else {
|
} else {
|
||||||
groups.push([layer])
|
groups.push([layer]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return groups
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLayerGroup(groupPrefix: string, idx: number) {
|
toggleLayerGroup(groupPrefix: string, idx: number) {
|
||||||
const lookupKey = [groupPrefix, idx].join('-')
|
const lookupKey = [groupPrefix, idx].join("-");
|
||||||
const newGroups = { ...this.state.collapsedGroups }
|
const newGroups = { ...this.state.collapsedGroups };
|
||||||
if(lookupKey in this.state.collapsedGroups) {
|
if(lookupKey in this.state.collapsedGroups) {
|
||||||
newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey]
|
newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey];
|
||||||
} else {
|
} else {
|
||||||
newGroups[lookupKey] = false
|
newGroups[lookupKey] = false;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
collapsedGroups: newGroups
|
collapsedGroups: newGroups
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isCollapsed(groupPrefix: string, idx: number) {
|
isCollapsed(groupPrefix: string, idx: number) {
|
||||||
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')]
|
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join("-")];
|
||||||
return collapsed === undefined ? true : collapsed
|
return collapsed === undefined ? true : collapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps: LayerListContainerProps, nextState: LayerListContainerState) {
|
shouldComponentUpdate (nextProps: LayerListContainerProps, nextState: LayerListContainerState) {
|
||||||
@@ -177,7 +177,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
const out = {
|
const out = {
|
||||||
...props
|
...props
|
||||||
} as LayerListContainerProps & { layers?: any };
|
} as LayerListContainerProps & { layers?: any };
|
||||||
delete out['layers'];
|
delete out["layers"];
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
const options = {
|
const options = {
|
||||||
root: this.scrollContainerRef.current,
|
root: this.scrollContainerRef.current,
|
||||||
threshold: 1.0
|
threshold: 1.0
|
||||||
}
|
};
|
||||||
const observer = new IntersectionObserver(entries => {
|
const observer = new IntersectionObserver(entries => {
|
||||||
observer.unobserve(target);
|
observer.unobserve(target);
|
||||||
if (entries.length > 0 && entries[0].intersectionRatio < 1) {
|
if (entries.length > 0 && entries[0].intersectionRatio < 1) {
|
||||||
@@ -215,25 +215,25 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const listItems: JSX.Element[] = []
|
const listItems: JSX.Element[] = [];
|
||||||
let idx = 0
|
let idx = 0;
|
||||||
const layersByGroup = this.groupedLayers();
|
const layersByGroup = this.groupedLayers();
|
||||||
layersByGroup.forEach(layers => {
|
layersByGroup.forEach(layers => {
|
||||||
const groupPrefix = layerPrefix(layers[0].id)
|
const groupPrefix = layerPrefix(layers[0].id);
|
||||||
if(layers.length > 1) {
|
if(layers.length > 1) {
|
||||||
const grp = <LayerListGroup
|
const grp = <LayerListGroup
|
||||||
data-wd-key={[groupPrefix, idx].join('-')}
|
data-wd-key={[groupPrefix, idx].join("-")}
|
||||||
aria-controls={layers.map(l => l.key).join(" ")}
|
aria-controls={layers.map(l => l.key).join(" ")}
|
||||||
key={`group-${groupPrefix}-${idx}`}
|
key={`group-${groupPrefix}-${idx}`}
|
||||||
title={groupPrefix}
|
title={groupPrefix}
|
||||||
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
isActive={!this.isCollapsed(groupPrefix, idx) || idx === this.props.selectedLayerIndex}
|
||||||
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
onActiveToggle={this.toggleLayerGroup.bind(this, groupPrefix, idx)}
|
||||||
/>
|
/>;
|
||||||
listItems.push(grp)
|
listItems.push(grp);
|
||||||
}
|
}
|
||||||
|
|
||||||
layers.forEach((layer, idxInGroup) => {
|
layers.forEach((layer, idxInGroup) => {
|
||||||
const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
|
const groupIdx = findClosestCommonPrefix(this.props.layers, idx);
|
||||||
|
|
||||||
const layerError = this.props.errors.find(error => {
|
const layerError = this.props.errors.find(error => {
|
||||||
return (
|
return (
|
||||||
@@ -250,9 +250,9 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
|
|
||||||
const listItem = <LayerListItem
|
const listItem = <LayerListItem
|
||||||
className={classnames({
|
className={classnames({
|
||||||
'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
"maputnik-layer-list-item-collapsed": layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx) && idx !== this.props.selectedLayerIndex,
|
||||||
'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1,
|
"maputnik-layer-list-item-group-last": idxInGroup == layers.length - 1 && layers.length > 1,
|
||||||
'maputnik-layer-list-item--error': !!layerError
|
"maputnik-layer-list-item--error": !!layerError
|
||||||
})}
|
})}
|
||||||
key={layer.key}
|
key={layer.key}
|
||||||
id={layer.key}
|
id={layer.key}
|
||||||
@@ -266,11 +266,11 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
onLayerCopy={this.props.onLayerCopy.bind(this)}
|
onLayerCopy={this.props.onLayerCopy.bind(this)}
|
||||||
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
|
onLayerVisibilityToggle={this.props.onLayerVisibilityToggle.bind(this)}
|
||||||
{...additionalProps}
|
{...additionalProps}
|
||||||
/>
|
/>;
|
||||||
listItems.push(listItem)
|
listItems.push(listItem);
|
||||||
idx += 1
|
idx += 1;
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
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">
|
||||||
@@ -310,7 +310,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
<div className="maputnik-default-property">
|
<div className="maputnik-default-property">
|
||||||
<div className="maputnik-multibutton">
|
<div className="maputnik-multibutton">
|
||||||
<button
|
<button
|
||||||
onClick={this.toggleModal.bind(this, 'add')}
|
onClick={this.toggleModal.bind(this, "add")}
|
||||||
data-wd-key="layer-list:add-layer"
|
data-wd-key="layer-list:add-layer"
|
||||||
className="maputnik-button maputnik-button-selected">
|
className="maputnik-button maputnik-button-selected">
|
||||||
{t("Add Layer")}
|
{t("Add Layer")}
|
||||||
@@ -326,7 +326,7 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
{listItems}
|
{listItems}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +360,6 @@ const LayerList: React.FC<LayerListProps> = (props) => {
|
|||||||
</SortableContext>
|
</SortableContext>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default LayerList;
|
export default LayerList;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import Collapser from './Collapser'
|
import Collapser from "./Collapser";
|
||||||
|
|
||||||
type LayerListGroupProps = {
|
type LayerListGroupProps = {
|
||||||
title: string
|
title: string
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
onActiveToggle(...args: unknown[]): unknown
|
onActiveToggle(...args: unknown[]): unknown
|
||||||
'aria-controls'?: string
|
"aria-controls"?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class LayerListGroup extends React.Component<LayerListGroupProps> {
|
export default class LayerListGroup extends React.Component<LayerListGroupProps> {
|
||||||
@@ -18,7 +18,7 @@ export default class LayerListGroup extends React.Component<LayerListGroupProps>
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="maputnik-layer-list-group-title"
|
className="maputnik-layer-list-group-title"
|
||||||
aria-controls={this.props['aria-controls']}
|
aria-controls={this.props["aria-controls"]}
|
||||||
aria-expanded={this.props.isActive}
|
aria-expanded={this.props.isActive}
|
||||||
>
|
>
|
||||||
{this.props.title}
|
{this.props.title}
|
||||||
@@ -29,6 +29,6 @@ export default class LayerListGroup extends React.Component<LayerListGroupProps>
|
|||||||
isCollapsed={this.props.isActive}
|
isCollapsed={this.props.isActive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import classnames from 'classnames'
|
import classnames from "classnames";
|
||||||
import {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from 'react-icons/md'
|
import {MdContentCopy, MdVisibility, MdVisibilityOff, MdDelete} from "react-icons/md";
|
||||||
import { IconContext } from 'react-icons'
|
import { IconContext } from "react-icons";
|
||||||
import {useSortable} from '@dnd-kit/sortable'
|
import {useSortable} from "@dnd-kit/sortable";
|
||||||
import {CSS} from '@dnd-kit/utilities'
|
import {CSS} from "@dnd-kit/utilities";
|
||||||
|
|
||||||
import IconLayer from './IconLayer'
|
import IconLayer from "./IconLayer";
|
||||||
|
|
||||||
|
|
||||||
type DraggableLabelProps = {
|
type DraggableLabelProps = {
|
||||||
@@ -21,12 +21,12 @@ const DraggableLabel: React.FC<DraggableLabelProps> = (props) => {
|
|||||||
<IconLayer
|
<IconLayer
|
||||||
className="layer-handle__icon"
|
className="layer-handle__icon"
|
||||||
type={props.layerType}
|
type={props.layerType}
|
||||||
style={{ width: '1em', height: '1em', verticalAlign: 'middle' }}
|
style={{ width: "1em", height: "1em", verticalAlign: "middle" }}
|
||||||
/>
|
/>
|
||||||
<button className="maputnik-layer-list-item-id">
|
<button className="maputnik-layer-list-item-id">
|
||||||
{props.layerId}
|
{props.layerId}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IconActionProps = {
|
type IconActionProps = {
|
||||||
@@ -40,17 +40,17 @@ 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 />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {classBlockName, classBlockModifier} = this.props;
|
const {classBlockName, classBlockModifier} = this.props;
|
||||||
|
|
||||||
let classAdditions = '';
|
let classAdditions = "";
|
||||||
if (classBlockName) {
|
if (classBlockName) {
|
||||||
classAdditions = `maputnik-layer-list-icon-action__${classBlockName}`;
|
classAdditions = `maputnik-layer-list-icon-action__${classBlockName}`;
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class IconAction extends React.Component<IconActionProps> {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{this.renderIcon()}
|
{this.renderIcon()}
|
||||||
</button>
|
</button>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ type LayerListItemProps = {
|
|||||||
const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props, ref) => {
|
const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props, ref) => {
|
||||||
const {
|
const {
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
visibility = 'visible',
|
visibility = "visible",
|
||||||
onLayerCopy = () => {},
|
onLayerCopy = () => {},
|
||||||
onLayerDestroy = () => {},
|
onLayerDestroy = () => {},
|
||||||
onLayerVisibilityToggle = () => {},
|
onLayerVisibilityToggle = () => {},
|
||||||
@@ -110,12 +110,12 @@ const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props
|
|||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.5 : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibilityAction = visibility === 'visible' ? 'show' : 'hide';
|
const visibilityAction = visibility === "visible" ? "show" : "hide";
|
||||||
|
|
||||||
// Cast ref to MutableRefObject since we know from the codebase that's what's always passed
|
// Cast ref to MutableRefObject since we know from the codebase that's what's always passed
|
||||||
const refObject = ref as React.MutableRefObject<HTMLLIElement | null> | null;
|
const refObject = ref as React.MutableRefObject<HTMLLIElement | null> | null;
|
||||||
|
|
||||||
return <IconContext.Provider value={{size: '14px'}}>
|
return <IconContext.Provider value={{size: "14px"}}>
|
||||||
<li
|
<li
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
setNodeRef(node);
|
setNodeRef(node);
|
||||||
@@ -141,13 +141,13 @@ const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props
|
|||||||
<span style={{flexGrow: 1}} />
|
<span style={{flexGrow: 1}} />
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:" + props.layerId+":delete"}
|
wdKey={"layer-list-item:" + props.layerId+":delete"}
|
||||||
action={'delete'}
|
action={"delete"}
|
||||||
classBlockName="delete"
|
classBlockName="delete"
|
||||||
onClick={_e => onLayerDestroy!(props.layerIndex)}
|
onClick={_e => onLayerDestroy!(props.layerIndex)}
|
||||||
/>
|
/>
|
||||||
<IconAction
|
<IconAction
|
||||||
wdKey={"layer-list-item:" + props.layerId+":copy"}
|
wdKey={"layer-list-item:" + props.layerId+":copy"}
|
||||||
action={'duplicate'}
|
action={"duplicate"}
|
||||||
classBlockName="duplicate"
|
classBlockName="duplicate"
|
||||||
onClick={_e => onLayerCopy!(props.layerIndex)}
|
onClick={_e => onLayerCopy!(props.layerIndex)}
|
||||||
/>
|
/>
|
||||||
@@ -159,7 +159,7 @@ const LayerListItem = React.forwardRef<HTMLLIElement, LayerListItemProps>((props
|
|||||||
onClick={_e => onLayerVisibilityToggle!(props.layerIndex)}
|
onClick={_e => onLayerVisibilityToggle!(props.layerIndex)}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default LayerListItem;
|
export default LayerListItem;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {createRoot} from 'react-dom/client'
|
import {createRoot} from "react-dom/client";
|
||||||
import MapLibreGl, {LayerSpecification, LngLat, Map, MapOptions, SourceSpecification, StyleSpecification} from 'maplibre-gl'
|
import MapLibreGl, {type LayerSpecification, type LngLat, type Map, type MapOptions, type SourceSpecification, type StyleSpecification} from "maplibre-gl";
|
||||||
import MaplibreInspect from '@maplibre/maplibre-gl-inspect'
|
import MaplibreInspect from "@maplibre/maplibre-gl-inspect";
|
||||||
import colors from '@maplibre/maplibre-gl-inspect/lib/colors'
|
import colors from "@maplibre/maplibre-gl-inspect/lib/colors";
|
||||||
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup'
|
import MapMaplibreGlLayerPopup from "./MapMaplibreGlLayerPopup";
|
||||||
import MapMaplibreGlFeaturePropertyPopup, { InspectFeature } from './MapMaplibreGlFeaturePropertyPopup'
|
import MapMaplibreGlFeaturePropertyPopup, { type InspectFeature } from "./MapMaplibreGlFeaturePropertyPopup";
|
||||||
import Color from 'color'
|
import Color from "color";
|
||||||
import ZoomControl from '../libs/zoomcontrol'
|
import ZoomControl from "../libs/zoomcontrol";
|
||||||
import { HighlightedLayer, colorHighlightedLayer } from '../libs/highlight'
|
import { type HighlightedLayer, colorHighlightedLayer } from "../libs/highlight";
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
import "maplibre-gl/dist/maplibre-gl.css";
|
||||||
import '../maplibregl.css'
|
import "../maplibregl.css";
|
||||||
import '../libs/maplibre-rtl'
|
import "../libs/maplibre-rtl";
|
||||||
import MaplibreGeocoder, { MaplibreGeocoderApi, MaplibreGeocoderApiConfig } from '@maplibre/maplibre-gl-geocoder';
|
import MaplibreGeocoder, { type MaplibreGeocoderApi, type MaplibreGeocoderApiConfig } from "@maplibre/maplibre-gl-geocoder";
|
||||||
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
|
import "@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css";
|
||||||
import { withTranslation, WithTranslation } from 'react-i18next'
|
import { withTranslation, type WithTranslation } from "react-i18next";
|
||||||
import i18next from 'i18next'
|
import i18next from "i18next";
|
||||||
import { Protocol } from "pmtiles";
|
import { Protocol } from "pmtiles";
|
||||||
|
|
||||||
function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[], highlightedLayer?: HighlightedLayer) {
|
function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[], highlightedLayer?: HighlightedLayer) {
|
||||||
@@ -22,30 +22,30 @@ function buildInspectStyle(originalMapStyle: StyleSpecification, coloredLayers:
|
|||||||
"id": "background",
|
"id": "background",
|
||||||
"type": "background",
|
"type": "background",
|
||||||
"paint": {
|
"paint": {
|
||||||
"background-color": '#1c1f24',
|
"background-color": "#1c1f24",
|
||||||
}
|
}
|
||||||
} as LayerSpecification
|
} as LayerSpecification;
|
||||||
|
|
||||||
const layer = colorHighlightedLayer(highlightedLayer)
|
const layer = colorHighlightedLayer(highlightedLayer);
|
||||||
if(layer) {
|
if(layer) {
|
||||||
coloredLayers.push(layer)
|
coloredLayers.push(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sources: {[key:string]: SourceSpecification} = {}
|
const sources: {[key:string]: SourceSpecification} = {};
|
||||||
|
|
||||||
Object.keys(originalMapStyle.sources).forEach(sourceId => {
|
Object.keys(originalMapStyle.sources).forEach(sourceId => {
|
||||||
const source = originalMapStyle.sources[sourceId]
|
const source = originalMapStyle.sources[sourceId];
|
||||||
if(source.type !== 'raster' && source.type !== 'raster-dem') {
|
if(source.type !== "raster" && source.type !== "raster-dem") {
|
||||||
sources[sourceId] = source
|
sources[sourceId] = source;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const inspectStyle = {
|
const inspectStyle = {
|
||||||
...originalMapStyle,
|
...originalMapStyle,
|
||||||
sources: sources,
|
sources: sources,
|
||||||
layers: [backgroundLayer].concat(coloredLayers as LayerSpecification[])
|
layers: [backgroundLayer].concat(coloredLayers as LayerSpecification[])
|
||||||
}
|
};
|
||||||
return inspectStyle
|
return inspectStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapMaplibreGlInternalProps = {
|
type MapMaplibreGlInternalProps = {
|
||||||
@@ -78,20 +78,20 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
options: {} as MapOptions,
|
options: {} as MapOptions,
|
||||||
}
|
};
|
||||||
container: HTMLDivElement | null = null
|
container: HTMLDivElement | null = null;
|
||||||
|
|
||||||
constructor(props: MapMaplibreGlInternalProps) {
|
constructor(props: MapMaplibreGlInternalProps) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
map: null,
|
map: null,
|
||||||
inspect: null,
|
inspect: null,
|
||||||
geocoder: null,
|
geocoder: null,
|
||||||
zoomControl: null,
|
zoomControl: null,
|
||||||
}
|
};
|
||||||
i18next.on('languageChanged', () => {
|
i18next.on("languageChanged", () => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
if(this.state.inspect && this.props.inspectModeEnabled !== this.state.inspect._showInspectMap) {
|
||||||
this.state.inspect.toggleInspector()
|
this.state.inspect.toggleInspector();
|
||||||
}
|
}
|
||||||
if (this.state.inspect && this.props.inspectModeEnabled) {
|
if (this.state.inspect && this.props.inspectModeEnabled) {
|
||||||
this.state.inspect.setOriginalStyle(styleWithTokens);
|
this.state.inspect.setOriginalStyle(styleWithTokens);
|
||||||
@@ -152,7 +152,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
const center = map.getCenter();
|
const center = map.getCenter();
|
||||||
const zoom = map.getZoom();
|
const zoom = map.getZoom();
|
||||||
this.props.onChange({center, zoom});
|
this.props.onChange({center, zoom});
|
||||||
}
|
};
|
||||||
mapViewChange();
|
mapViewChange();
|
||||||
|
|
||||||
map.showTileBoundaries = mapOpts.showTileBoundaries!;
|
map.showTileBoundaries = mapOpts.showTileBoundaries!;
|
||||||
@@ -162,12 +162,12 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
const geocoder = this.initGeocoder(map);
|
const geocoder = this.initGeocoder(map);
|
||||||
|
|
||||||
const zoomControl = new ZoomControl();
|
const zoomControl = new ZoomControl();
|
||||||
map.addControl(zoomControl, 'top-right');
|
map.addControl(zoomControl, "top-right");
|
||||||
|
|
||||||
const nav = new MapLibreGl.NavigationControl({visualizePitch:true});
|
const nav = new MapLibreGl.NavigationControl({visualizePitch:true});
|
||||||
map.addControl(nav, 'top-right');
|
map.addControl(nav, "top-right");
|
||||||
|
|
||||||
const tmpNode = document.createElement('div');
|
const tmpNode = document.createElement("div");
|
||||||
const root = createRoot(tmpNode);
|
const root = createRoot(tmpNode);
|
||||||
|
|
||||||
const inspectPopup = new MapLibreGl.Popup({
|
const inspectPopup = new MapLibreGl.Popup({
|
||||||
@@ -182,17 +182,17 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
showInspectButton: false,
|
showInspectButton: false,
|
||||||
blockHoverPopupOnClick: true,
|
blockHoverPopupOnClick: true,
|
||||||
assignLayerColor: (layerId: string, alpha: number) => {
|
assignLayerColor: (layerId: string, alpha: number) => {
|
||||||
return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string()
|
return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string();
|
||||||
},
|
},
|
||||||
buildInspectStyle: (originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[]) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
|
buildInspectStyle: (originalMapStyle: StyleSpecification, coloredLayers: HighlightedLayer[]) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
|
||||||
renderPopup: (features: InspectFeature[]) => {
|
renderPopup: (features: InspectFeature[]) => {
|
||||||
if(this.props.inspectModeEnabled) {
|
if(this.props.inspectModeEnabled) {
|
||||||
inspectPopup.once('open', () => {
|
inspectPopup.once("open", () => {
|
||||||
root.render(<MapMaplibreGlFeaturePropertyPopup features={features} />);
|
root.render(<MapMaplibreGlFeaturePropertyPopup features={features} />);
|
||||||
});
|
});
|
||||||
return tmpNode;
|
return tmpNode;
|
||||||
} else {
|
} else {
|
||||||
inspectPopup.once('open', () => {
|
inspectPopup.once("open", () => {
|
||||||
root.render(<MapMaplibreGlLayerPopup
|
root.render(<MapMaplibreGlLayerPopup
|
||||||
features={features}
|
features={features}
|
||||||
onLayerSelect={this.onLayerSelectById}
|
onLayerSelect={this.onLayerSelectById}
|
||||||
@@ -202,8 +202,8 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
return tmpNode;
|
return tmpNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
map.addControl(inspect)
|
map.addControl(inspect);
|
||||||
|
|
||||||
map.on("style.load", () => {
|
map.on("style.load", () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -213,18 +213,18 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
zoomControl,
|
zoomControl,
|
||||||
zoom: map.getZoom()
|
zoom: map.getZoom()
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
map.on("data", e => {
|
map.on("data", e => {
|
||||||
if(e.dataType !== 'tile') return
|
if(e.dataType !== "tile") return;
|
||||||
this.props.onDataChange!({
|
this.props.onDataChange!({
|
||||||
map: this.state.map
|
map: this.state.map
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
map.on("error", e => {
|
map.on("error", e => {
|
||||||
console.log("ERROR", e);
|
console.log("ERROR", e);
|
||||||
})
|
});
|
||||||
|
|
||||||
map.on("zoom", _e => {
|
map.on("zoom", _e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -239,7 +239,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
onLayerSelectById = (id: string) => {
|
onLayerSelectById = (id: string) => {
|
||||||
const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id);
|
const index = this.props.mapStyle.layers.findIndex(layer => layer.id === id);
|
||||||
this.props.onLayerSelect(index);
|
this.props.onLayerSelect(index);
|
||||||
}
|
};
|
||||||
|
|
||||||
initGeocoder(map: Map) {
|
initGeocoder(map: Map) {
|
||||||
const geocoderConfig = {
|
const geocoderConfig = {
|
||||||
@@ -257,15 +257,15 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
(feature.bbox[3] - feature.bbox[1]) / 2
|
(feature.bbox[3] - feature.bbox[1]) / 2
|
||||||
];
|
];
|
||||||
const point = {
|
const point = {
|
||||||
type: 'Feature',
|
type: "Feature",
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Point',
|
type: "Point",
|
||||||
coordinates: center
|
coordinates: center
|
||||||
},
|
},
|
||||||
place_name: feature.properties.display_name,
|
place_name: feature.properties.display_name,
|
||||||
properties: feature.properties,
|
properties: feature.properties,
|
||||||
text: feature.properties.display_name,
|
text: feature.properties.display_name,
|
||||||
place_type: ['place'],
|
place_type: ["place"],
|
||||||
center
|
center
|
||||||
};
|
};
|
||||||
features.push(point);
|
features.push(point);
|
||||||
@@ -282,7 +282,7 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
placeholder: this.props.t("Search"),
|
placeholder: this.props.t("Search"),
|
||||||
maplibregl: MapLibreGl,
|
maplibregl: MapLibreGl,
|
||||||
});
|
});
|
||||||
map.addControl(geocoder, 'top-left');
|
map.addControl(geocoder, "top-left");
|
||||||
return geocoder;
|
return geocoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,9 +294,9 @@ class MapMaplibreGlInternal extends React.Component<MapMaplibreGlInternalProps,
|
|||||||
className="maputnik-map__map"
|
className="maputnik-map__map"
|
||||||
role="region"
|
role="region"
|
||||||
aria-label={t("Map view")}
|
aria-label={t("Map view")}
|
||||||
ref={x => {this.container = x}}
|
ref={x => {this.container = x;}}
|
||||||
data-wd-key="maplibre:map"
|
data-wd-key="maplibre:map"
|
||||||
></div>
|
></div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import type { GeoJSONFeatureWithSourceLayer } from '@maplibre/maplibre-gl-inspect'
|
import type { GeoJSONFeatureWithSourceLayer } from "@maplibre/maplibre-gl-inspect";
|
||||||
|
|
||||||
export type InspectFeature = GeoJSONFeatureWithSourceLayer & {
|
export type InspectFeature = GeoJSONFeatureWithSourceLayer & {
|
||||||
inspectModeCounter?: number
|
inspectModeCounter?: number
|
||||||
counter?: number
|
counter?: number
|
||||||
}
|
};
|
||||||
|
|
||||||
function displayValue(value: string | number | Date | object | undefined) {
|
function displayValue(value: string | number | Date | object | undefined) {
|
||||||
if (typeof value === 'undefined' || value === null) return value;
|
if (typeof value === "undefined" || value === null) return value;
|
||||||
if (value instanceof Date) return value.toLocaleString();
|
if (value instanceof Date) return value.toLocaleString();
|
||||||
if (typeof value === 'object' ||
|
if (typeof value === "object" ||
|
||||||
typeof value === 'number' ||
|
typeof value === "number" ||
|
||||||
typeof value === 'string') return value.toString();
|
typeof value === "string") return value.toString();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,21 +19,21 @@ function renderKeyValueTableRow(key: string, value: string | undefined) {
|
|||||||
return <tr key={key}>
|
return <tr key={key}>
|
||||||
<td className="maputnik-popup-table-cell">{key}</td>
|
<td className="maputnik-popup-table-cell">{key}</td>
|
||||||
<td className="maputnik-popup-table-cell">{value}</td>
|
<td className="maputnik-popup-table-cell">{value}</td>
|
||||||
</tr>
|
</tr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFeature(feature: InspectFeature, idx: number) {
|
function renderFeature(feature: InspectFeature, idx: number) {
|
||||||
return <React.Fragment key={idx}>
|
return <React.Fragment key={idx}>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={2} className="maputnik-popup-layer-id">{feature.layer['source']}: {feature.layer['source-layer']}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</td>
|
<td colSpan={2} className="maputnik-popup-layer-id">{feature.layer["source"]}: {feature.layer["source-layer"]}{feature.inspectModeCounter && <span> × {feature.inspectModeCounter}</span>}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{renderKeyValueTableRow("$type", feature.geometry.type)}
|
{renderKeyValueTableRow("$type", feature.geometry.type)}
|
||||||
{renderKeyValueTableRow("$id", displayValue(feature.id))}
|
{renderKeyValueTableRow("$id", displayValue(feature.id))}
|
||||||
{Object.keys(feature.properties).map(propertyName => {
|
{Object.keys(feature.properties).map(propertyName => {
|
||||||
const property = feature.properties[propertyName];
|
const property = feature.properties[propertyName];
|
||||||
return renderKeyValueTableRow(propertyName, displayValue(property))
|
return renderKeyValueTableRow(propertyName, displayValue(property));
|
||||||
})}
|
})}
|
||||||
</React.Fragment>
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
function removeDuplicatedFeatures(features: InspectFeature[]) {
|
||||||
@@ -41,22 +41,22 @@ function removeDuplicatedFeatures(features: InspectFeature[]) {
|
|||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
const featureIndex = uniqueFeatures.findIndex(feature2 => {
|
const featureIndex = uniqueFeatures.findIndex(feature2 => {
|
||||||
return feature.layer['source-layer'] === feature2.layer['source-layer']
|
return feature.layer["source-layer"] === feature2.layer["source-layer"]
|
||||||
&& JSON.stringify(feature.properties) === JSON.stringify(feature2.properties)
|
&& JSON.stringify(feature.properties) === JSON.stringify(feature2.properties);
|
||||||
})
|
});
|
||||||
|
|
||||||
if(featureIndex === -1) {
|
if(featureIndex === -1) {
|
||||||
uniqueFeatures.push(feature)
|
uniqueFeatures.push(feature);
|
||||||
} else {
|
} else {
|
||||||
if('inspectModeCounter' in uniqueFeatures[featureIndex]) {
|
if("inspectModeCounter" in uniqueFeatures[featureIndex]) {
|
||||||
uniqueFeatures[featureIndex].inspectModeCounter!++
|
uniqueFeatures[featureIndex].inspectModeCounter!++;
|
||||||
} else {
|
} else {
|
||||||
uniqueFeatures[featureIndex].inspectModeCounter = 2
|
uniqueFeatures[featureIndex].inspectModeCounter = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return uniqueFeatures
|
return uniqueFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeaturePropertyPopupProps = {
|
type FeaturePropertyPopupProps = {
|
||||||
@@ -65,16 +65,16 @@ type FeaturePropertyPopupProps = {
|
|||||||
|
|
||||||
class FeaturePropertyPopup extends React.Component<FeaturePropertyPopupProps> {
|
class FeaturePropertyPopup extends React.Component<FeaturePropertyPopupProps> {
|
||||||
render() {
|
render() {
|
||||||
const features = removeDuplicatedFeatures(this.props.features)
|
const features = removeDuplicatedFeatures(this.props.features);
|
||||||
return <div className="maputnik-feature-property-popup" dir="ltr" data-wd-key="feature-property-popup">
|
return <div className="maputnik-feature-property-popup" dir="ltr" data-wd-key="feature-property-popup">
|
||||||
<table className="maputnik-popup-table">
|
<table className="maputnik-popup-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{features.map(renderFeature)}
|
{features.map(renderFeature)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default FeaturePropertyPopup
|
export default FeaturePropertyPopup;
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import IconLayer from './IconLayer'
|
import IconLayer from "./IconLayer";
|
||||||
import type {InspectFeature} from './MapMaplibreGlFeaturePropertyPopup';
|
import type {InspectFeature} from "./MapMaplibreGlFeaturePropertyPopup";
|
||||||
|
|
||||||
function groupFeaturesBySourceLayer(features: InspectFeature[]) {
|
function groupFeaturesBySourceLayer(features: InspectFeature[]) {
|
||||||
const sources: {[key: string]: InspectFeature[]} = {}
|
const sources: {[key: string]: InspectFeature[]} = {};
|
||||||
|
|
||||||
const returnedFeatures: {[key: string]: number} = {}
|
const returnedFeatures: {[key: string]: number} = {};
|
||||||
|
|
||||||
features.forEach(feature => {
|
features.forEach(feature => {
|
||||||
const sourceKey = feature.layer['source-layer'] as string;
|
const sourceKey = feature.layer["source-layer"] as string;
|
||||||
if(Object.prototype.hasOwnProperty.call(returnedFeatures, feature.layer.id)) {
|
if(Object.prototype.hasOwnProperty.call(returnedFeatures, feature.layer.id)) {
|
||||||
returnedFeatures[feature.layer.id]++
|
returnedFeatures[feature.layer.id]++;
|
||||||
|
|
||||||
const featureObject = sources[sourceKey].find((f: InspectFeature) => f.layer.id === feature.layer.id)
|
const featureObject = sources[sourceKey].find((f: InspectFeature) => f.layer.id === feature.layer.id);
|
||||||
|
|
||||||
featureObject!.counter = returnedFeatures[feature.layer.id]
|
featureObject!.counter = returnedFeatures[feature.layer.id];
|
||||||
} else {
|
} else {
|
||||||
sources[sourceKey] = sources[sourceKey] || []
|
sources[sourceKey] = sources[sourceKey] || [];
|
||||||
sources[sourceKey].push(feature)
|
sources[sourceKey].push(feature);
|
||||||
|
|
||||||
returnedFeatures[feature.layer.id] = 1
|
returnedFeatures[feature.layer.id] = 1;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return sources
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeatureLayerPopupProps = {
|
type FeatureLayerPopupProps = {
|
||||||
@@ -66,7 +66,7 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const sources = groupFeaturesBySourceLayer(this.props.features)
|
const sources = groupFeaturesBySourceLayer(this.props.features);
|
||||||
|
|
||||||
const items = Object.keys(sources).map(vectorLayerId => {
|
const items = Object.keys(sources).map(vectorLayerId => {
|
||||||
const layers = sources[vectorLayerId].map((feature: InspectFeature, idx: number) => {
|
const layers = sources[vectorLayerId].map((feature: InspectFeature, idx: number) => {
|
||||||
@@ -83,7 +83,7 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
|||||||
<label
|
<label
|
||||||
className="maputnik-popup-layer__label"
|
className="maputnik-popup-layer__label"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.props.onLayerSelect(feature.layer.id)
|
this.props.onLayerSelect(feature.layer.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{feature.layer.type &&
|
{feature.layer.type &&
|
||||||
@@ -96,19 +96,19 @@ class FeatureLayerPopup extends React.Component<FeatureLayerPopupProps> {
|
|||||||
{feature.layer.id}
|
{feature.layer.id}
|
||||||
{feature.counter && <span> × {feature.counter}</span>}
|
{feature.counter && <span> × {feature.counter}</span>}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>;
|
||||||
})
|
});
|
||||||
return <div key={vectorLayerId}>
|
return <div key={vectorLayerId}>
|
||||||
<div className="maputnik-popup-layer-id">{vectorLayerId}</div>
|
<div className="maputnik-popup-layer-id">{vectorLayerId}</div>
|
||||||
{layers}
|
{layers}
|
||||||
</div>
|
</div>;
|
||||||
})
|
});
|
||||||
|
|
||||||
return <div className="maputnik-feature-layer-popup" data-wd-key="feature-layer-popup" dir="ltr">
|
return <div className="maputnik-feature-layer-popup" data-wd-key="feature-layer-popup" dir="ltr">
|
||||||
{items}
|
{items}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default FeatureLayerPopup
|
export default FeatureLayerPopup;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {throttle} from 'lodash';
|
import {throttle} from "lodash";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import MapMaplibreGlLayerPopup from './MapMaplibreGlLayerPopup';
|
import MapMaplibreGlLayerPopup from "./MapMaplibreGlLayerPopup";
|
||||||
|
|
||||||
import 'ol/ol.css'
|
import "ol/ol.css";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import {apply} from 'ol-mapbox-style';
|
import {apply} from "ol-mapbox-style";
|
||||||
import {Map, View, Overlay} from 'ol';
|
import {Map, View, Overlay} from "ol";
|
||||||
|
|
||||||
import {toLonLat} from 'ol/proj';
|
import {toLonLat} from "ol/proj";
|
||||||
import type {StyleSpecification} from 'maplibre-gl';
|
import type {StyleSpecification} from "maplibre-gl";
|
||||||
|
|
||||||
|
|
||||||
function renderCoords (coords: string[]) {
|
function renderCoords (coords: string[]) {
|
||||||
@@ -19,8 +19,8 @@ function renderCoords (coords: string[]) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return <span className="maputnik-coords">
|
return <span className="maputnik-coords">
|
||||||
{coords.map((coord) => String(coord).padStart(7, "\u00A0")).join(', ')}
|
{coords.map((coord) => String(coord).padStart(7, "\u00A0")).join(", ")}
|
||||||
</span>
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
onMapLoaded: () => {},
|
onMapLoaded: () => {},
|
||||||
onDataChange: () => {},
|
onDataChange: () => {},
|
||||||
onLayerSelect: () => {},
|
onLayerSelect: () => {},
|
||||||
}
|
};
|
||||||
updateStyle: any;
|
updateStyle: any;
|
||||||
map: any;
|
map: any;
|
||||||
container: HTMLDivElement | null = null;
|
container: HTMLDivElement | null = null;
|
||||||
@@ -101,15 +101,15 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on('pointermove', (evt) => {
|
map.on("pointermove", (evt) => {
|
||||||
const coords = toLonLat(evt.coordinate);
|
const coords = toLonLat(evt.coordinate);
|
||||||
this.setState({
|
this.setState({
|
||||||
cursor: [
|
cursor: [
|
||||||
coords[0].toFixed(2),
|
coords[0].toFixed(2),
|
||||||
coords[1].toFixed(2)
|
coords[1].toFixed(2)
|
||||||
]
|
]
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const onMoveEnd = () => {
|
const onMoveEnd = () => {
|
||||||
const zoom = map.getView().getZoom();
|
const zoom = map.getView().getZoom();
|
||||||
@@ -122,12 +122,12 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
lat: center[1],
|
lat: center[1],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onMoveEnd();
|
onMoveEnd();
|
||||||
map.on('moveend', onMoveEnd);
|
map.on("moveend", onMoveEnd);
|
||||||
|
|
||||||
map.on('postrender', (_e) => {
|
map.on("postrender", (_e) => {
|
||||||
const center = toLonLat(map.getView().getCenter()!);
|
const center = toLonLat(map.getView().getCenter()!);
|
||||||
this.setState({
|
this.setState({
|
||||||
center: [
|
center: [
|
||||||
@@ -150,13 +150,13 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
closeOverlay = (e: any) => {
|
closeOverlay = (e: any) => {
|
||||||
e.target.blur();
|
e.target.blur();
|
||||||
this.overlay!.setPosition(undefined);
|
this.overlay!.setPosition(undefined);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
return <div className="maputnik-ol-container">
|
return <div className="maputnik-ol-container">
|
||||||
<div
|
<div
|
||||||
ref={x => {this.popupContainer = x}}
|
ref={x => {this.popupContainer = x;}}
|
||||||
style={{background: "black"}}
|
style={{background: "black"}}
|
||||||
className="maputnik-popup"
|
className="maputnik-popup"
|
||||||
>
|
>
|
||||||
@@ -193,14 +193,14 @@ class MapOpenLayersInternal extends React.Component<MapOpenLayersInternalProps,
|
|||||||
}
|
}
|
||||||
<div
|
<div
|
||||||
className="maputnik-ol"
|
className="maputnik-ol"
|
||||||
ref={x => {this.container = x}}
|
ref={x => {this.container = x;}}
|
||||||
role="region"
|
role="region"
|
||||||
aria-label={t("Map view")}
|
aria-label={t("Map view")}
|
||||||
style={{
|
style={{
|
||||||
...this.props.style,
|
...this.props.style,
|
||||||
}}>
|
}}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import FieldFunction from './FieldFunction'
|
import FieldFunction from "./FieldFunction";
|
||||||
import type {LayerSpecification} from 'maplibre-gl'
|
import type {LayerSpecification} from "maplibre-gl";
|
||||||
|
|
||||||
const iconProperties = ['background-pattern', 'fill-pattern', 'line-pattern', 'fill-extrusion-pattern', 'icon-image']
|
const iconProperties = ["background-pattern", "fill-pattern", "line-pattern", "fill-extrusion-pattern", "icon-image"];
|
||||||
|
|
||||||
/** Extract field spec by {@fieldName} from the {@layerType} in the
|
/** Extract field spec by {@fieldName} from the {@layerType} in the
|
||||||
* style specification from either the paint or layout group */
|
* style specification from either the paint or layout group */
|
||||||
function getFieldSpec(spec: any, layerType: LayerSpecification["type"], fieldName: string) {
|
function getFieldSpec(spec: any, layerType: LayerSpecification["type"], fieldName: string) {
|
||||||
const groupName = getGroupName(spec, layerType, fieldName)
|
const groupName = getGroupName(spec, layerType, fieldName);
|
||||||
const group = spec[groupName + '_' + layerType]
|
const group = spec[groupName + "_" + layerType];
|
||||||
const fieldSpec = group[fieldName]
|
const fieldSpec = group[fieldName];
|
||||||
if(iconProperties.indexOf(fieldName) >= 0) {
|
if(iconProperties.indexOf(fieldName) >= 0) {
|
||||||
return {
|
return {
|
||||||
...fieldSpec,
|
...fieldSpec,
|
||||||
values: spec.$root.sprite.values
|
values: spec.$root.sprite.values
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if(fieldName === 'text-font') {
|
if(fieldName === "text-font") {
|
||||||
return {
|
return {
|
||||||
...fieldSpec,
|
...fieldSpec,
|
||||||
values: spec.$root.glyphs.values
|
values: spec.$root.glyphs.values
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return fieldSpec
|
return fieldSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGroupName(spec: any, layerType: LayerSpecification["type"], fieldName: string): 'paint' | 'layout' {
|
function getGroupName(spec: any, layerType: LayerSpecification["type"], fieldName: string): "paint" | "layout" {
|
||||||
const paint = spec['paint_' + layerType] || {}
|
const paint = spec["paint_" + layerType] || {};
|
||||||
return (fieldName in paint) ? 'paint' : 'layout';
|
return (fieldName in paint) ? "paint" : "layout";
|
||||||
}
|
}
|
||||||
|
|
||||||
type PropertyGroupProps = {
|
type PropertyGroupProps = {
|
||||||
@@ -41,21 +41,21 @@ type PropertyGroupProps = {
|
|||||||
|
|
||||||
export default class PropertyGroup extends React.Component<PropertyGroupProps> {
|
export default class PropertyGroup extends React.Component<PropertyGroupProps> {
|
||||||
onPropertyChange = (property: string, newValue: any) => {
|
onPropertyChange = (property: string, newValue: any) => {
|
||||||
const group = getGroupName(this.props.spec, this.props.layer.type, property)
|
const group = getGroupName(this.props.spec, this.props.layer.type, property);
|
||||||
this.props.onChange(group ,property, newValue)
|
this.props.onChange(group ,property, newValue);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {errors} = this.props;
|
const {errors} = this.props;
|
||||||
const fields = this.props.groupFields.map(fieldName => {
|
const fields = this.props.groupFields.map(fieldName => {
|
||||||
const fieldSpec = getFieldSpec(this.props.spec, this.props.layer.type, fieldName)
|
const fieldSpec = getFieldSpec(this.props.spec, this.props.layer.type, fieldName);
|
||||||
|
|
||||||
const paint = this.props.layer.paint || {}
|
const paint = this.props.layer.paint || {};
|
||||||
const layout = this.props.layer.layout || {}
|
const layout = this.props.layer.layout || {};
|
||||||
const fieldValue = fieldName in paint
|
const fieldValue = fieldName in paint
|
||||||
? paint[fieldName as keyof typeof paint]
|
? paint[fieldName as keyof typeof paint]
|
||||||
: layout[fieldName as keyof typeof layout]
|
: layout[fieldName as keyof typeof layout];
|
||||||
const fieldType = fieldName in paint ? 'paint' : 'layout';
|
const fieldType = fieldName in paint ? "paint" : "layout";
|
||||||
|
|
||||||
return <FieldFunction
|
return <FieldFunction
|
||||||
errors={errors}
|
errors={errors}
|
||||||
@@ -65,11 +65,11 @@ export default class PropertyGroup extends React.Component<PropertyGroupProps> {
|
|||||||
value={fieldValue}
|
value={fieldValue}
|
||||||
fieldType={fieldType}
|
fieldType={fieldType}
|
||||||
fieldSpec={fieldSpec}
|
fieldSpec={fieldSpec}
|
||||||
/>
|
/>;
|
||||||
})
|
});
|
||||||
|
|
||||||
return <div className="maputnik-property-group">
|
return <div className="maputnik-property-group">
|
||||||
{fields}
|
{fields}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
type ScrollContainerProps = {
|
type ScrollContainerProps = {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
@@ -8,6 +8,6 @@ export default class ScrollContainer extends React.Component<ScrollContainerProp
|
|||||||
render() {
|
render() {
|
||||||
return <div className="maputnik-scroll-container">
|
return <div className="maputnik-scroll-container">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import {otherFilterOps} from '../libs/filterops'
|
import {otherFilterOps} from "../libs/filterops";
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
import InputAutocomplete from './InputAutocomplete'
|
import InputAutocomplete from "./InputAutocomplete";
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from "./InputSelect";
|
||||||
|
|
||||||
function tryParseInt(v: string | number) {
|
function tryParseInt(v: string | number) {
|
||||||
if (v === '') return v
|
if (v === "") return v;
|
||||||
if (isNaN(v as number)) return v
|
if (isNaN(v as number)) return v;
|
||||||
return parseFloat(v as string)
|
return parseFloat(v as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryParseBool(v: string | boolean) {
|
function tryParseBool(v: string | boolean) {
|
||||||
@@ -43,23 +43,23 @@ type SingleFilterEditorProps = {
|
|||||||
export default class SingleFilterEditor extends React.Component<SingleFilterEditorProps> {
|
export default class SingleFilterEditor extends React.Component<SingleFilterEditorProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
properties: {},
|
properties: {},
|
||||||
}
|
};
|
||||||
|
|
||||||
onFilterPartChanged(filterOp: string, propertyName: string, filterArgs: string[]) {
|
onFilterPartChanged(filterOp: string, propertyName: string, filterArgs: string[]) {
|
||||||
let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)]
|
let newFilter = [filterOp, propertyName, ...filterArgs.map(parseFilter)];
|
||||||
if(filterOp === 'has' || filterOp === '!has') {
|
if(filterOp === "has" || filterOp === "!has") {
|
||||||
newFilter = [filterOp, propertyName]
|
newFilter = [filterOp, propertyName];
|
||||||
} else if(filterArgs.length === 0) {
|
} else if(filterArgs.length === 0) {
|
||||||
newFilter = [filterOp, propertyName, '']
|
newFilter = [filterOp, propertyName, ""];
|
||||||
}
|
}
|
||||||
this.props.onChange(newFilter)
|
this.props.onChange(newFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const f = this.props.filter
|
const f = this.props.filter;
|
||||||
const filterOp = f[0]
|
const filterOp = f[0];
|
||||||
const propertyName = f[1]
|
const propertyName = f[1];
|
||||||
const filterArgs = f.slice(2)
|
const filterArgs = f.slice(2);
|
||||||
|
|
||||||
return <div className="maputnik-filter-editor-single">
|
return <div className="maputnik-filter-editor-single">
|
||||||
<div className="maputnik-filter-editor-property">
|
<div className="maputnik-filter-editor-property">
|
||||||
@@ -82,11 +82,11 @@ export default class SingleFilterEditor extends React.Component<SingleFilterEdit
|
|||||||
<div className="maputnik-filter-editor-args">
|
<div className="maputnik-filter-editor-args">
|
||||||
<InputString
|
<InputString
|
||||||
aria-label="value"
|
aria-label="value"
|
||||||
value={filterArgs.join(',')}
|
value={filterArgs.join(",")}
|
||||||
onChange={(v: string) => this.onFilterPartChanged(filterOp, propertyName, v.split(','))}
|
onChange={(v: string) => this.onFilterPartChanged(filterOp, propertyName, v.split(","))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import './SmallError.scss';
|
import "./SmallError.scss";
|
||||||
|
|
||||||
|
|
||||||
type SmallErrorInternalProps = {
|
type SmallErrorInternalProps = {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
import {mdiFunctionVariant, mdiTableRowPlusAfter} from "@mdi/js";
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import InputSpec from './InputSpec'
|
import InputSpec from "./InputSpec";
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from "./InputNumber";
|
||||||
import InputString from './InputString'
|
import InputString from "./InputString";
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from "./InputSelect";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import docUid from '../libs/document-uid'
|
import docUid from "../libs/document-uid";
|
||||||
import sortNumerically from '../libs/sort-numerically'
|
import sortNumerically from "../libs/sort-numerically";
|
||||||
import {findDefaultFromSpec} from '../libs/spec-helper';
|
import {findDefaultFromSpec} from "../libs/spec-helper";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import labelFromFieldName from '../libs/label-from-field-name'
|
import labelFromFieldName from "../libs/label-from-field-name";
|
||||||
import DeleteStopButton from './_DeleteStopButton'
|
import DeleteStopButton from "./_DeleteStopButton";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ function setStopRefs(props: DataPropertyInternalProps, state: DataPropertyState)
|
|||||||
}
|
}
|
||||||
newRefs[idx] = docUid("stop-");
|
newRefs[idx] = docUid("stop-");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRefs;
|
return newRefs;
|
||||||
@@ -59,17 +59,17 @@ type DataPropertyValue = {
|
|||||||
base?: number
|
base?: number
|
||||||
type?: string
|
type?: string
|
||||||
stops: Stop[]
|
stops: Stop[]
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Stop = [{
|
export type Stop = [{
|
||||||
zoom: number
|
zoom: number
|
||||||
value: number
|
value: number
|
||||||
}, number]
|
}, number];
|
||||||
|
|
||||||
class DataPropertyInternal extends React.Component<DataPropertyInternalProps, DataPropertyState> {
|
class DataPropertyInternal extends React.Component<DataPropertyInternalProps, DataPropertyState> {
|
||||||
state = {
|
state = {
|
||||||
refs: {} as {[key: number]: string}
|
refs: {} as {[key: number]: string}
|
||||||
}
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const newRefs = setStopRefs(this.props, this.state);
|
const newRefs = setStopRefs(this.props, this.state);
|
||||||
@@ -77,7 +77,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
if(newRefs) {
|
if(newRefs) {
|
||||||
this.setState({
|
this.setState({
|
||||||
refs: newRefs
|
refs: newRefs
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,20 +93,20 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
|
|
||||||
getFieldFunctionType(fieldSpec: any) {
|
getFieldFunctionType(fieldSpec: any) {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return "exponential"
|
return "exponential";
|
||||||
}
|
}
|
||||||
if (fieldSpec.type === "number") {
|
if (fieldSpec.type === "number") {
|
||||||
return "interval"
|
return "interval";
|
||||||
}
|
}
|
||||||
return "categorical"
|
return "categorical";
|
||||||
}
|
}
|
||||||
|
|
||||||
getDataFunctionTypes(fieldSpec: any) {
|
getDataFunctionTypes(fieldSpec: any) {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
return ["interpolate", "categorical", "interval", "exponential", "identity"]
|
return ["interpolate", "categorical", "interval", "exponential", "identity"];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return ["categorical", "interval", "identity"]
|
return ["categorical", "interval", "identity"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
return {
|
return {
|
||||||
ref: this.state.refs[idx],
|
ref: this.state.refs[idx],
|
||||||
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));
|
||||||
@@ -127,7 +127,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
mappedWithRef
|
mappedWithRef
|
||||||
.forEach((stop, idx) =>{
|
.forEach((stop, idx) =>{
|
||||||
newRefs[idx] = stop.ref;
|
newRefs[idx] = stop.ref;
|
||||||
})
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
refs: newRefs
|
refs: newRefs
|
||||||
@@ -144,7 +144,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const stopValue = value.type === 'categorical' ? '' : 0;
|
const stopValue = value.type === "categorical" ? "" : 0;
|
||||||
value = {
|
value = {
|
||||||
property: "",
|
property: "",
|
||||||
type: value.type,
|
type: value.type,
|
||||||
@@ -154,13 +154,13 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
[{zoom: 10, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec as any)]
|
[{zoom: 10, value: stopValue}, findDefaultFromSpec(this.props.fieldSpec as any)]
|
||||||
],
|
],
|
||||||
...value,
|
...value,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
this.props.onChange!(fieldName, value);
|
this.props.onChange!(fieldName, value);
|
||||||
}
|
};
|
||||||
|
|
||||||
changeStop(changeIdx: number, stopData: { zoom: number | undefined, value: number }, value: number) {
|
changeStop(changeIdx: number, stopData: { zoom: number | undefined, value: number }, value: number) {
|
||||||
const stops = this.props.value?.stops.slice(0) || []
|
const stops = this.props.value?.stops.slice(0) || [];
|
||||||
// const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
// const changedStop = stopData.zoom === undefined ? stopData.value : stopData
|
||||||
stops[changeIdx] = [
|
stops[changeIdx] = [
|
||||||
{
|
{
|
||||||
@@ -175,20 +175,20 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
const changedValue = {
|
const changedValue = {
|
||||||
...this.props.value,
|
...this.props.value,
|
||||||
stops: orderedStops,
|
stops: orderedStops,
|
||||||
}
|
};
|
||||||
this.onChange(this.props.fieldName, changedValue)
|
this.onChange(this.props.fieldName, changedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeBase(newValue: number | undefined) {
|
changeBase(newValue: number | undefined) {
|
||||||
const changedValue = {
|
const changedValue = {
|
||||||
...this.props.value,
|
...this.props.value,
|
||||||
base: newValue
|
base: newValue
|
||||||
}
|
};
|
||||||
|
|
||||||
if (changedValue.base === undefined) {
|
if (changedValue.base === undefined) {
|
||||||
delete changedValue["base"];
|
delete changedValue["base"];
|
||||||
}
|
}
|
||||||
this.props.onChange!(this.props.fieldName, changedValue)
|
this.props.onChange!(this.props.fieldName, changedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeDataType(propVal: string) {
|
changeDataType(propVal: string) {
|
||||||
@@ -205,43 +205,43 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
|
|
||||||
changeDataProperty(propName: "property" | "default", propVal: any) {
|
changeDataProperty(propName: "property" | "default", propVal: any) {
|
||||||
if (propVal) {
|
if (propVal) {
|
||||||
this.props.value![propName] = propVal
|
this.props.value![propName] = propVal;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
delete this.props.value![propName]
|
delete this.props.value![propName];
|
||||||
}
|
}
|
||||||
this.onChange(this.props.fieldName, this.props.value)
|
this.onChange(this.props.fieldName, this.props.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
|
|
||||||
if (typeof this.props.value?.type === "undefined") {
|
if (typeof this.props.value?.type === "undefined") {
|
||||||
this.props.value!.type = this.getFieldFunctionType(this.props.fieldSpec)
|
this.props.value!.type = this.getFieldFunctionType(this.props.fieldSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataFields;
|
let dataFields;
|
||||||
if (this.props.value?.stops) {
|
if (this.props.value?.stops) {
|
||||||
dataFields = this.props.value.stops.map((stop, idx) => {
|
dataFields = this.props.value.stops.map((stop, idx) => {
|
||||||
const zoomLevel = typeof stop[0] === 'object' ? stop[0].zoom : undefined;
|
const zoomLevel = typeof stop[0] === "object" ? stop[0].zoom : undefined;
|
||||||
const key = this.state.refs[idx];
|
const key = this.state.refs[idx];
|
||||||
const dataLevel = typeof stop[0] === 'object' ? stop[0].value : stop[0];
|
const dataLevel = typeof stop[0] === "object" ? stop[0].value : stop[0];
|
||||||
const value = stop[1]
|
const value = stop[1];
|
||||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
|
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />;
|
||||||
|
|
||||||
const dataProps = {
|
const dataProps = {
|
||||||
'aria-label': t("Input value"),
|
"aria-label": t("Input value"),
|
||||||
label: t("Data value"),
|
label: t("Data value"),
|
||||||
value: dataLevel as any,
|
value: dataLevel as any,
|
||||||
onChange: (newData: string | number | undefined) => this.changeStop(idx, { zoom: zoomLevel, value: newData as number }, value)
|
onChange: (newData: string | number | undefined) => this.changeStop(idx, { zoom: zoomLevel, value: newData as number }, value)
|
||||||
}
|
};
|
||||||
|
|
||||||
let dataInput;
|
let dataInput;
|
||||||
if(this.props.value?.type === "categorical") {
|
if(this.props.value?.type === "categorical") {
|
||||||
dataInput = <InputString {...dataProps} />
|
dataInput = <InputString {...dataProps} />;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dataInput = <InputNumber {...dataProps} />
|
dataInput = <InputNumber {...dataProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let zoomInput = null;
|
let zoomInput = null;
|
||||||
@@ -254,7 +254,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
min={0}
|
min={0}
|
||||||
max={22}
|
max={22}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <tr key={key}>
|
return <tr key={key}>
|
||||||
@@ -276,8 +276,8 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
<td>
|
<td>
|
||||||
{deleteStopBtn}
|
{deleteStopBtn}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="maputnik-data-spec-block">
|
return <div className="maputnik-data-spec-block">
|
||||||
@@ -376,7 +376,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import {MdDelete} from 'react-icons/md'
|
import {MdDelete} from "react-icons/md";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
|
||||||
type DeleteStopButtonInternalProps = {
|
type DeleteStopButtonInternalProps = {
|
||||||
@@ -19,7 +19,7 @@ class DeleteStopButtonInternal extends React.Component<DeleteStopButtonInternalP
|
|||||||
title={t("Remove zoom level from stop")}
|
title={t("Remove zoom level from stop")}
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</InputButton>
|
</InputButton>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {MdDelete, MdUndo} from 'react-icons/md'
|
import {MdDelete, MdUndo} from "react-icons/md";
|
||||||
import stringifyPretty from 'json-stringify-pretty-compact'
|
import stringifyPretty from "json-stringify-pretty-compact";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import labelFromFieldName from '../libs/label-from-field-name'
|
import labelFromFieldName from "../libs/label-from-field-name";
|
||||||
import FieldJson from './FieldJson'
|
import FieldJson from "./FieldJson";
|
||||||
|
|
||||||
|
|
||||||
type ExpressionPropertyInternalProps = {
|
type ExpressionPropertyInternalProps = {
|
||||||
@@ -32,7 +32,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
errors: {},
|
errors: {},
|
||||||
onFocus: () => {},
|
onFocus: () => {},
|
||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props: ExpressionPropertyInternalProps) {
|
constructor(props: ExpressionPropertyInternalProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -44,14 +44,14 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
onJSONInvalid = (_err: Error) => {
|
onJSONInvalid = (_err: Error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
jsonError: true,
|
jsonError: true,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onJSONValid = () => {
|
onJSONValid = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
jsonError: false,
|
jsonError: false,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {t, errors, fieldName, fieldType, value, canUndo} = this.props;
|
const {t, errors, fieldName, fieldType, value, canUndo} = this.props;
|
||||||
@@ -89,7 +89,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
const foundErrors = [];
|
const foundErrors = [];
|
||||||
|
|
||||||
function getValue(data: any) {
|
function getValue(data: any) {
|
||||||
return stringifyPretty(data, {indent: 2, maxLength: 38})
|
return stringifyPretty(data, {indent: 2, maxLength: 38});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonError) {
|
if (jsonError) {
|
||||||
@@ -102,7 +102,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
})
|
})
|
||||||
.forEach(([_key, error]) => {
|
.forEach(([_key, error]) => {
|
||||||
return foundErrors.push(error);
|
return foundErrors.push(error);
|
||||||
})
|
});
|
||||||
|
|
||||||
if (fieldError) {
|
if (fieldError) {
|
||||||
foundErrors.push(fieldError);
|
foundErrors.push(fieldError);
|
||||||
@@ -135,7 +135,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
getValue={getValue}
|
getValue={getValue}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import {MdFunctions, MdInsertChart} from 'react-icons/md'
|
import {MdFunctions, MdInsertChart} from "react-icons/md";
|
||||||
import {mdiFunctionVariant} from '@mdi/js';
|
import {mdiFunctionVariant} from "@mdi/js";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FunctionInputButtonsInternalProps = {
|
type FunctionInputButtonsInternalProps = {
|
||||||
fieldSpec?: any
|
fieldSpec?: any
|
||||||
@@ -17,7 +17,7 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
|
|||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
|
|
||||||
if (this.props.fieldSpec.expression?.parameters.includes('zoom')) {
|
if (this.props.fieldSpec.expression?.parameters.includes("zoom")) {
|
||||||
const expressionInputButton = (
|
const expressionInputButton = (
|
||||||
<InputButton
|
<InputButton
|
||||||
className="maputnik-make-zoom-function"
|
className="maputnik-make-zoom-function"
|
||||||
@@ -36,24 +36,24 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
|
|||||||
title={t("Convert property into a zoom function")}
|
title={t("Convert property into a zoom function")}
|
||||||
>
|
>
|
||||||
<MdFunctions />
|
<MdFunctions />
|
||||||
</InputButton>
|
</InputButton>;
|
||||||
|
|
||||||
let makeDataInputButton;
|
let makeDataInputButton;
|
||||||
if (this.props.fieldSpec['property-type'] === 'data-driven') {
|
if (this.props.fieldSpec["property-type"] === "data-driven") {
|
||||||
makeDataInputButton = <InputButton
|
makeDataInputButton = <InputButton
|
||||||
className="maputnik-make-data-function"
|
className="maputnik-make-data-function"
|
||||||
onClick={this.props.onDataClick}
|
onClick={this.props.onDataClick}
|
||||||
title={t("Convert property to data function")}
|
title={t("Convert property to data function")}
|
||||||
>
|
>
|
||||||
<MdInsertChart />
|
<MdInsertChart />
|
||||||
</InputButton>
|
</InputButton>;
|
||||||
}
|
}
|
||||||
return <div>
|
return <div>
|
||||||
{expressionInputButton}
|
{expressionInputButton}
|
||||||
{makeDataInputButton}
|
{makeDataInputButton}
|
||||||
{makeZoomInputButton}
|
{makeZoomInputButton}
|
||||||
</div>
|
</div>;
|
||||||
} else if (this.props.fieldSpec.expression?.parameters.includes('elevation')) {
|
} else if (this.props.fieldSpec.expression?.parameters.includes("elevation")) {
|
||||||
const inputElevationButton = <InputButton
|
const inputElevationButton = <InputButton
|
||||||
className="maputnik-make-elevation-function"
|
className="maputnik-make-elevation-function"
|
||||||
onClick={this.props.onElevationClick}
|
onClick={this.props.onElevationClick}
|
||||||
@@ -61,10 +61,10 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
|
|||||||
data-wd-key='make-elevation-function'
|
data-wd-key='make-elevation-function'
|
||||||
>
|
>
|
||||||
<MdFunctions />
|
<MdFunctions />
|
||||||
</InputButton>
|
</InputButton>;
|
||||||
return <div>{inputElevationButton}</div>
|
return <div>{inputElevationButton}</div>;
|
||||||
} else {
|
} else {
|
||||||
return <div></div>
|
return <div></div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
import FieldSpec, {FieldSpecProps} from './FieldSpec'
|
import FieldSpec, {type FieldSpecProps} from "./FieldSpec";
|
||||||
import FunctionButtons from './_FunctionButtons'
|
import FunctionButtons from "./_FunctionButtons";
|
||||||
|
|
||||||
import labelFromFieldName from '../libs/label-from-field-name'
|
import labelFromFieldName from "../libs/label-from-field-name";
|
||||||
|
|
||||||
|
|
||||||
type SpecPropertyProps = FieldSpecProps & {
|
type SpecPropertyProps = FieldSpecProps & {
|
||||||
@@ -22,7 +22,7 @@ type SpecPropertyProps = FieldSpecProps & {
|
|||||||
export default class SpecProperty extends React.Component<SpecPropertyProps> {
|
export default class SpecProperty extends React.Component<SpecPropertyProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
errors: {},
|
errors: {},
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {errors, fieldName, fieldType} = this.props;
|
const {errors, fieldName, fieldType} = this.props;
|
||||||
@@ -33,7 +33,7 @@ export default class SpecProperty extends React.Component<SpecPropertyProps> {
|
|||||||
onDataClick={this.props.onDataClick}
|
onDataClick={this.props.onDataClick}
|
||||||
onExpressionClick={this.props.onExpressionClick}
|
onExpressionClick={this.props.onExpressionClick}
|
||||||
onElevationClick={this.props.onElevationClick}
|
onElevationClick={this.props.onElevationClick}
|
||||||
/>
|
/>;
|
||||||
|
|
||||||
const error = errors![fieldType+"."+fieldName as any] as any;
|
const error = errors![fieldType+"."+fieldName as any] as any;
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ export default class SpecProperty extends React.Component<SpecPropertyProps> {
|
|||||||
{...this.props}
|
{...this.props}
|
||||||
error={error}
|
error={error}
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
label={labelFromFieldName(this.props.fieldName || '')}
|
label={labelFromFieldName(this.props.fieldName || "")}
|
||||||
action={functionBtn}
|
action={functionBtn}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {mdiFunctionVariant, mdiTableRowPlusAfter} from '@mdi/js';
|
import {mdiFunctionVariant, mdiTableRowPlusAfter} from "@mdi/js";
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import InputButton from './InputButton'
|
import InputButton from "./InputButton";
|
||||||
import InputSpec from './InputSpec'
|
import InputSpec from "./InputSpec";
|
||||||
import InputNumber from './InputNumber'
|
import InputNumber from "./InputNumber";
|
||||||
import InputSelect from './InputSelect'
|
import InputSelect from "./InputSelect";
|
||||||
import Block from './Block'
|
import Block from "./Block";
|
||||||
|
|
||||||
import DeleteStopButton from './_DeleteStopButton'
|
import DeleteStopButton from "./_DeleteStopButton";
|
||||||
import labelFromFieldName from '../libs/label-from-field-name'
|
import labelFromFieldName from "../libs/label-from-field-name";
|
||||||
|
|
||||||
import docUid from '../libs/document-uid'
|
import docUid from "../libs/document-uid";
|
||||||
import sortNumerically from '../libs/sort-numerically'
|
import sortNumerically from "../libs/sort-numerically";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +35,7 @@ function setStopRefs(props: ZoomPropertyInternalProps, state: ZoomPropertyState)
|
|||||||
} else {
|
} else {
|
||||||
newRefs[idx] = state.refs[idx];
|
newRefs[idx] = state.refs[idx];
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
return newRefs;
|
return newRefs;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ function setStopRefs(props: ZoomPropertyInternalProps, state: ZoomPropertyState)
|
|||||||
type ZoomWithStops = {
|
type ZoomWithStops = {
|
||||||
stops: [number | undefined, number][]
|
stops: [number | undefined, number][]
|
||||||
base?: number
|
base?: number
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
type ZoomPropertyInternalProps = {
|
type ZoomPropertyInternalProps = {
|
||||||
@@ -64,16 +64,16 @@ type ZoomPropertyInternalProps = {
|
|||||||
|
|
||||||
type ZoomPropertyState = {
|
type ZoomPropertyState = {
|
||||||
refs: {[key: number]: string}
|
refs: {[key: number]: string}
|
||||||
}
|
};
|
||||||
|
|
||||||
class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, ZoomPropertyState> {
|
class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, ZoomPropertyState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
errors: {},
|
errors: {},
|
||||||
}
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
refs: {} as {[key: number]: string}
|
refs: {} as {[key: number]: string}
|
||||||
}
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const newRefs = setStopRefs(this.props, this.state);
|
const newRefs = setStopRefs(this.props, this.state);
|
||||||
@@ -81,7 +81,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
if(newRefs) {
|
if(newRefs) {
|
||||||
this.setState({
|
this.setState({
|
||||||
refs: newRefs
|
refs: newRefs
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
return {
|
return {
|
||||||
ref: this.state.refs[idx],
|
ref: this.state.refs[idx],
|
||||||
data: stop
|
data: stop
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
// Sort by zoom
|
// Sort by zoom
|
||||||
.sort((a, b) => sortNumerically(a.data[0]!, b.data[0]!));
|
.sort((a, b) => sortNumerically(a.data[0]!, b.data[0]!));
|
||||||
@@ -112,7 +112,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
mappedWithRef
|
mappedWithRef
|
||||||
.forEach((stop, idx) =>{
|
.forEach((stop, idx) =>{
|
||||||
newRefs[idx] = stop.ref;
|
newRefs[idx] = stop.ref;
|
||||||
})
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
refs: newRefs
|
refs: newRefs
|
||||||
@@ -130,34 +130,34 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
const changedValue = {
|
const changedValue = {
|
||||||
...this.props.value as ZoomWithStops,
|
...this.props.value as ZoomWithStops,
|
||||||
stops: orderedStops
|
stops: orderedStops
|
||||||
}
|
};
|
||||||
this.props.onChange!(this.props.fieldName, changedValue)
|
this.props.onChange!(this.props.fieldName, changedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeBase(newValue: number | undefined) {
|
changeBase(newValue: number | undefined) {
|
||||||
const changedValue = {
|
const changedValue = {
|
||||||
...this.props.value,
|
...this.props.value,
|
||||||
base: newValue
|
base: newValue
|
||||||
}
|
};
|
||||||
|
|
||||||
if (changedValue.base === undefined) {
|
if (changedValue.base === undefined) {
|
||||||
delete changedValue["base"];
|
delete changedValue["base"];
|
||||||
}
|
}
|
||||||
this.props.onChange!(this.props.fieldName, changedValue)
|
this.props.onChange!(this.props.fieldName, changedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeDataType = (type: string) => {
|
changeDataType = (type: string) => {
|
||||||
if (type !== "interpolate" && this.props.onChangeToDataFunction) {
|
if (type !== "interpolate" && this.props.onChangeToDataFunction) {
|
||||||
this.props.onChangeToDataFunction(type);
|
this.props.onChangeToDataFunction(type);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
const zoomFields = this.props.value?.stops.map((stop, idx) => {
|
const zoomFields = this.props.value?.stops.map((stop, idx) => {
|
||||||
const zoomLevel = stop[0]
|
const zoomLevel = stop[0];
|
||||||
const value = stop[1]
|
const value = stop[1];
|
||||||
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />
|
const deleteStopBtn = <DeleteStopButton onClick={this.props.onDeleteStop?.bind(this, idx)} />;
|
||||||
return <tr
|
return <tr
|
||||||
key={`${stop[0]}-${stop[1]}`}
|
key={`${stop[0]}-${stop[1]}`}
|
||||||
>
|
>
|
||||||
@@ -182,7 +182,7 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
<td>
|
<td>
|
||||||
{deleteStopBtn}
|
{deleteStopBtn}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>;
|
||||||
});
|
});
|
||||||
|
|
||||||
// return <div className="maputnik-zoom-spec-property">
|
// return <div className="maputnik-zoom-spec-property">
|
||||||
@@ -248,14 +248,14 @@ class ZoomPropertyInternal extends React.Component<ZoomPropertyInternalProps, Zo
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDataFunctionTypes(fieldSpec: {
|
getDataFunctionTypes(fieldSpec: {
|
||||||
"property-type"?: string
|
"property-type"?: string
|
||||||
"function-type"?: string
|
"function-type"?: string
|
||||||
}) {
|
}) {
|
||||||
if (fieldSpec['property-type'] === 'data-driven') {
|
if (fieldSpec["property-type"] === "data-driven") {
|
||||||
return ["interpolate", "categorical", "interval", "exponential", "identity"];
|
return ["interpolate", "categorical", "interval", "exponential", "identity"];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { PropsWithChildren } from 'react'
|
import React, { type PropsWithChildren } from "react";
|
||||||
import {MdClose} from 'react-icons/md'
|
import {MdClose} from "react-icons/md";
|
||||||
import AriaModal from 'react-aria-modal'
|
import AriaModal from "react-aria-modal";
|
||||||
import classnames from 'classnames';
|
import classnames from "classnames";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type ModalInternalProps = PropsWithChildren & {
|
type ModalInternalProps = PropsWithChildren & {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
@@ -17,7 +17,7 @@ type ModalInternalProps = PropsWithChildren & {
|
|||||||
class ModalInternal extends React.Component<ModalInternalProps> {
|
class ModalInternal extends React.Component<ModalInternalProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
underlayClickExits: true
|
underlayClickExits: true
|
||||||
}
|
};
|
||||||
|
|
||||||
// See <https://github.com/maplibre/maputnik/issues/416>
|
// See <https://github.com/maplibre/maputnik/issues/416>
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
@@ -28,7 +28,7 @@ class ModalInternal extends React.Component<ModalInternalProps> {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.props.onOpenToggle(false);
|
this.props.onOpenToggle(false);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
@@ -59,7 +59,7 @@ class ModalInternal extends React.Component<ModalInternalProps> {
|
|||||||
<div className="maputnik-modal-content">{this.props.children}</div>
|
<div className="maputnik-modal-content">{this.props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AriaModal>
|
</AriaModal>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import type {LayerSpecification, SourceSpecification} from 'maplibre-gl'
|
import type {LayerSpecification, SourceSpecification} from "maplibre-gl";
|
||||||
|
|
||||||
import InputButton from '../InputButton'
|
import InputButton from "../InputButton";
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
import FieldType from '../FieldType'
|
import FieldType from "../FieldType";
|
||||||
import FieldId from '../FieldId'
|
import FieldId from "../FieldId";
|
||||||
import FieldSource from '../FieldSource'
|
import FieldSource from "../FieldSource";
|
||||||
import FieldSourceLayer from '../FieldSourceLayer'
|
import FieldSourceLayer from "../FieldSourceLayer";
|
||||||
import { NON_SOURCE_LAYERS } from '../../libs/non-source-layers'
|
import { NON_SOURCE_LAYERS } from "../../libs/non-source-layers";
|
||||||
|
|
||||||
type ModalAddInternalProps = {
|
type ModalAddInternalProps = {
|
||||||
layers: LayerSpecification[]
|
layers: LayerSpecification[]
|
||||||
@@ -23,50 +23,50 @@ type ModalAddState = {
|
|||||||
type: LayerSpecification["type"]
|
type: LayerSpecification["type"]
|
||||||
id: string
|
id: string
|
||||||
source?: string
|
source?: string
|
||||||
'source-layer'?: string
|
"source-layer"?: string
|
||||||
error?: string | null
|
error?: string | null
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddState> {
|
class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddState> {
|
||||||
addLayer = () => {
|
addLayer = () => {
|
||||||
if (this.props.layers.some(l => l.id === this.state.id)) {
|
if (this.props.layers.some(l => l.id === this.state.id)) {
|
||||||
this.setState({ error: this.props.t('Layer ID already exists') })
|
this.setState({ error: this.props.t("Layer ID already exists") });
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changedLayers = this.props.layers.slice(0)
|
const changedLayers = this.props.layers.slice(0);
|
||||||
const layer: ModalAddState = {
|
const layer: ModalAddState = {
|
||||||
id: this.state.id,
|
id: this.state.id,
|
||||||
type: this.state.type,
|
type: this.state.type,
|
||||||
}
|
};
|
||||||
|
|
||||||
if(this.state.type !== 'background') {
|
if(this.state.type !== "background") {
|
||||||
layer.source = this.state.source
|
layer.source = this.state.source;
|
||||||
if(!NON_SOURCE_LAYERS.includes(this.state.type) && this.state['source-layer']) {
|
if(!NON_SOURCE_LAYERS.includes(this.state.type) && this.state["source-layer"]) {
|
||||||
layer['source-layer'] = this.state['source-layer']
|
layer["source-layer"] = this.state["source-layer"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changedLayers.push(layer as LayerSpecification)
|
changedLayers.push(layer as LayerSpecification);
|
||||||
this.setState({ error: null }, () => {
|
this.setState({ error: null }, () => {
|
||||||
this.props.onLayersChange(changedLayers)
|
this.props.onLayersChange(changedLayers);
|
||||||
this.props.onOpenToggle(false)
|
this.props.onOpenToggle(false);
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props: ModalAddInternalProps) {
|
constructor(props: ModalAddInternalProps) {
|
||||||
super(props)
|
super(props);
|
||||||
const state: ModalAddState = {
|
const state: ModalAddState = {
|
||||||
type: 'fill',
|
type: "fill",
|
||||||
id: '',
|
id: "",
|
||||||
error: null,
|
error: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
if(Object.keys(props.sources).length > 0) {
|
if(Object.keys(props.sources).length > 0) {
|
||||||
state.source = Object.keys(this.props.sources)[0];
|
state.source = Object.keys(this.props.sources)[0];
|
||||||
const sourceLayers = this.props.sources[state.source].layers || []
|
const sourceLayers = this.props.sources[state.source].layers || [];
|
||||||
if (sourceLayers.length > 0) {
|
if (sourceLayers.length > 0) {
|
||||||
state['source-layer'] = sourceLayers[0];
|
state["source-layer"] = sourceLayers[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.state = state;
|
this.state = state;
|
||||||
@@ -104,22 +104,22 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
getSources(type: LayerSpecification["type"]) {
|
getSources(type: LayerSpecification["type"]) {
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case 'background':
|
case "background":
|
||||||
return [];
|
return [];
|
||||||
case 'hillshade':
|
case "hillshade":
|
||||||
case 'color-relief':
|
case "color-relief":
|
||||||
return Object.entries(this.props.sources).filter(([_, v]) => v.type === 'raster-dem').map(([k, _]) => k);
|
return Object.entries(this.props.sources).filter(([_, v]) => v.type === "raster-dem").map(([k, _]) => k);
|
||||||
case 'raster':
|
case "raster":
|
||||||
return Object.entries(this.props.sources).filter(([_, v]) => v.type === 'raster').map(([k, _]) => k);
|
return Object.entries(this.props.sources).filter(([_, v]) => v.type === "raster").map(([k, _]) => k);
|
||||||
case 'heatmap':
|
case "heatmap":
|
||||||
case 'circle':
|
case "circle":
|
||||||
case 'fill':
|
case "fill":
|
||||||
case 'fill-extrusion':
|
case "fill-extrusion":
|
||||||
case 'line':
|
case "line":
|
||||||
case 'symbol':
|
case "symbol":
|
||||||
return Object.entries(this.props.sources).filter(([_, v]) => v.type === 'vector' || v.type === 'geojson').map(([k, _]) => k);
|
return Object.entries(this.props.sources).filter(([_, v]) => v.type === "vector" || v.type === "geojson").map(([k, _]) => k);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
return <Modal
|
return <Modal
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Add Layer')}
|
title={t("Add Layer")}
|
||||||
data-wd-key="modal:add-layer"
|
data-wd-key="modal:add-layer"
|
||||||
className="maputnik-add-modal"
|
className="maputnik-add-modal"
|
||||||
>
|
>
|
||||||
@@ -157,7 +157,7 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
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, error: null })
|
this.setState({ id: v, error: null });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FieldType
|
<FieldType
|
||||||
@@ -165,7 +165,7 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
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"
|
||||||
@@ -176,8 +176,8 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
{!NON_SOURCE_LAYERS.includes(this.state.type) &&
|
{!NON_SOURCE_LAYERS.includes(this.state.type) &&
|
||||||
<FieldSourceLayer
|
<FieldSourceLayer
|
||||||
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
|
||||||
@@ -188,7 +188,7 @@ class ModalAddInternal extends React.Component<ModalAddInternalProps, ModalAddSt
|
|||||||
{t("Add Layer")}
|
{t("Add Layer")}
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
|
|
||||||
|
|
||||||
type ModalDebugInternalProps = {
|
type ModalDebugInternalProps = {
|
||||||
@@ -34,29 +34,29 @@ class ModalDebugInternal extends React.Component<ModalDebugInternalProps> {
|
|||||||
data-wd-key="modal:debug"
|
data-wd-key="modal:debug"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Debug')}
|
title={t("Debug")}
|
||||||
>
|
>
|
||||||
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||||
<h1>{t("Options")}</h1>
|
<h1>{t("Options")}</h1>
|
||||||
{this.props.renderer === 'mlgljs' &&
|
{this.props.renderer === "mlgljs" &&
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(this.props.maplibreGlDebugOptions!).map(([key, val]) => {
|
{Object.entries(this.props.maplibreGlDebugOptions!).map(([key, val]) => {
|
||||||
return <li key={key}>
|
return <li key={key}>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" checked={val} onChange={(e) => this.props.onChangeMaplibreGlDebug(key, e.target.checked)} /> {key}
|
<input type="checkbox" checked={val} onChange={(e) => this.props.onChangeMaplibreGlDebug(key, e.target.checked)} /> {key}
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>;
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
{this.props.renderer === 'ol' &&
|
{this.props.renderer === "ol" &&
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(this.props.openlayersDebugOptions!).map(([key, val]) => {
|
{Object.entries(this.props.openlayersDebugOptions!).map(([key, val]) => {
|
||||||
return <li key={key}>
|
return <li key={key}>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" checked={val} onChange={(e) => this.props.onChangeOpenlayersDebug(key, e.target.checked)} /> {key}
|
<input type="checkbox" checked={val} onChange={(e) => this.props.onChangeOpenlayersDebug(key, e.target.checked)} /> {key}
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>;
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ class ModalDebugInternal extends React.Component<ModalDebugInternalProps> {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import Slugify from 'slugify'
|
import Slugify from "slugify";
|
||||||
import {saveAs} from 'file-saver'
|
import {saveAs} from "file-saver";
|
||||||
import {version} from 'maplibre-gl/package.json'
|
import {version} from "maplibre-gl/package.json";
|
||||||
import {format} from '@maplibre/maplibre-gl-style-spec'
|
import {format} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import {MdMap, MdSave} from 'react-icons/md'
|
import {MdMap, MdSave} from "react-icons/md";
|
||||||
import {WithTranslation, withTranslation} from 'react-i18next';
|
import {type WithTranslation, withTranslation} from "react-i18next";
|
||||||
|
|
||||||
import FieldString from '../FieldString'
|
import FieldString from "../FieldString";
|
||||||
import InputButton from '../InputButton'
|
import InputButton from "../InputButton";
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
import style from '../../libs/style'
|
import style from "../../libs/style";
|
||||||
import fieldSpecAdditional from '../../libs/field-spec-additional'
|
import fieldSpecAdditional from "../../libs/field-spec-additional";
|
||||||
import type {OnStyleChangedCallback, StyleSpecificationWithId} from '../../libs/definitions'
|
import type {OnStyleChangedCallback, StyleSpecificationWithId} from "../../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
const MAPLIBRE_GL_VERSION = version;
|
const MAPLIBRE_GL_VERSION = version;
|
||||||
@@ -41,12 +41,12 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
exportName() {
|
exportName() {
|
||||||
if (this.props.mapStyle.name) {
|
if (this.props.mapStyle.name) {
|
||||||
return Slugify(this.props.mapStyle.name, {
|
return Slugify(this.props.mapStyle.name, {
|
||||||
replacement: '_',
|
replacement: "_",
|
||||||
remove: /[*\-+~.()'"!:]/g,
|
remove: /[*\-+~.()'"!:]/g,
|
||||||
lower: true
|
lower: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return this.props.mapStyle.id
|
return this.props.mapStyle.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
let fileHandle = this.props.fileHandle;
|
let fileHandle = this.props.fileHandle;
|
||||||
if (fileHandle == null) {
|
if (fileHandle == null) {
|
||||||
fileHandle = await this.createFileHandle();
|
fileHandle = await this.createFileHandle();
|
||||||
this.props.onSetFileHandle(fileHandle)
|
this.props.onSetFileHandle(fileHandle);
|
||||||
if (fileHandle == null) return;
|
if (fileHandle == null) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
const tokenStyle = this.tokenizedStyle();
|
const tokenStyle = this.tokenizedStyle();
|
||||||
|
|
||||||
const fileHandle = await this.createFileHandle();
|
const fileHandle = await this.createFileHandle();
|
||||||
this.props.onSetFileHandle(fileHandle)
|
this.props.onSetFileHandle(fileHandle);
|
||||||
if (fileHandle == null) return;
|
if (fileHandle == null) return;
|
||||||
|
|
||||||
const writable = await fileHandle.createWritable();
|
const writable = await fileHandle.createWritable();
|
||||||
@@ -134,7 +134,7 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fileHandle = await window.showSaveFilePicker(pickerOpts) as FileSystemFileHandle;
|
const fileHandle = await window.showSaveFilePicker(pickerOpts) as FileSystemFileHandle;
|
||||||
this.props.onSetFileHandle(fileHandle)
|
this.props.onSetFileHandle(fileHandle);
|
||||||
return fileHandle;
|
return fileHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +145,8 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
...this.props.mapStyle.metadata as any,
|
...this.props.mapStyle.metadata as any,
|
||||||
[property]: value
|
[property]: value
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
this.props.onStyleChanged(changedStyle)
|
this.props.onStyleChanged(changedStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
data-wd-key="modal:export"
|
data-wd-key="modal:export"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Save Style')}
|
title={t("Save Style")}
|
||||||
className="maputnik-export-modal"
|
className="maputnik-export-modal"
|
||||||
>
|
>
|
||||||
|
|
||||||
@@ -171,25 +171,25 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.maptiler_access_token.label}
|
label={fsa.maputnik.maptiler_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.maptiler_access_token}
|
fieldSpec={fsa.maputnik.maptiler_access_token}
|
||||||
value={(this.props.mapStyle.metadata || {} as any)['maputnik:openmaptiles_access_token']}
|
value={(this.props.mapStyle.metadata || {} as any)["maputnik:openmaptiles_access_token"]}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
||||||
/>
|
/>
|
||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.thunderforest_access_token.label}
|
label={fsa.maputnik.thunderforest_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.thunderforest_access_token}
|
fieldSpec={fsa.maputnik.thunderforest_access_token}
|
||||||
value={(this.props.mapStyle.metadata || {} as any)['maputnik:thunderforest_access_token']}
|
value={(this.props.mapStyle.metadata || {} as any)["maputnik:thunderforest_access_token"]}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
||||||
/>
|
/>
|
||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.stadia_access_token.label}
|
label={fsa.maputnik.stadia_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.stadia_access_token}
|
fieldSpec={fsa.maputnik.stadia_access_token}
|
||||||
value={(this.props.mapStyle.metadata || {} as any)['maputnik:stadia_access_token']}
|
value={(this.props.mapStyle.metadata || {} as any)["maputnik:stadia_access_token"]}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:stadia_access_token")}
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:stadia_access_token")}
|
||||||
/>
|
/>
|
||||||
<FieldString
|
<FieldString
|
||||||
label={fsa.maputnik.locationiq_access_token.label}
|
label={fsa.maputnik.locationiq_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.locationiq_access_token}
|
fieldSpec={fsa.maputnik.locationiq_access_token}
|
||||||
value={(this.props.mapStyle.metadata || {} as any)['maputnik:locationiq_access_token']}
|
value={(this.props.mapStyle.metadata || {} as any)["maputnik:locationiq_access_token"]}
|
||||||
onChange={this.changeMetadataProperty.bind(this, "maputnik:locationiq_access_token")}
|
onChange={this.changeMetadataProperty.bind(this, "maputnik:locationiq_access_token")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -213,7 +213,7 @@ class ModalExportInternal extends React.Component<ModalExportInternalProps> {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</Modal>
|
</Modal>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import InputButton from '../InputButton'
|
import InputButton from "../InputButton";
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
|
|
||||||
|
|
||||||
type ModalLoadingInternalProps = {
|
type ModalLoadingInternalProps = {
|
||||||
@@ -31,7 +31,7 @@ class ModalLoadingInternal extends React.Component<ModalLoadingInternalProps> {
|
|||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</p>
|
</p>
|
||||||
</Modal>
|
</Modal>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React, { FormEvent } from 'react'
|
import React, { type FormEvent } from "react";
|
||||||
import {MdFileUpload} from 'react-icons/md'
|
import {MdFileUpload} from "react-icons/md";
|
||||||
import {MdAddCircleOutline} from 'react-icons/md'
|
import {MdAddCircleOutline} from "react-icons/md";
|
||||||
import FileReaderInput, { Result } from 'react-file-reader-input'
|
import FileReaderInput, { type Result } from "react-file-reader-input";
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import ModalLoading from './ModalLoading'
|
import ModalLoading from "./ModalLoading";
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
import InputButton from '../InputButton'
|
import InputButton from "../InputButton";
|
||||||
import InputUrl from '../InputUrl'
|
import InputUrl from "../InputUrl";
|
||||||
|
|
||||||
import style from '../../libs/style'
|
import style from "../../libs/style";
|
||||||
import publicStyles from '../../config/styles.json'
|
import publicStyles from "../../config/styles.json";
|
||||||
|
|
||||||
type PublicStyleProps = {
|
type PublicStyleProps = {
|
||||||
url: string
|
url: string
|
||||||
@@ -39,7 +39,7 @@ class PublicStyle extends React.Component<PublicStyleProps> {
|
|||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
clearError() {
|
clearError() {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: null
|
error: null
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancelActiveRequest(e: Event) {
|
onCancelActiveRequest(e: Event) {
|
||||||
@@ -90,7 +90,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
let canceled: boolean = false;
|
let canceled: boolean = false;
|
||||||
|
|
||||||
fetch(styleUrl, {
|
fetch(styleUrl, {
|
||||||
mode: 'cors',
|
mode: "cors",
|
||||||
credentials: "same-origin"
|
credentials: "same-origin"
|
||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
@@ -106,10 +106,10 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
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({
|
||||||
@@ -118,8 +118,8 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
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: {
|
||||||
@@ -128,13 +128,13 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
activeRequestUrl: styleUrl
|
activeRequestUrl: styleUrl
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onSubmitUrl = (e: FormEvent<HTMLFormElement>) => {
|
onSubmitUrl = (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onStyleSelect(this.state.styleUrl);
|
this.onStyleSelect(this.state.styleUrl);
|
||||||
}
|
};
|
||||||
|
|
||||||
onOpenFile = async () => {
|
onOpenFile = async () => {
|
||||||
this.clearError();
|
this.clearError();
|
||||||
@@ -155,19 +155,19 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
|
|
||||||
let mapStyle;
|
let mapStyle;
|
||||||
try {
|
try {
|
||||||
mapStyle = JSON.parse(content)
|
mapStyle = JSON.parse(content);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: (err as Error).toString()
|
error: (err as Error).toString()
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mapStyle = style.ensureStyleValidity(mapStyle)
|
mapStyle = style.ensureStyleValidity(mapStyle);
|
||||||
|
|
||||||
this.props.onStyleOpen(mapStyle, fileHandle);
|
this.props.onStyleOpen(mapStyle, fileHandle);
|
||||||
this.onOpenToggle();
|
this.onOpenToggle();
|
||||||
return file;
|
return file;
|
||||||
}
|
};
|
||||||
|
|
||||||
// it is not guaranteed that the File System Access API is available on all
|
// it is not guaranteed that the File System Access API is available on all
|
||||||
// browsers. If the function is not available, a fallback behavior is used.
|
// browsers. If the function is not available, a fallback behavior is used.
|
||||||
@@ -180,7 +180,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
let mapStyle;
|
let mapStyle;
|
||||||
try {
|
try {
|
||||||
mapStyle = JSON.parse(e.target?.result as string)
|
mapStyle = JSON.parse(e.target?.result as string);
|
||||||
}
|
}
|
||||||
catch(err) {
|
catch(err) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -188,12 +188,12 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mapStyle = style.ensureStyleValidity(mapStyle)
|
mapStyle = style.ensureStyleValidity(mapStyle);
|
||||||
this.props.onStyleOpen(mapStyle);
|
this.props.onStyleOpen(mapStyle);
|
||||||
this.onOpenToggle();
|
this.onOpenToggle();
|
||||||
}
|
};
|
||||||
reader.onerror = e => console.log(e.target);
|
reader.onerror = e => console.log(e.target);
|
||||||
}
|
};
|
||||||
|
|
||||||
onOpenToggle() {
|
onOpenToggle() {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -207,7 +207,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
this.setState({
|
this.setState({
|
||||||
styleUrl: url,
|
styleUrl: url,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
@@ -218,8 +218,8 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
title={style.title}
|
title={style.title}
|
||||||
thumbnailUrl={style.thumbnail}
|
thumbnailUrl={style.thumbnail}
|
||||||
onSelect={this.onStyleSelect}
|
onSelect={this.onStyleSelect}
|
||||||
/>
|
/>;
|
||||||
})
|
});
|
||||||
|
|
||||||
let errorElement;
|
let errorElement;
|
||||||
if(this.state.error) {
|
if(this.state.error) {
|
||||||
@@ -237,7 +237,7 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
data-wd-key="modal:open"
|
data-wd-key="modal:open"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={() => this.onOpenToggle()}
|
onOpenToggle={() => this.onOpenToggle()}
|
||||||
title={t('Open Style')}
|
title={t("Open Style")}
|
||||||
>
|
>
|
||||||
{errorElement}
|
{errorElement}
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
@@ -299,12 +299,12 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
|
|
||||||
<ModalLoading
|
<ModalLoading
|
||||||
isOpen={!!this.state.activeRequest}
|
isOpen={!!this.state.activeRequest}
|
||||||
title={t('Loading style')}
|
title={t("Loading style")}
|
||||||
onCancel={(e: Event) => this.onCancelActiveRequest(e)}
|
onCancel={(e: Event) => this.onCancelActiveRequest(e)}
|
||||||
message={t("Loading: {{requestUrl}}", { requestUrl: this.state.activeRequestUrl })}
|
message={t("Loading: {{requestUrl}}", { requestUrl: this.state.activeRequestUrl })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
import type {LightSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from 'maplibre-gl'
|
import type {LightSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from "maplibre-gl";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import FieldArray from '../FieldArray'
|
import FieldArray from "../FieldArray";
|
||||||
import FieldNumber from '../FieldNumber'
|
import FieldNumber from "../FieldNumber";
|
||||||
import FieldString from '../FieldString'
|
import FieldString from "../FieldString";
|
||||||
import FieldUrl from '../FieldUrl'
|
import FieldUrl from "../FieldUrl";
|
||||||
import FieldSelect from '../FieldSelect'
|
import FieldSelect from "../FieldSelect";
|
||||||
import FieldEnum from '../FieldEnum'
|
import FieldEnum from "../FieldEnum";
|
||||||
import FieldColor from '../FieldColor'
|
import FieldColor from "../FieldColor";
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
import fieldSpecAdditional from '../../libs/field-spec-additional'
|
import fieldSpecAdditional from "../../libs/field-spec-additional";
|
||||||
import type {OnStyleChangedCallback, StyleSpecificationWithId} from '../../libs/definitions';
|
import type {OnStyleChangedCallback, StyleSpecificationWithId} from "../../libs/definitions";
|
||||||
|
|
||||||
type ModalSettingsInternalProps = {
|
type ModalSettingsInternalProps = {
|
||||||
mapStyle: StyleSpecificationWithId
|
mapStyle: StyleSpecificationWithId
|
||||||
@@ -26,7 +26,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
changeTransitionProperty(property: keyof TransitionSpecification, value: number | undefined) {
|
changeTransitionProperty(property: keyof TransitionSpecification, value: number | undefined) {
|
||||||
const transition = {
|
const transition = {
|
||||||
...this.props.mapStyle.transition,
|
...this.props.mapStyle.transition,
|
||||||
}
|
};
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
delete transition[property];
|
delete transition[property];
|
||||||
@@ -44,7 +44,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
changeLightProperty(property: keyof LightSpecification, value: any) {
|
changeLightProperty(property: keyof LightSpecification, value: any) {
|
||||||
const light = {
|
const light = {
|
||||||
...this.props.mapStyle.light,
|
...this.props.mapStyle.light,
|
||||||
}
|
};
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
delete light[property];
|
delete light[property];
|
||||||
@@ -108,7 +108,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
data-wd-key="modal:settings"
|
data-wd-key="modal:settings"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Style Settings')}
|
title={t("Style Settings")}
|
||||||
>
|
>
|
||||||
<div className="modal:settings">
|
<div className="modal:settings">
|
||||||
<FieldString
|
<FieldString
|
||||||
@@ -145,7 +145,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
label={fsa.maputnik.maptiler_access_token.label}
|
label={fsa.maputnik.maptiler_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.maptiler_access_token}
|
fieldSpec={fsa.maputnik.maptiler_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:openmaptiles_access_token"
|
data-wd-key="modal:settings.maputnik:openmaptiles_access_token"
|
||||||
value={metadata['maputnik:openmaptiles_access_token']}
|
value={metadata["maputnik:openmaptiles_access_token"]}
|
||||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
label={fsa.maputnik.thunderforest_access_token.label}
|
label={fsa.maputnik.thunderforest_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.thunderforest_access_token}
|
fieldSpec={fsa.maputnik.thunderforest_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:thunderforest_access_token"
|
data-wd-key="modal:settings.maputnik:thunderforest_access_token"
|
||||||
value={metadata['maputnik:thunderforest_access_token']}
|
value={metadata["maputnik:thunderforest_access_token"]}
|
||||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
label={fsa.maputnik.stadia_access_token.label}
|
label={fsa.maputnik.stadia_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.stadia_access_token}
|
fieldSpec={fsa.maputnik.stadia_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:stadia_access_token"
|
data-wd-key="modal:settings.maputnik:stadia_access_token"
|
||||||
value={metadata['maputnik:stadia_access_token']}
|
value={metadata["maputnik:stadia_access_token"]}
|
||||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:stadia_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:stadia_access_token")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
label={fsa.maputnik.locationiq_access_token.label}
|
label={fsa.maputnik.locationiq_access_token.label}
|
||||||
fieldSpec={fsa.maputnik.locationiq_access_token}
|
fieldSpec={fsa.maputnik.locationiq_access_token}
|
||||||
data-wd-key="modal:settings.maputnik:locationiq_access_token"
|
data-wd-key="modal:settings.maputnik:locationiq_access_token"
|
||||||
value={metadata['maputnik:locationiq_access_token']}
|
value={metadata["maputnik:locationiq_access_token"]}
|
||||||
onChange={onChangeMetadataProperty.bind(this, "maputnik:locationiq_access_token")}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:locationiq_access_token")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -280,16 +280,16 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={fsa.maputnik.style_renderer}
|
fieldSpec={fsa.maputnik.style_renderer}
|
||||||
data-wd-key="modal:settings.maputnik:renderer"
|
data-wd-key="modal:settings.maputnik:renderer"
|
||||||
options={[
|
options={[
|
||||||
['mlgljs', 'MapLibreGL JS'],
|
["mlgljs", "MapLibreGL JS"],
|
||||||
['ol', t('Open Layers (experimental)')],
|
["ol", t("Open Layers (experimental)")],
|
||||||
]}
|
]}
|
||||||
value={metadata['maputnik:renderer'] || 'mlgljs'}
|
value={metadata["maputnik:renderer"] || "mlgljs"}
|
||||||
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
|
onChange={onChangeMetadataProperty.bind(this, "maputnik:renderer")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalSettings = withTranslation()(ModalSettingsInternal)
|
const ModalSettings = withTranslation()(ModalSettingsInternal);
|
||||||
export default ModalSettings;
|
export default ModalSettings;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { Trans, WithTranslation, withTranslation } from 'react-i18next';
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
|
|
||||||
|
|
||||||
type ModalShortcutsInternalProps = {
|
type ModalShortcutsInternalProps = {
|
||||||
@@ -46,7 +46,7 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
key: <kbd>!</kbd>,
|
key: <kbd>!</kbd>,
|
||||||
text: t("Debug modal")
|
text: t("Debug modal")
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
|
|
||||||
const mapShortcuts = [
|
const mapShortcuts = [
|
||||||
@@ -98,14 +98,14 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
key: <><kbd>Shift</kbd> + <kbd>Down</kbd></>,
|
key: <><kbd>Shift</kbd> + <kbd>Down</kbd></>,
|
||||||
text: t("Decrease the pitch by 10 degrees.")
|
text: t("Decrease the pitch by 10 degrees.")
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal:shortcuts"
|
data-wd-key="modal:shortcuts"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Shortcuts')}
|
title={t("Shortcuts")}
|
||||||
>
|
>
|
||||||
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
<section className="maputnik-modal-section maputnik-modal-shortcuts">
|
||||||
<p>
|
<p>
|
||||||
@@ -118,7 +118,7 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
return <div key={idx} className="maputnik-modal-shortcuts__shortcut">
|
return <div key={idx} className="maputnik-modal-shortcuts__shortcut">
|
||||||
<dt key={"dt"+idx}>{item.key}</dt>
|
<dt key={"dt"+idx}>{item.key}</dt>
|
||||||
<dd key={"dd"+idx}>{item.text}</dd>
|
<dd key={"dd"+idx}>{item.text}</dd>
|
||||||
</div>
|
</div>;
|
||||||
})}
|
})}
|
||||||
</dl>
|
</dl>
|
||||||
<p>{t("If the Map is in focused you can use the following shortcuts")}</p>
|
<p>{t("If the Map is in focused you can use the following shortcuts")}</p>
|
||||||
@@ -126,11 +126,11 @@ class ModalShortcutsInternal extends React.Component<ModalShortcutsInternalProps
|
|||||||
{mapShortcuts.map((item, idx) => {
|
{mapShortcuts.map((item, idx) => {
|
||||||
return <li key={idx}>
|
return <li key={idx}>
|
||||||
<span>{item.key}</span> {item.text}
|
<span>{item.key}</span> {item.text}
|
||||||
</li>
|
</li>;
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {MdAddCircleOutline, MdDelete} from 'react-icons/md'
|
import {MdAddCircleOutline, MdDelete} from "react-icons/md";
|
||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, VectorSourceSpecification} from 'maplibre-gl'
|
import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, VectorSourceSpecification} from "maplibre-gl";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from "./Modal";
|
||||||
import InputButton from '../InputButton'
|
import InputButton from "../InputButton";
|
||||||
import FieldString from '../FieldString'
|
import FieldString from "../FieldString";
|
||||||
import FieldSelect from '../FieldSelect'
|
import FieldSelect from "../FieldSelect";
|
||||||
import ModalSourcesTypeEditor, { EditorMode } from './ModalSourcesTypeEditor'
|
import ModalSourcesTypeEditor, { type EditorMode } from "./ModalSourcesTypeEditor";
|
||||||
|
|
||||||
import style from '../../libs/style'
|
import style from "../../libs/style";
|
||||||
import { deleteSource, addSource, changeSource } from '../../libs/source'
|
import { deleteSource, addSource, changeSource } from "../../libs/source";
|
||||||
import publicSources from '../../config/tilesets.json'
|
import publicSources from "../../config/tilesets.json";
|
||||||
import { OnStyleChangedCallback, StyleSpecificationWithId } from '../../libs/definitions';
|
import { type OnStyleChangedCallback, type StyleSpecificationWithId } from "../../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
type PublicSourceProps = {
|
type PublicSourceProps = {
|
||||||
@@ -37,39 +37,39 @@ class PublicSource extends React.Component<PublicSourceProps> {
|
|||||||
<span className="maputnik-space" />
|
<span className="maputnik-space" />
|
||||||
<MdAddCircleOutline />
|
<MdAddCircleOutline />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function editorMode(source: SourceSpecification) {
|
function editorMode(source: SourceSpecification) {
|
||||||
if(source.type === 'raster') {
|
if(source.type === "raster") {
|
||||||
if(source.tiles) return 'tile_raster'
|
if(source.tiles) return "tile_raster";
|
||||||
return 'tilejson_raster'
|
return "tilejson_raster";
|
||||||
}
|
}
|
||||||
if(source.type === 'raster-dem') {
|
if(source.type === "raster-dem") {
|
||||||
if(source.tiles) return 'tilexyz_raster-dem'
|
if(source.tiles) return "tilexyz_raster-dem";
|
||||||
return 'tilejson_raster-dem'
|
return "tilejson_raster-dem";
|
||||||
}
|
}
|
||||||
if(source.type === 'vector') {
|
if(source.type === "vector") {
|
||||||
if(source.tiles) return 'tile_vector'
|
if(source.tiles) return "tile_vector";
|
||||||
if(source.url && source.url.startsWith("pmtiles://")) return 'pmtiles_vector'
|
if(source.url && source.url.startsWith("pmtiles://")) return "pmtiles_vector";
|
||||||
return 'tilejson_vector'
|
return "tilejson_vector";
|
||||||
}
|
}
|
||||||
if(source.type === 'geojson') {
|
if(source.type === "geojson") {
|
||||||
if (typeof(source.data) === "string") {
|
if (typeof(source.data) === "string") {
|
||||||
return 'geojson_url';
|
return "geojson_url";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return 'geojson_json';
|
return "geojson_json";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(source.type === 'image') {
|
if(source.type === "image") {
|
||||||
return 'image';
|
return "image";
|
||||||
}
|
}
|
||||||
if(source.type === 'video') {
|
if(source.type === "video") {
|
||||||
return 'video';
|
return "video";
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActiveModalSourcesTypeEditorProps = {
|
type ActiveModalSourcesTypeEditorProps = {
|
||||||
@@ -90,7 +90,7 @@ class ActiveModalSourcesTypeEditor extends React.Component<ActiveModalSourcesTyp
|
|||||||
aria-label={t("Remove '{{sourceId}}' source", {sourceId: this.props.sourceId})}
|
aria-label={t("Remove '{{sourceId}}' source", {sourceId: this.props.sourceId})}
|
||||||
className="maputnik-active-source-type-editor-header-delete"
|
className="maputnik-active-source-type-editor-header-delete"
|
||||||
onClick={()=> this.props.onDelete(this.props.sourceId)}
|
onClick={()=> this.props.onDelete(this.props.sourceId)}
|
||||||
style={{backgroundColor: 'transparent'}}
|
style={{backgroundColor: "transparent"}}
|
||||||
>
|
>
|
||||||
<MdDelete />
|
<MdDelete />
|
||||||
</InputButton>
|
</InputButton>
|
||||||
@@ -102,7 +102,7 @@ class ActiveModalSourcesTypeEditor extends React.Component<ActiveModalSourcesTyp
|
|||||||
source={this.props.source}
|
source={this.props.source}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,100 +118,100 @@ type AddSourceState = {
|
|||||||
|
|
||||||
class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
||||||
constructor(props: AddSourceProps) {
|
constructor(props: AddSourceProps) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
mode: 'tilejson_vector',
|
mode: "tilejson_vector",
|
||||||
sourceId: style.generateId(),
|
sourceId: style.generateId(),
|
||||||
source: this.defaultSource('tilejson_vector'),
|
source: this.defaultSource("tilejson_vector"),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultSource(mode: EditorMode): SourceSpecification {
|
defaultSource(mode: EditorMode): SourceSpecification {
|
||||||
const source = (this.state || {}).source || {}
|
const source = (this.state || {}).source || {};
|
||||||
const {protocol} = window.location;
|
const {protocol} = window.location;
|
||||||
|
|
||||||
switch(mode) {
|
switch(mode) {
|
||||||
case 'pmtiles_vector': return {
|
case "pmtiles_vector": return {
|
||||||
type: 'vector',
|
type: "vector",
|
||||||
url: `${protocol}//localhost:3000/file.pmtiles`
|
url: `${protocol}//localhost:3000/file.pmtiles`
|
||||||
}
|
};
|
||||||
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 'tile_vector': return {
|
case "tile_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,
|
||||||
scheme: (source as VectorSourceSpecification).scheme || 'xyz'
|
scheme: (source as VectorSourceSpecification).scheme || "xyz"
|
||||||
}
|
};
|
||||||
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 'tile_raster': return {
|
case "tile_raster": return {
|
||||||
type: 'raster',
|
type: "raster",
|
||||||
tiles: (source as RasterSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.png`],
|
tiles: (source as RasterSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.png`],
|
||||||
minzoom: (source as RasterSourceSpecification).minzoom || 0,
|
minzoom: (source as RasterSourceSpecification).minzoom || 0,
|
||||||
maxzoom: (source as RasterSourceSpecification).maxzoom || 14,
|
maxzoom: (source as RasterSourceSpecification).maxzoom || 14,
|
||||||
scheme: (source as RasterSourceSpecification).scheme || 'xyz',
|
scheme: (source as RasterSourceSpecification).scheme || "xyz",
|
||||||
tileSize: (source as RasterSourceSpecification).tileSize || 512,
|
tileSize: (source as RasterSourceSpecification).tileSize || 512,
|
||||||
}
|
};
|
||||||
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}.png`],
|
tiles: (source as RasterDEMSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.png`],
|
||||||
minzoom: (source as RasterDEMSourceSpecification).minzoom || 0,
|
minzoom: (source as RasterDEMSourceSpecification).minzoom || 0,
|
||||||
maxzoom: (source as RasterDEMSourceSpecification).maxzoom || 14,
|
maxzoom: (source as RasterDEMSourceSpecification).maxzoom || 14,
|
||||||
tileSize: (source as RasterDEMSourceSpecification).tileSize || 512
|
tileSize: (source as RasterDEMSourceSpecification).tileSize || 512
|
||||||
}
|
};
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd = () => {
|
onAdd = () => {
|
||||||
const {source, sourceId} = this.state;
|
const {source, sourceId} = this.state;
|
||||||
this.props.onAdd(sourceId, source);
|
this.props.onAdd(sourceId, source);
|
||||||
}
|
};
|
||||||
|
|
||||||
onChangeSource = (source: SourceSpecification) => {
|
onChangeSource = (source: SourceSpecification) => {
|
||||||
this.setState({source});
|
this.setState({source});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
@@ -238,17 +238,17 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
label={t("Source Type")}
|
label={t("Source Type")}
|
||||||
fieldSpec={sourceTypeFieldSpec}
|
fieldSpec={sourceTypeFieldSpec}
|
||||||
options={[
|
options={[
|
||||||
['geojson_json', t('GeoJSON (JSON)')],
|
["geojson_json", t("GeoJSON (JSON)")],
|
||||||
['geojson_url', t('GeoJSON (URL)')],
|
["geojson_url", t("GeoJSON (URL)")],
|
||||||
['tilejson_vector', t('Vector (TileJSON URL)')],
|
["tilejson_vector", t("Vector (TileJSON URL)")],
|
||||||
['tile_vector', t('Vector (Tile URLs)')],
|
["tile_vector", t("Vector (Tile URLs)")],
|
||||||
['tilejson_raster', t('Raster (TileJSON URL)')],
|
["tilejson_raster", t("Raster (TileJSON URL)")],
|
||||||
['tile_raster', t('Raster (Tile URLs)')],
|
["tile_raster", t("Raster (Tile URLs)")],
|
||||||
['tilejson_raster-dem', t('Raster DEM (TileJSON URL)')],
|
["tilejson_raster-dem", t("Raster DEM (TileJSON URL)")],
|
||||||
['tilexyz_raster-dem', t('Raster DEM (XYZ URLs)')],
|
["tilexyz_raster-dem", t("Raster DEM (XYZ URLs)")],
|
||||||
['pmtiles_vector', t('Vector (PMTiles)')],
|
["pmtiles_vector", t("Vector (PMTiles)")],
|
||||||
['image', t('Image')],
|
["image", t("Image")],
|
||||||
['video', t('Video')],
|
["video", t("Video")],
|
||||||
]}
|
]}
|
||||||
onChange={mode => this.setState({mode: mode as EditorMode, source: this.defaultSource(mode as EditorMode)})}
|
onChange={mode => this.setState({mode: mode as EditorMode, source: this.defaultSource(mode as EditorMode)})}
|
||||||
value={this.state.mode as string}
|
value={this.state.mode as string}
|
||||||
@@ -266,7 +266,7 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
|||||||
>
|
>
|
||||||
{t("Add Source")}
|
{t("Add Source")}
|
||||||
</InputButton>
|
</InputButton>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,16 +279,16 @@ type ModalSourcesInternalProps = {
|
|||||||
|
|
||||||
class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
||||||
stripTitle(source: SourceSpecification & {title?: string}): SourceSpecification {
|
stripTitle(source: SourceSpecification & {title?: string}): SourceSpecification {
|
||||||
const strippedSource = {...source}
|
const strippedSource = {...source};
|
||||||
delete strippedSource['title']
|
delete strippedSource["title"];
|
||||||
return strippedSource
|
return strippedSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {t, mapStyle} = this.props;
|
const {t, mapStyle} = this.props;
|
||||||
const i18nProps = {t, i18n: this.props.i18n, tReady: this.props.tReady};
|
const i18nProps = {t, i18n: this.props.i18n, tReady: this.props.tReady};
|
||||||
const activeSources = Object.keys(mapStyle.sources).map(sourceId => {
|
const activeSources = Object.keys(mapStyle.sources).map(sourceId => {
|
||||||
const source = mapStyle.sources[sourceId]
|
const source = mapStyle.sources[sourceId];
|
||||||
return <ActiveModalSourcesTypeEditor
|
return <ActiveModalSourcesTypeEditor
|
||||||
key={sourceId}
|
key={sourceId}
|
||||||
sourceId={sourceId}
|
sourceId={sourceId}
|
||||||
@@ -296,8 +296,8 @@ class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
|||||||
onChange={(src: SourceSpecification) => this.props.onStyleChanged(changeSource(mapStyle, sourceId, src))}
|
onChange={(src: SourceSpecification) => this.props.onStyleChanged(changeSource(mapStyle, sourceId, src))}
|
||||||
onDelete={() => this.props.onStyleChanged(deleteSource(mapStyle, sourceId))}
|
onDelete={() => this.props.onStyleChanged(deleteSource(mapStyle, sourceId))}
|
||||||
{...i18nProps}
|
{...i18nProps}
|
||||||
/>
|
/>;
|
||||||
})
|
});
|
||||||
|
|
||||||
const tilesetOptions = Object.keys(publicSources).filter((sourceId: string) => !(sourceId in mapStyle.sources)).map((sourceId: string) => {
|
const tilesetOptions = Object.keys(publicSources).filter((sourceId: string) => !(sourceId in mapStyle.sources)).map((sourceId: string) => {
|
||||||
const source = publicSources[sourceId as keyof typeof publicSources] as SourceSpecification & {title: string};
|
const source = publicSources[sourceId as keyof typeof publicSources] as SourceSpecification & {title: string};
|
||||||
@@ -307,14 +307,14 @@ class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
|||||||
type={source.type}
|
type={source.type}
|
||||||
title={source.title}
|
title={source.title}
|
||||||
onSelect={() => this.props.onStyleChanged(addSource(mapStyle, sourceId, this.stripTitle(source)))}
|
onSelect={() => this.props.onStyleChanged(addSource(mapStyle, sourceId, this.stripTitle(source)))}
|
||||||
/>
|
/>;
|
||||||
})
|
});
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal:sources"
|
data-wd-key="modal:sources"
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onOpenToggle={this.props.onOpenToggle}
|
onOpenToggle={this.props.onOpenToggle}
|
||||||
title={t('Sources')}
|
title={t("Sources")}
|
||||||
>
|
>
|
||||||
<section className="maputnik-modal-section">
|
<section className="maputnik-modal-section">
|
||||||
<h1>{t("Active Sources")}</h1>
|
<h1>{t("Active Sources")}</h1>
|
||||||
@@ -339,7 +339,7 @@ class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
|||||||
{...i18nProps}
|
{...i18nProps}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</Modal>
|
</Modal>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import {latest} from '@maplibre/maplibre-gl-style-spec'
|
import {latest} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { TFunction } from 'i18next'
|
import { type TFunction } from "i18next";
|
||||||
|
|
||||||
import Block from '../Block'
|
import Block from "../Block";
|
||||||
import FieldUrl from '../FieldUrl'
|
import FieldUrl from "../FieldUrl";
|
||||||
import FieldNumber from '../FieldNumber'
|
import FieldNumber from "../FieldNumber";
|
||||||
import FieldSelect from '../FieldSelect'
|
import FieldSelect from "../FieldSelect";
|
||||||
import FieldDynamicArray from '../FieldDynamicArray'
|
import FieldDynamicArray from "../FieldDynamicArray";
|
||||||
import FieldArray from '../FieldArray'
|
import FieldArray from "../FieldArray";
|
||||||
import FieldJson from '../FieldJson'
|
import FieldJson from "../FieldJson";
|
||||||
import FieldCheckbox from '../FieldCheckbox'
|
import FieldCheckbox from "../FieldCheckbox";
|
||||||
|
|
||||||
|
|
||||||
export type EditorMode = "video" | "image" | "tilejson_vector" | "tile_raster" | "tilejson_raster" | "tilexyz_raster-dem" | "tilejson_raster-dem" | "pmtiles_vector" | "tile_vector" | "geojson_url" | "geojson_json" | null;
|
export type EditorMode = "video" | "image" | "tilejson_vector" | "tile_raster" | "tilejson_raster" | "tilexyz_raster-dem" | "tilejson_raster-dem" | "pmtiles_vector" | "tile_vector" | "geojson_url" | "geojson_json" | null;
|
||||||
@@ -38,7 +38,7 @@ class TileJSONSourceEditor extends React.Component<TileJSONSourceEditorProps> {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ type TileURLSourceEditorProps = {
|
|||||||
tiles: string[]
|
tiles: string[]
|
||||||
minzoom: number
|
minzoom: number
|
||||||
maxzoom: number
|
maxzoom: number
|
||||||
scheme: 'xyz' | 'tms'
|
scheme: "xyz" | "tms"
|
||||||
}
|
}
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
@@ -58,7 +58,7 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
tiles,
|
tiles,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTileUrls() {
|
renderTileUrls() {
|
||||||
@@ -69,7 +69,7 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
type="url"
|
type="url"
|
||||||
value={tiles}
|
value={tiles}
|
||||||
onChange={this.changeTileUrls.bind(this)}
|
onChange={this.changeTileUrls.bind(this)}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -80,8 +80,8 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
label={t("Scheme Type")}
|
label={t("Scheme Type")}
|
||||||
fieldSpec={latest.source_vector.scheme}
|
fieldSpec={latest.source_vector.scheme}
|
||||||
options={[
|
options={[
|
||||||
['xyz', 'xyz (Slippy map tilenames scheme)'],
|
["xyz", "xyz (Slippy map tilenames scheme)"],
|
||||||
['tms', 'tms (OSGeo spec scheme)'],
|
["tms", "tms (OSGeo spec scheme)"],
|
||||||
]}
|
]}
|
||||||
onChange={scheme => this.props.onChange({
|
onChange={scheme => this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
@@ -109,7 +109,7 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ class ImageSourceEditor extends React.Component<ImageSourceEditorProps> {
|
|||||||
...this.props.source,
|
...this.props.source,
|
||||||
coordinates,
|
coordinates,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<FieldUrl
|
<FieldUrl
|
||||||
@@ -165,7 +165,7 @@ class ImageSourceEditor extends React.Component<ImageSourceEditorProps> {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,14 +188,14 @@ class VideoSourceEditor extends React.Component<VideoSourceEditorProps> {
|
|||||||
...this.props.source,
|
...this.props.source,
|
||||||
coordinates,
|
coordinates,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const changeUrls = (urls: string[]) => {
|
const changeUrls = (urls: string[]) => {
|
||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
urls,
|
urls,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<FieldDynamicArray
|
<FieldDynamicArray
|
||||||
@@ -219,7 +219,7 @@ class VideoSourceEditor extends React.Component<VideoSourceEditorProps> {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ class GeoJSONSourceUrlEditor extends React.Component<GeoJSONSourceUrlEditorProps
|
|||||||
...this.props.source,
|
...this.props.source,
|
||||||
data: data
|
data: data
|
||||||
})}
|
})}
|
||||||
/>
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,21 +270,21 @@ class GeoJSONSourceFieldJsonEditor extends React.Component<GeoJSONSourceFieldJso
|
|||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
data,
|
data,
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
<FieldCheckbox
|
<FieldCheckbox
|
||||||
label={t('Cluster')}
|
label={t("Cluster")}
|
||||||
value={this.props.source.cluster}
|
value={this.props.source.cluster}
|
||||||
onChange={cluster => {
|
onChange={cluster => {
|
||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
cluster: cluster,
|
cluster: cluster,
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ class PMTilesSourceEditor extends React.Component<PMTilesSourceEditorProps> {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,50 +332,50 @@ class ModalSourcesTypeEditorInternal extends React.Component<ModalSourcesTypeEdi
|
|||||||
tReady: this.props.tReady,
|
tReady: this.props.tReady,
|
||||||
};
|
};
|
||||||
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 'tile_vector': return <TileURLSourceEditor {...commonProps} />
|
case "tile_vector": return <TileURLSourceEditor {...commonProps} />;
|
||||||
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
|
case "tilejson_raster": return <TileJSONSourceEditor {...commonProps} />;
|
||||||
case 'tile_raster': return <TileURLSourceEditor {...commonProps}>
|
case "tile_raster": return <TileURLSourceEditor {...commonProps}>
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Tile Size")}
|
label={t("Tile Size")}
|
||||||
fieldSpec={latest.source_raster.tileSize}
|
fieldSpec={latest.source_raster.tileSize}
|
||||||
onChange={tileSize => this.props.onChange({
|
onChange={tileSize => this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
tileSize: tileSize
|
tileSize: tileSize
|
||||||
})}
|
})}
|
||||||
value={this.props.source.tileSize || latest.source_raster.tileSize.default}
|
value={this.props.source.tileSize || latest.source_raster.tileSize.default}
|
||||||
data-wd-key="modal:sources.add.tile_size"
|
data-wd-key="modal:sources.add.tile_size"
|
||||||
/>
|
/>
|
||||||
</TileURLSourceEditor>
|
</TileURLSourceEditor>;
|
||||||
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}>
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
label={t("Tile Size")}
|
label={t("Tile Size")}
|
||||||
fieldSpec={latest.source_raster_dem.tileSize}
|
fieldSpec={latest.source_raster_dem.tileSize}
|
||||||
onChange={tileSize => this.props.onChange({
|
onChange={tileSize => this.props.onChange({
|
||||||
...this.props.source,
|
...this.props.source,
|
||||||
tileSize: tileSize
|
tileSize: tileSize
|
||||||
})}
|
})}
|
||||||
value={this.props.source.tileSize || latest.source_raster_dem.tileSize.default}
|
value={this.props.source.tileSize || latest.source_raster_dem.tileSize.default}
|
||||||
data-wd-key="modal:sources.add.tile_size"
|
data-wd-key="modal:sources.add.tile_size"
|
||||||
/>
|
/>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
label={t("Encoding")}
|
label={t("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 'pmtiles_vector': return <PMTilesSourceEditor {...commonProps} />
|
case "pmtiles_vector": return <PMTilesSourceEditor {...commonProps} />;
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { IconContext } from "react-icons";
|
import { IconContext } from "react-icons";
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
import './favicon.ico'
|
import "./favicon.ico";
|
||||||
import './styles/index.scss'
|
import "./styles/index.scss";
|
||||||
import './i18n';
|
import "./i18n";
|
||||||
import App from './components/App';
|
import App from "./components/App";
|
||||||
|
|
||||||
const root = createRoot(document.querySelector("#app"));
|
const root = createRoot(document.querySelector("#app"));
|
||||||
root.render(
|
root.render(
|
||||||
<IconContext.Provider value={{className: 'react-icons'}}>
|
<IconContext.Provider value={{className: "react-icons"}}>
|
||||||
<App/>
|
<App/>
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import throttle from 'lodash.throttle'
|
import throttle from "lodash.throttle";
|
||||||
|
|
||||||
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
|
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
|
||||||
const reducedMotionEnabled = throttle(() => {
|
const reducedMotionEnabled = throttle(() => {
|
||||||
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
}, 3000)
|
}, 3000);
|
||||||
|
|
||||||
export { reducedMotionEnabled }
|
export { reducedMotionEnabled };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {parse} from '@prantlf/jsonlint';
|
import {parse} from "@prantlf/jsonlint";
|
||||||
import CodeMirror, { MarkerRange } from 'codemirror';
|
import CodeMirror, { type MarkerRange } from "codemirror";
|
||||||
import jsonToAst from 'json-to-ast';
|
import jsonToAst from "json-to-ast";
|
||||||
import {expression, validateStyleMin} from '@maplibre/maplibre-gl-style-spec';
|
import {expression, validateStyleMin} from "@maplibre/maplibre-gl-style-spec";
|
||||||
|
|
||||||
type MarkerRangeWithMessage = MarkerRange & {message: string};
|
type MarkerRangeWithMessage = MarkerRange & {message: string};
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) =>
|
|||||||
newNode = newNode.value;
|
newNode = newNode.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getArrayPositionalFromAst(newNode, path.slice(1))
|
return getArrayPositionalFromAst(newNode, path.slice(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,12 +121,12 @@ CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) =>
|
|||||||
// Remove the 'layers[0].' as we're validating the layer only here
|
// Remove the 'layers[0].' as we're validating the layer only here
|
||||||
const errMessageParts = err.message.replace(/^layers\[0\]./, "").split(":");
|
const errMessageParts = err.message.replace(/^layers\[0\]./, "").split(":");
|
||||||
return {
|
return {
|
||||||
name: '',
|
name: "",
|
||||||
key: errMessageParts[0],
|
key: errMessageParts[0],
|
||||||
message: errMessageParts[1],
|
message: errMessageParts[1],
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (context === "expression") {
|
else if (context === "expression") {
|
||||||
@@ -147,11 +147,11 @@ CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) =>
|
|||||||
from: CodeMirror.Pos(doc.firstLine(), 0),
|
from: CodeMirror.Pos(doc.firstLine(), 0),
|
||||||
to: CodeMirror.Pos(doc.lastLine(), lastLineHandle.text.length),
|
to: CodeMirror.Pos(doc.lastLine(), lastLineHandle.text.length),
|
||||||
message: message,
|
message: message,
|
||||||
}
|
};
|
||||||
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);
|
||||||
@@ -167,7 +167,7 @@ CodeMirror.registerHelper("lint", "mgl", (text: string, opts: any, doc: any) =>
|
|||||||
message: message,
|
message: message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
|
|||||||
2
src/libs/definitions.d.ts
vendored
2
src/libs/definitions.d.ts
vendored
@@ -6,7 +6,7 @@ export type OnStyleChangedOpts = {
|
|||||||
save?: boolean;
|
save?: boolean;
|
||||||
addRevision?: boolean;
|
addRevision?: boolean;
|
||||||
initialLoad?: boolean;
|
initialLoad?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type OnStyleChangedCallback = (newStyle: StyleSpecificationWithId, opts: OnStyleChangedOpts={}) => void;
|
export type OnStyleChangedCallback = (newStyle: StyleSpecificationWithId, opts: OnStyleChangedOpts={}) => void;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import {diff} from '@maplibre/maplibre-gl-style-spec'
|
import {diff} from "@maplibre/maplibre-gl-style-spec";
|
||||||
import type {StyleSpecification} from 'maplibre-gl'
|
import type {StyleSpecification} from "maplibre-gl";
|
||||||
|
|
||||||
function diffMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
function diffMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
||||||
const changes = diff(beforeStyle, afterStyle)
|
const changes = diff(beforeStyle, afterStyle);
|
||||||
return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' '))
|
return changes.map(cmd => cmd.command + " " + cmd.args.join(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function undoMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
export function undoMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
||||||
return diffMessages(beforeStyle, afterStyle).map(m => 'Undo ' + m)
|
return diffMessages(beforeStyle, afterStyle).map(m => "Undo " + m);
|
||||||
}
|
}
|
||||||
export function redoMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
export function redoMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
|
||||||
return diffMessages(beforeStyle, afterStyle).map(m => 'Redo ' + m)
|
return diffMessages(beforeStyle, afterStyle).map(m => "Redo " + m);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TFunction } from "i18next";
|
import { type TFunction } from "i18next";
|
||||||
|
|
||||||
const spec = (t: TFunction) => ({
|
const spec = (t: TFunction) => ({
|
||||||
maputnik: {
|
maputnik: {
|
||||||
@@ -31,6 +31,6 @@ const spec = (t: TFunction) => ({
|
|||||||
doc: t("Choose the default Maputnik renderer for this style.")
|
doc: t("Choose the default Maputnik renderer for this style.")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
export default spec;
|
export default spec;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json'
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
|
|
||||||
export const combiningFilterOps = ['all', 'any', 'none'];
|
export const combiningFilterOps = ["all", "any", "none"];
|
||||||
export const setFilterOps = ['in', '!in'];
|
export const setFilterOps = ["in", "!in"];
|
||||||
export const otherFilterOps = Object
|
export const otherFilterOps = Object
|
||||||
.keys(latest.filter_operator.values)
|
.keys(latest.filter_operator.values)
|
||||||
.filter(op => combiningFilterOps.indexOf(op) < 0);
|
.filter(op => combiningFilterOps.indexOf(op) < 0);
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
import stylegen from '@maplibre/maplibre-gl-inspect/lib/stylegen'
|
import stylegen from "@maplibre/maplibre-gl-inspect/lib/stylegen";
|
||||||
import colors from '@maplibre/maplibre-gl-inspect/lib/colors'
|
import colors from "@maplibre/maplibre-gl-inspect/lib/colors";
|
||||||
import type {FilterSpecification,LayerSpecification } from 'maplibre-gl'
|
import type {FilterSpecification,LayerSpecification } from "maplibre-gl";
|
||||||
|
|
||||||
export type HighlightedLayer = LayerSpecification & {filter?: FilterSpecification};
|
export type HighlightedLayer = LayerSpecification & {filter?: FilterSpecification};
|
||||||
|
|
||||||
function changeLayer(l: HighlightedLayer, layer: LayerSpecification) {
|
function changeLayer(l: HighlightedLayer, layer: LayerSpecification) {
|
||||||
if(l.type === 'circle') {
|
if(l.type === "circle") {
|
||||||
l.paint!['circle-radius'] = 3
|
l.paint!["circle-radius"] = 3;
|
||||||
} else if(l.type === 'line') {
|
} else if(l.type === "line") {
|
||||||
l.paint!['line-width'] = 2
|
l.paint!["line-width"] = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if("filter" in layer) {
|
if("filter" in layer) {
|
||||||
l.filter = layer.filter
|
l.filter = layer.filter;
|
||||||
} else {
|
} else {
|
||||||
delete l['filter']
|
delete l["filter"];
|
||||||
}
|
}
|
||||||
l.id = l.id + '_highlight'
|
l.id = l.id + "_highlight";
|
||||||
return l
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function colorHighlightedLayer(layer?: LayerSpecification): HighlightedLayer | null {
|
export function colorHighlightedLayer(layer?: LayerSpecification): HighlightedLayer | null {
|
||||||
if(!layer || layer.type === 'background' || layer.type === 'raster') return null
|
if(!layer || layer.type === "background" || layer.type === "raster") return null;
|
||||||
|
|
||||||
const sourceLayerId = layer['source-layer'] || ''
|
const sourceLayerId = layer["source-layer"] || "";
|
||||||
const color = colors.brightColor(sourceLayerId, 1);
|
const color = colors.brightColor(sourceLayerId, 1);
|
||||||
|
|
||||||
if(layer.type === "fill" || layer.type === 'fill-extrusion') {
|
if(layer.type === "fill" || layer.type === "fill-extrusion") {
|
||||||
return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer['source-layer']), layer)
|
return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer["source-layer"]), layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(layer.type === "symbol" || layer.type === 'circle') {
|
if(layer.type === "symbol" || layer.type === "circle") {
|
||||||
return changeLayer(stylegen.circleLayer(color, layer.source, layer['source-layer']), layer)
|
return changeLayer(stylegen.circleLayer(color, layer.source, layer["source-layer"]), layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(layer.type === 'line') {
|
if(layer.type === "line") {
|
||||||
return changeLayer(stylegen.lineLayer(color, layer.source, layer['source-layer']), layer)
|
return changeLayer(stylegen.lineLayer(color, layer.source, layer["source-layer"]), layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import capitalize from 'lodash.capitalize'
|
import capitalize from "lodash.capitalize";
|
||||||
|
|
||||||
export default function labelFromFieldName(fieldName: string) {
|
export default function labelFromFieldName(fieldName: string) {
|
||||||
let label;
|
let label;
|
||||||
const parts = fieldName.split('-');
|
const parts = fieldName.split("-");
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
label = fieldName.split('-').slice(1).join(' ');
|
label = fieldName.split("-").slice(1).join(" ");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
label = fieldName;
|
label = fieldName;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user