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&amp;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&amp;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&amp;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&amp;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&amp;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 />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typescript&package-manager=npm_and_yarn&previous-version=5.8.3&new-version=5.9.2)](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:
dependabot[bot]
2025-09-13 18:45:03 +00:00
committed by GitHub
parent 51d063ca5a
commit 5fe38bb6ff
115 changed files with 2111 additions and 2053 deletions

View File

@@ -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");

View File

@@ -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}

View File

@@ -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");
}); });
}); });

View File

@@ -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();

View File

@@ -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();
}, },

View File

@@ -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")
}; };
} }

View File

@@ -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;

View File

@@ -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 />)

View File

@@ -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
} }
} }
) );

View File

@@ -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
View File

@@ -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": {

View File

@@ -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",

View File

@@ -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}
/> />;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>
) );
} }
} }
} }

View File

@@ -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} />;
} }
} }

View File

@@ -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 &&

View File

@@ -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

View File

@@ -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 & {

View File

@@ -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 & {

View File

@@ -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 & {

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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 & {

View File

@@ -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>
); );

View File

@@ -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

View File

@@ -1,4 +1,4 @@
import InputJson, {InputJsonProps} from './InputJson' import InputJson, {type InputJsonProps} from "./InputJson";
type FieldJsonProps = InputJsonProps & {}; type FieldJsonProps = InputJsonProps & {};

View File

@@ -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"
> >

View File

@@ -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"
> >

View File

@@ -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 & {

View File

@@ -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 & {

View File

@@ -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 & {

View File

@@ -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}

View File

@@ -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}
> >

View File

@@ -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;
} }
} }

View File

@@ -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

View File

@@ -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>

View File

@@ -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 & {

View File

@@ -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>
)} )}

View File

@@ -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"

View File

@@ -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>;
} }
} }

View File

@@ -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;

View File

@@ -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>
) );
} }
} }

View File

@@ -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");
}); });
}); });

View File

@@ -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>
) );
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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}
/> />;
} }
} }
} }

View File

@@ -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">

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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"]}
/> />;
} }
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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;

View File

@@ -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>;
} }
} }

View File

@@ -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;

View File

@@ -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>;
} }
} }

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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 = {

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }
} }

View File

@@ -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}
/> />;
} }
} }

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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>
) );
} }
} }

View File

@@ -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;

View File

@@ -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>;
} }
} }

View File

@@ -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>;
} }
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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>
); );

View File

@@ -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 };

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
} }

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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