mirror of
https://github.com/maputnik/editor.git
synced 2026-08-30 17:07:26 +00:00
Compare commits
15 Commits
bc57cf69d2
...
854e3eeb45
| Author | SHA1 | Date | |
|---|---|---|---|
| 854e3eeb45 | |||
| 7c112d7c10 | |||
| c870fefb14 | |||
| df64f5c1aa | |||
| d4d161ccbe | |||
| 9d35c3d650 | |||
| 176e1e4ae8 | |||
| b70b6f16b7 | |||
| 3c3fcadbb6 | |||
| 174548944f | |||
| c57d7d3a89 | |||
| 58ff17998d | |||
| b42afd0027 | |||
| 5312d61598 | |||
| 56cdfd23df |
@@ -5,7 +5,11 @@
|
|||||||
- Add support for hillshade's color arrays and relief-color elevation expression
|
- Add support for hillshade's color arrays and relief-color elevation expression
|
||||||
- Change layers icons to make them a bit more distinct
|
- Change layers icons to make them a bit more distinct
|
||||||
- Remove `@mdi` packages in favor of `react-icons`
|
- Remove `@mdi` packages in favor of `react-icons`
|
||||||
|
- Add ability to control the projection of the map - either globe or mercator
|
||||||
|
- Add markdown support for doc related to the style-spec fields
|
||||||
- Added global state modal to allow editing the global state
|
- Added global state modal to allow editing the global state
|
||||||
|
- Added color highlight for problematic properties
|
||||||
|
- Upgraded codemirror from version 5 to version 6
|
||||||
- _...Add new stuff here..._
|
- _...Add new stuff here..._
|
||||||
|
|
||||||
### 🐞 Bug fixes
|
### 🐞 Bug fixes
|
||||||
@@ -14,6 +18,8 @@
|
|||||||
- Fix modal close button possition
|
- Fix modal close button possition
|
||||||
- Fixed an issue with the generation of tranlations
|
- Fixed an issue with the generation of tranlations
|
||||||
- Fix missing spec info when clicking next to a property
|
- Fix missing spec info when clicking next to a property
|
||||||
|
- Fix Firefox open file that stopped working due to react upgrade
|
||||||
|
- Fix issue with missing bottom error panel
|
||||||
- _...Add new stuff here..._
|
- _...Add new stuff here..._
|
||||||
|
|
||||||
## 3.0.0
|
## 3.0.0
|
||||||
|
|||||||
@@ -491,6 +491,20 @@ describe("layers", () => {
|
|||||||
when.click("field-doc-button-Offset", 0);
|
when.click("field-doc-button-Offset", 0);
|
||||||
then(get.elementByTestId("spec-field-doc")).shouldContainText("Offset distance");
|
then(get.elementByTestId("spec-field-doc")).shouldContainText("Offset distance");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should hide spec info when clicking a second time", () => {
|
||||||
|
when.modal.fillLayers({
|
||||||
|
type: "symbol",
|
||||||
|
layer: "example",
|
||||||
|
});
|
||||||
|
|
||||||
|
when.hover("spec-field-container:text-rotate");
|
||||||
|
then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible();
|
||||||
|
when.click("field-doc-button-Rotate", 0);
|
||||||
|
when.wait(200);
|
||||||
|
when.click("field-doc-button-Rotate", 0);
|
||||||
|
then(get.elementByTestId("spec-field-doc")).shouldNotBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("raster", () => {
|
describe("raster", () => {
|
||||||
@@ -684,7 +698,19 @@ describe("layers", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("layereditor jsonlint should error", ()=>{
|
describe("layers editor", () => {
|
||||||
|
describe("property fields", () => {
|
||||||
|
it("should show error", () => {
|
||||||
|
when.modal.fillLayers({
|
||||||
|
type: "circle",
|
||||||
|
layer: "invalid",
|
||||||
|
});
|
||||||
|
|
||||||
|
then(get.element(".maputnik-input-block--error .maputnik-input-block-label")).shouldHaveCss("color", "rgb(207, 74, 74)");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("jsonlint should error", ()=>{
|
||||||
it("add", () => {
|
it("add", () => {
|
||||||
const id = when.modal.fillLayers({
|
const id = when.modal.fillLayers({
|
||||||
type: "circle",
|
type: "circle",
|
||||||
@@ -710,6 +736,9 @@ describe("layers", () => {
|
|||||||
error.should("exist");
|
error.should("exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe("drag and drop", () => {
|
describe("drag and drop", () => {
|
||||||
it("move layer should update local storage", () => {
|
it("move layer should update local storage", () => {
|
||||||
|
|||||||
@@ -26,6 +26,22 @@ export default class MaputnikCypressHelper {
|
|||||||
this.helper.when.wait(200);
|
this.helper.when.wait(200);
|
||||||
this.helper.get.elementByTestId(element).realMouseUp();
|
this.helper.get.elementByTestId(element).realMouseUp();
|
||||||
},
|
},
|
||||||
|
openFileByFixture: (fixture: string, buttonTestId: string, inputTestId: string) => {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
const file = {
|
||||||
|
text: cy.stub().resolves(cy.fixture(fixture).then(JSON.stringify)),
|
||||||
|
};
|
||||||
|
const fileHandle = {
|
||||||
|
getFile: cy.stub().resolves(file),
|
||||||
|
};
|
||||||
|
if (!win.showOpenFilePicker) {
|
||||||
|
this.helper.get.elementByTestId(inputTestId).selectFile("cypress/fixtures/" + fixture, { force: true });
|
||||||
|
} else {
|
||||||
|
cy.stub(win, "showOpenFilePicker").resolves([fileHandle]);
|
||||||
|
this.helper.get.elementByTestId(buttonTestId).click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
...this.helper.when,
|
...this.helper.when,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ export class MaputnikDriver {
|
|||||||
this.helper.when.waitForResponse("example-style.json");
|
this.helper.when.waitForResponse("example-style.json");
|
||||||
},
|
},
|
||||||
chooseExampleFile: () => {
|
chooseExampleFile: () => {
|
||||||
this.helper.get
|
this.helper.given.fixture("example-style.json", "example-style.json");
|
||||||
.bySelector("type", "file")
|
this.helper.when.openFileByFixture("example-style.json", "modal:open.file.button", "modal:open.file.input");
|
||||||
.selectFile("cypress/fixtures/example-style.json", { force: true });
|
this.helper.when.wait(200);
|
||||||
},
|
},
|
||||||
setStyle: (
|
setStyle: (
|
||||||
styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "",
|
styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "",
|
||||||
|
|||||||
@@ -18,10 +18,9 @@ describe("modals", () => {
|
|||||||
then(get.elementByTestId("modal:open")).shouldNotExist();
|
then(get.elementByTestId("modal:open")).shouldNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip("upload", () => {
|
it("upload", () => {
|
||||||
// HM: I was not able to make the following choose file actually to select a file and close the modal...
|
|
||||||
when.chooseExampleFile();
|
when.chooseExampleFile();
|
||||||
then(get.responseBody("example-style.json")).shouldEqualToStoredStyle();
|
then(get.fixture("example-style.json")).shouldEqualToStoredStyle();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when click open url", () => {
|
describe("when click open url", () => {
|
||||||
@@ -236,6 +235,29 @@ describe("modals", () => {
|
|||||||
).shouldInclude({ "maputnik:locationiq_access_token": apiKey });
|
).shouldInclude({ "maputnik:locationiq_access_token": apiKey });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("style projection mercator", () => {
|
||||||
|
when.select("modal:settings.projection", "mercator");
|
||||||
|
then(
|
||||||
|
get.styleFromLocalStorage().then((style) => style.projection)
|
||||||
|
).shouldInclude({ type: "mercator" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("style projection globe", () => {
|
||||||
|
when.select("modal:settings.projection", "globe");
|
||||||
|
then(
|
||||||
|
get.styleFromLocalStorage().then((style) => style.projection)
|
||||||
|
).shouldInclude({ type: "globe" });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("style projection vertical-perspective", () => {
|
||||||
|
when.select("modal:settings.projection", "vertical-perspective");
|
||||||
|
then(
|
||||||
|
get.styleFromLocalStorage().then((style) => style.projection)
|
||||||
|
).shouldInclude({ type: "vertical-perspective" });
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it("style renderer", () => {
|
it("style renderer", () => {
|
||||||
cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers
|
cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers
|
||||||
when.select("modal:settings.maputnik:renderer", "ol");
|
when.select("modal:settings.maputnik:renderer", "ol");
|
||||||
@@ -311,6 +333,7 @@ describe("modals", () => {
|
|||||||
|
|
||||||
it("add variable", () => {
|
it("add variable", () => {
|
||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
|
when.wait(100);
|
||||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
state: { key1: { default: "value" } },
|
state: { key1: { default: "value" } },
|
||||||
});
|
});
|
||||||
@@ -321,6 +344,7 @@ describe("modals", () => {
|
|||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
|
when.wait(100);
|
||||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } },
|
state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } },
|
||||||
});
|
});
|
||||||
@@ -331,6 +355,7 @@ describe("modals", () => {
|
|||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
when.click("global-state-remove-variable", 0);
|
when.click("global-state-remove-variable", 0);
|
||||||
|
when.wait(100);
|
||||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
state: { key2: { default: "value" }, key3: { default: "value" } },
|
state: { key2: { default: "value" }, key3: { default: "value" } },
|
||||||
});
|
});
|
||||||
@@ -340,6 +365,7 @@ describe("modals", () => {
|
|||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
when.setValue("global-state-variable-key:0", "mykey");
|
when.setValue("global-state-variable-key:0", "mykey");
|
||||||
when.typeKeys("{enter}");
|
when.typeKeys("{enter}");
|
||||||
|
when.wait(100);
|
||||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
state: { mykey: { default: "value" } },
|
state: { mykey: { default: "value" } },
|
||||||
});
|
});
|
||||||
@@ -349,12 +375,28 @@ describe("modals", () => {
|
|||||||
when.click("global-state-add-variable");
|
when.click("global-state-add-variable");
|
||||||
when.setValue("global-state-variable-value:0", "myvalue");
|
when.setValue("global-state-variable-value:0", "myvalue");
|
||||||
when.typeKeys("{enter}");
|
when.typeKeys("{enter}");
|
||||||
|
when.wait(100);
|
||||||
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
state: { key1: { default: "myvalue" } },
|
state: { key1: { default: "myvalue" } },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("error panel", () => {
|
||||||
|
it("not visible when no errors", () => {
|
||||||
|
then(get.element("maputnik-message-panel-error")).shouldNotExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("visible on style error", () => {
|
||||||
|
when.modal.open();
|
||||||
|
when.modal.fillLayers({
|
||||||
|
type: "circle",
|
||||||
|
layer: "invalid",
|
||||||
|
});
|
||||||
|
then(get.element(".maputnik-message-panel-error")).shouldBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Handle localStorage QuotaExceededError", () => {
|
describe("Handle localStorage QuotaExceededError", () => {
|
||||||
it("handles quota exceeded error when opening style from URL", () => {
|
it("handles quota exceeded error when opening style from URL", () => {
|
||||||
// Clear localStorage to start fresh
|
// Clear localStorage to start fresh
|
||||||
|
|||||||
Generated
+3243
-1643
File diff suppressed because it is too large
Load Diff
+7
-5
@@ -25,7 +25,10 @@
|
|||||||
"homepage": "https://github.com/maplibre/maputnik#readme",
|
"homepage": "https://github.com/maplibre/maputnik#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-json": "^6.0.2",
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
|
"@codemirror/lint": "^6.8.5",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
|
"@codemirror/view": "^6.38.2",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@@ -37,7 +40,7 @@
|
|||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"color": "^5.0.0",
|
"color": "^5.0.2",
|
||||||
"detect-browser": "^5.3.0",
|
"detect-browser": "^5.3.0",
|
||||||
"downshift": "^9.0.10",
|
"downshift": "^9.0.10",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
@@ -67,9 +70,9 @@
|
|||||||
"react-collapse": "^5.1.1",
|
"react-collapse": "^5.1.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-file-reader-input": "^2.0.0",
|
|
||||||
"react-i18next": "^15.7.3",
|
"react-i18next": "^15.7.3",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"string-hash": "^1.1.3",
|
"string-hash": "^1.1.3",
|
||||||
@@ -112,13 +115,12 @@
|
|||||||
"@types/lodash.isequal": "^4.5.8",
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
"@types/lodash.throttle": "^4.1.9",
|
"@types/lodash.throttle": "^4.1.9",
|
||||||
"@types/randomcolor": "^0.5.9",
|
"@types/randomcolor": "^0.5.9",
|
||||||
"@types/react": "^19.1.12",
|
"@types/react": "^19.1.13",
|
||||||
"@types/react-aria-menubutton": "^6.2.14",
|
"@types/react-aria-menubutton": "^6.2.14",
|
||||||
"@types/react-aria-modal": "^5.0.0",
|
"@types/react-aria-modal": "^5.0.0",
|
||||||
"@types/react-collapse": "^5.0.4",
|
"@types/react-collapse": "^5.0.4",
|
||||||
"@types/react-color": "^3.0.13",
|
"@types/react-color": "^3.0.13",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@types/react-file-reader-input": "^2.0.4",
|
|
||||||
"@types/string-hash": "^1.1.3",
|
"@types/string-hash": "^1.1.3",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/wicg-file-system-access": "^2023.10.6",
|
"@types/wicg-file-system-access": "^2023.10.6",
|
||||||
@@ -140,7 +142,7 @@
|
|||||||
"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.9.2",
|
"typescript": "^5.9.2",
|
||||||
"typescript-eslint": "^8.43.0",
|
"typescript-eslint": "^8.44.0",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vite": "^7.1.5",
|
"vite": "^7.1.5",
|
||||||
"vite-plugin-istanbul": "^7.1.0"
|
"vite-plugin-istanbul": "^7.1.0"
|
||||||
|
|||||||
+3
-15
@@ -36,7 +36,7 @@ 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 { type MapOptions } from "maplibre-gl";
|
import { type MapOptions } from "maplibre-gl";
|
||||||
import { type OnStyleChangedOpts, type StyleSpecificationWithId } from "../libs/definitions";
|
import { type MappedError, 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;
|
||||||
@@ -82,20 +82,8 @@ function updateRootSpec(spec: any, fieldName: string, newValues: any) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type MappedErrors = {
|
|
||||||
message: string
|
|
||||||
parsed?: {
|
|
||||||
type: string
|
|
||||||
data: {
|
|
||||||
index: number
|
|
||||||
key: string
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
errors: MappedErrors[],
|
errors: MappedError[],
|
||||||
infos: string[],
|
infos: string[],
|
||||||
mapStyle: StyleSpecificationWithId,
|
mapStyle: StyleSpecificationWithId,
|
||||||
dirtyMapStyle?: StyleSpecification,
|
dirtyMapStyle?: StyleSpecification,
|
||||||
@@ -383,7 +371,7 @@ export default class App extends React.Component<any, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedErrors = layerErrors.concat(errors).map(error => {
|
const mappedErrors: MappedError[] = layerErrors.concat(errors).map(error => {
|
||||||
// Special case: Duplicate layer id
|
// Special case: Duplicate layer id
|
||||||
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
|
const dupMatch = error.message.match(/layers\[(\d+)\]: (duplicate layer id "?(.*)"?, previously used)/);
|
||||||
if (dupMatch) {
|
if (dupMatch) {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import React from "react";
|
|||||||
import {formatLayerId} from "../libs/format";
|
import {formatLayerId} from "../libs/format";
|
||||||
import {type LayerSpecification, type StyleSpecification} from "maplibre-gl";
|
import {type LayerSpecification, type StyleSpecification} from "maplibre-gl";
|
||||||
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
import { type MappedError } from "../libs/definitions";
|
||||||
|
|
||||||
type AppMessagePanelInternalProps = {
|
type AppMessagePanelInternalProps = {
|
||||||
errors?: unknown[]
|
errors?: MappedError[]
|
||||||
infos?: string[]
|
infos?: string[]
|
||||||
mapStyle?: StyleSpecification
|
mapStyle?: StyleSpecification
|
||||||
onLayerSelect?(index: number): void;
|
onLayerSelect?(index: number): void;
|
||||||
@@ -19,7 +20,7 @@ class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalPro
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {t, selectedLayerIndex} = this.props;
|
const {t, selectedLayerIndex} = this.props;
|
||||||
const errors = this.props.errors?.map((error: any, idx) => {
|
const errors = this.props.errors?.map((error, idx) => {
|
||||||
let content;
|
let content;
|
||||||
if (error.parsed && error.parsed.type === "layer") {
|
if (error.parsed && error.parsed.type === "layer") {
|
||||||
const {parsed} = error;
|
const {parsed} = error;
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import React, {type PropsWithChildren, type SyntheticEvent} from "react";
|
import React, {type CSSProperties, 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 & {
|
export type BlockProps = PropsWithChildren & {
|
||||||
"data-wd-key"?: string
|
"data-wd-key"?: string
|
||||||
label?: string
|
label?: string
|
||||||
action?: React.ReactElement
|
action?: React.ReactElement
|
||||||
style?: object
|
style?: CSSProperties
|
||||||
onChange?(...args: unknown[]): unknown
|
onChange?(...args: unknown[]): unknown
|
||||||
fieldSpec?: object
|
fieldSpec?: object
|
||||||
wideMode?: boolean
|
wideMode?: boolean
|
||||||
|
error?: {message: string}
|
||||||
};
|
};
|
||||||
|
|
||||||
type BlockState = {
|
type BlockState = {
|
||||||
@@ -65,7 +66,8 @@ export default class Block extends React.Component<BlockProps, BlockState> {
|
|||||||
className={classnames({
|
className={classnames({
|
||||||
"maputnik-input-block": true,
|
"maputnik-input-block": true,
|
||||||
"maputnik-input-block--wide": this.props.wideMode,
|
"maputnik-input-block--wide": this.props.wideMode,
|
||||||
"maputnik-action-block": this.props.action
|
"maputnik-action-block": this.props.action,
|
||||||
|
"maputnik-input-block--error": this.props.error
|
||||||
})}
|
})}
|
||||||
onClick={this.onLabelClick}
|
onClick={this.onLabelClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
js: "JS",
|
js: "JS",
|
||||||
@@ -48,7 +49,11 @@ export default class Doc extends React.Component<DocProps> {
|
|||||||
<>
|
<>
|
||||||
{doc &&
|
{doc &&
|
||||||
<div className="SpecDoc">
|
<div className="SpecDoc">
|
||||||
<div className="SpecDoc__doc" data-wd-key='spec-field-doc'>{doc}</div>
|
<div className="SpecDoc__doc" data-wd-key='spec-field-doc'>
|
||||||
|
<Markdown components={{
|
||||||
|
a: ({node: _node, href, children, ...props}) => <a href={href} target="_blank" {...props}>{children}</a>,
|
||||||
|
}}>{doc}</Markdown>
|
||||||
|
</div>
|
||||||
{renderValues &&
|
{renderValues &&
|
||||||
<ul className="SpecDoc__values">
|
<ul className="SpecDoc__values">
|
||||||
{Object.entries(values).map(([key, value]) => {
|
{Object.entries(values).map(([key, value]) => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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";
|
||||||
|
import { type MappedLayerErrors } from "../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
function isLiteralExpression(value: any) {
|
function isLiteralExpression(value: any) {
|
||||||
@@ -119,7 +120,7 @@ type FieldFunctionProps = {
|
|||||||
fieldName: string
|
fieldName: string
|
||||||
fieldType: string
|
fieldType: string
|
||||||
fieldSpec: any
|
fieldSpec: any
|
||||||
errors?: {[key: string]: {message: string}}
|
errors?: MappedLayerErrors
|
||||||
value?: any
|
value?: any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Block from "./Block";
|
import Block, { type BlockProps } from "./Block";
|
||||||
import InputSpec, { type FieldSpecType, type InputSpecProps } from "./InputSpec";
|
import InputSpec, { type FieldSpecType, type InputSpecProps } from "./InputSpec";
|
||||||
import Fieldset from "./Fieldset";
|
import Fieldset, { type FieldsetProps } 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) {
|
||||||
@@ -34,15 +34,13 @@ function getElementFromType(fieldSpec: { type?: FieldSpecType, values?: unknown[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FieldSpecProps = InputSpecProps & {
|
export type FieldSpecProps = InputSpecProps & BlockProps & FieldsetProps;
|
||||||
name?: string
|
|
||||||
};
|
|
||||||
|
|
||||||
const FieldSpec: React.FC<FieldSpecProps> = (props) => {
|
const FieldSpec: React.FC<FieldSpecProps> = (props) => {
|
||||||
const TypeBlock = getElementFromType(props.fieldSpec!);
|
const TypeBlock = getElementFromType(props.fieldSpec!);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypeBlock label={props.label} action={props.action} fieldSpec={props.fieldSpec}>
|
<TypeBlock label={props.label} action={props.action} fieldSpec={props.fieldSpec} error={props.error}>
|
||||||
<InputSpec {...props} />
|
<InputSpec {...props} />
|
||||||
</TypeBlock>
|
</TypeBlock>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import React, { type PropsWithChildren, type ReactElement } from "react";
|
import React, { type PropsWithChildren, type ReactElement } from "react";
|
||||||
|
import classnames from "classnames";
|
||||||
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 & {
|
export type FieldsetProps = PropsWithChildren & {
|
||||||
label?: string,
|
label?: string,
|
||||||
fieldSpec?: { doc?: string },
|
fieldSpec?: { doc?: string },
|
||||||
action?: ReactElement,
|
action?: ReactElement,
|
||||||
|
error?: {message: string}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +32,10 @@ const Fieldset: React.FC<FieldsetProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!props.fieldSpec && (
|
{!props.fieldSpec && (
|
||||||
<div className="maputnik-input-block-label">
|
<div className={classnames({
|
||||||
|
"maputnik-input-block-label": true,
|
||||||
|
"maputnik-input-block--error": props.error
|
||||||
|
})}>
|
||||||
{props.label}
|
{props.label}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { TbMathFunction } from "react-icons/tb";
|
|||||||
import { PiListPlusBold } from "react-icons/pi";
|
import { PiListPlusBold } from "react-icons/pi";
|
||||||
import {isEqual} from "lodash";
|
import {isEqual} from "lodash";
|
||||||
import {type ExpressionSpecification, type LegacyFilterSpecification} from "maplibre-gl";
|
import {type ExpressionSpecification, type LegacyFilterSpecification} from "maplibre-gl";
|
||||||
import {latest, migrate, convertFilter} from "@maplibre/maplibre-gl-style-spec";
|
import {migrate, convertFilter} from "@maplibre/maplibre-gl-style-spec";
|
||||||
|
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
|
||||||
|
|
||||||
import {combiningFilterOps} from "../libs/filterops";
|
import {combiningFilterOps} from "../libs/filterops";
|
||||||
import InputSelect from "./InputSelect";
|
import InputSelect from "./InputSelect";
|
||||||
@@ -14,7 +15,7 @@ import InputButton from "./InputButton";
|
|||||||
import Doc from "./Doc";
|
import Doc from "./Doc";
|
||||||
import ExpressionProperty from "./_ExpressionProperty";
|
import ExpressionProperty from "./_ExpressionProperty";
|
||||||
import { type WithTranslation, withTranslation } from "react-i18next";
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import type { StyleSpecificationWithId } from "../libs/definitions";
|
import type { MappedLayerErrors, StyleSpecificationWithId } from "../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
function combiningFilter(props: FilterEditorInternalProps): LegacyFilterSpecification | ExpressionSpecification {
|
function combiningFilter(props: FilterEditorInternalProps): LegacyFilterSpecification | ExpressionSpecification {
|
||||||
@@ -95,8 +96,8 @@ type FilterEditorInternalProps = {
|
|||||||
/** Properties of the vector layer and the available fields */
|
/** Properties of the vector layer and the available fields */
|
||||||
properties?: {[key:string]: any}
|
properties?: {[key:string]: any}
|
||||||
filter?: any[]
|
filter?: any[]
|
||||||
errors?: {[key:string]: any}
|
errors?: MappedLayerErrors
|
||||||
onChange(value: LegacyFilterSpecification | ExpressionSpecification): unknown
|
onChange(value: LegacyFilterSpecification | ExpressionSpecification): void
|
||||||
} & WithTranslation;
|
} & WithTranslation;
|
||||||
|
|
||||||
type FilterEditorState = {
|
type FilterEditorState = {
|
||||||
@@ -293,6 +294,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
|
|||||||
this.props.onChange(defaultFilter);
|
this.props.onChange(defaultFilter);
|
||||||
}}
|
}}
|
||||||
fieldName="filter"
|
fieldName="filter"
|
||||||
|
fieldSpec={fieldSpec as any}
|
||||||
value={filter}
|
value={filter}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
|
|||||||
@@ -11,15 +11,10 @@ import type { StylePropertySpecification } from "maplibre-gl";
|
|||||||
export type InputJsonProps = {
|
export type InputJsonProps = {
|
||||||
value: object
|
value: object
|
||||||
maxHeight?: number
|
maxHeight?: number
|
||||||
lineNumbers?: boolean
|
|
||||||
lineWrapping?: boolean
|
|
||||||
gutters?: string[]
|
|
||||||
className?: string
|
className?: string
|
||||||
onChange(object: object): void
|
onChange(object: object): void
|
||||||
onFocus?(...args: unknown[]): unknown
|
onFocus?(...args: unknown[]): unknown
|
||||||
onBlur?(...args: unknown[]): unknown
|
onBlur?(...args: unknown[]): unknown
|
||||||
onJSONValid?(): void
|
|
||||||
onJSONInvalid?(_err: Error): void
|
|
||||||
lintType: "layer" | "style" | "expression" | "json"
|
lintType: "layer" | "style" | "expression" | "json"
|
||||||
spec?: StylePropertySpecification | undefined
|
spec?: StylePropertySpecification | undefined
|
||||||
};
|
};
|
||||||
@@ -32,16 +27,11 @@ type InputJsonState = {
|
|||||||
|
|
||||||
class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJsonState> {
|
class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJsonState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
lineNumbers: true,
|
|
||||||
lineWrapping: false,
|
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
|
||||||
onFocus: () => {},
|
onFocus: () => {},
|
||||||
onBlur: () => {},
|
onBlur: () => {},
|
||||||
onJSONInvalid: () => {},
|
|
||||||
onJSONValid: () => {},
|
|
||||||
};
|
};
|
||||||
_keyEvent: string;
|
_keyEvent: string;
|
||||||
_doc: EditorView | undefined;
|
_view: EditorView | undefined;
|
||||||
_el: HTMLDivElement | null = null;
|
_el: HTMLDivElement | null = null;
|
||||||
_cancelNextChange: boolean = false;
|
_cancelNextChange: boolean = false;
|
||||||
|
|
||||||
@@ -59,37 +49,15 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this._doc = createEditor(
|
this._view = createEditor({
|
||||||
this._el!,
|
parent: this._el!,
|
||||||
this.getPrettyJson(this.props.value),
|
value: this.getPrettyJson(this.props.value),
|
||||||
this.props.lintType || "layer",
|
lintType: this.props.lintType || "layer",
|
||||||
(value:string) => this.props.onChange(value ? JSON.parse(value) : {}),
|
onChange: (value:string) => this.onChange(value),
|
||||||
this.props.spec
|
onFocus: () => this.onFocus(),
|
||||||
);
|
onBlur: () => this.onBlur(),
|
||||||
/*CodeMirror(this._el!, {
|
spec: this.props.spec
|
||||||
value: this.props.getValue!(this.props.layer),
|
|
||||||
mode: this.props.mode || {
|
|
||||||
name: "mgl",
|
|
||||||
},
|
|
||||||
lineWrapping: this.props.lineWrapping,
|
|
||||||
tabSize: 2,
|
|
||||||
theme: "maputnik",
|
|
||||||
viewportMargin: Infinity,
|
|
||||||
lineNumbers: this.props.lineNumbers,
|
|
||||||
lint: this.props.lint || {
|
|
||||||
context: "layer"
|
|
||||||
},
|
|
||||||
matchBrackets: true,
|
|
||||||
gutters: this.props.gutters,
|
|
||||||
scrollbarStyle: "null",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this._doc.on("change", this.onChange);
|
|
||||||
this._doc.on("focus", this.onFocus);
|
|
||||||
this._doc.on("blur", this.onBlur);
|
|
||||||
*/
|
|
||||||
this._doc.dispatch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onPointerDown = () => {
|
onPointerDown = () => {
|
||||||
@@ -100,7 +68,6 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
if (this.props.onFocus) this.props.onFocus();
|
if (this.props.onFocus) this.props.onFocus();
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditing: true,
|
isEditing: true,
|
||||||
showMessage: (this._keyEvent === "keyboard"),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,35 +76,31 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
if (this.props.onBlur) this.props.onBlur();
|
if (this.props.onBlur) this.props.onBlur();
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
showMessage: false,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnMount () {
|
componentDidUpdate(prevProps: InputJsonProps) {
|
||||||
//this._doc!.off("change", this.onChange);
|
if (!this.state.isEditing && prevProps.value !== this.props.value) {
|
||||||
//this._doc!.off("focus", this.onFocus);
|
this._cancelNextChange = true;
|
||||||
//this._doc!.off("blur", this.onBlur);
|
this._view!.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: this._view!.state.doc.length,
|
||||||
|
insert: this.getPrettyJson(this.props.value)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(_prevProps: InputJsonProps) {
|
|
||||||
//if (!this.state.isEditing && prevProps.layer !== this.props.layer) {
|
|
||||||
// this._cancelNextChange = true;
|
|
||||||
// this._doc!.setValue(
|
|
||||||
// this.props.getValue!(this.props.layer),
|
|
||||||
// );
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (_e: unknown) => {
|
onChange = (_e: unknown) => {
|
||||||
/*
|
|
||||||
if (this._cancelNextChange) {
|
if (this._cancelNextChange) {
|
||||||
this._cancelNextChange = false;
|
this._cancelNextChange = false;
|
||||||
this.setState({
|
this.setState({
|
||||||
prevValue: this._doc!.getValue(),
|
prevValue: this._view!.state.doc.toString(),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newCode = this._doc!.getValue();
|
const newCode = this._view!.state.doc.toString();
|
||||||
|
|
||||||
if (this.state.prevValue !== newCode) {
|
if (this.state.prevValue !== newCode) {
|
||||||
let parsedLayer, err;
|
let parsedLayer, err;
|
||||||
@@ -148,19 +111,14 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
|
|||||||
console.warn(_err);
|
console.warn(_err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err && this.props.onJSONInvalid) {
|
if (!err) {
|
||||||
this.props.onJSONInvalid();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this.props.onChange) this.props.onChange(parsedLayer);
|
if (this.props.onChange) this.props.onChange(parsedLayer);
|
||||||
if (this.props.onJSONValid) this.props.onJSONValid();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
prevValue: newCode,
|
prevValue: newCode,
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export type InputSpecProps = {
|
|||||||
/** Override the style of the field */
|
/** Override the style of the field */
|
||||||
style?: object
|
style?: object
|
||||||
"aria-label"?: string
|
"aria-label"?: string
|
||||||
error?: unknown[]
|
|
||||||
label?: string
|
label?: string
|
||||||
action?: ReactElement
|
action?: ReactElement
|
||||||
};
|
};
|
||||||
@@ -43,7 +42,6 @@ export default class InputSpec extends React.Component<InputSpecProps> {
|
|||||||
|
|
||||||
childNodes() {
|
childNodes() {
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
error: this.props.error,
|
|
||||||
fieldSpec: this.props.fieldSpec,
|
fieldSpec: this.props.fieldSpec,
|
||||||
label: this.props.label,
|
label: this.props.label,
|
||||||
action: this.props.action,
|
action: this.props.action,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {formatLayerId} from "../libs/format";
|
|||||||
import { type WithTranslation, withTranslation } from "react-i18next";
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { type 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 { type OnMoveLayerCallback } from "../libs/definitions";
|
import { type MappedError, type MappedLayerErrors, type OnMoveLayerCallback } from "../libs/definitions";
|
||||||
|
|
||||||
type MaputnikLayoutGroup = {
|
type MaputnikLayoutGroup = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -128,7 +128,7 @@ type LayerEditorInternalProps = {
|
|||||||
isFirstLayer?: boolean
|
isFirstLayer?: boolean
|
||||||
isLastLayer?: boolean
|
isLastLayer?: boolean
|
||||||
layerIndex: number
|
layerIndex: number
|
||||||
errors?: any[]
|
errors?: MappedError[]
|
||||||
} & WithTranslation;
|
} & WithTranslation;
|
||||||
|
|
||||||
type LayerEditorState = {
|
type LayerEditorState = {
|
||||||
@@ -193,7 +193,7 @@ class LayerEditorInternal extends React.Component<LayerEditorInternalProps, Laye
|
|||||||
}
|
}
|
||||||
const {errors, layerIndex} = this.props;
|
const {errors, layerIndex} = this.props;
|
||||||
|
|
||||||
const errorData: {[key in LayerSpecification as string]: {message: string}} = {};
|
const errorData: MappedLayerErrors = {};
|
||||||
errors!.forEach(error => {
|
errors!.forEach(error => {
|
||||||
if (
|
if (
|
||||||
error.parsed &&
|
error.parsed &&
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ 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 { type WithTranslation, withTranslation } from "react-i18next";
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { type OnMoveLayerCallback } from "../libs/definitions";
|
import { type MappedError, type OnMoveLayerCallback } from "../libs/definitions";
|
||||||
|
|
||||||
type LayerListContainerProps = {
|
type LayerListContainerProps = {
|
||||||
layers: LayerSpecification[]
|
layers: LayerSpecification[]
|
||||||
@@ -33,7 +33,7 @@ type LayerListContainerProps = {
|
|||||||
onLayerCopy(...args: unknown[]): unknown
|
onLayerCopy(...args: unknown[]): unknown
|
||||||
onLayerVisibilityToggle(...args: unknown[]): unknown
|
onLayerVisibilityToggle(...args: unknown[]): unknown
|
||||||
sources: Record<string, SourceSpecification & {layers: string[]}>;
|
sources: Record<string, SourceSpecification & {layers: string[]}>;
|
||||||
errors: any[]
|
errors: MappedError[]
|
||||||
};
|
};
|
||||||
type LayerListContainerInternalProps = LayerListContainerProps & WithTranslation;
|
type LayerListContainerInternalProps = LayerListContainerProps & WithTranslation;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
import FieldFunction from "./FieldFunction";
|
import FieldFunction from "./FieldFunction";
|
||||||
import type {LayerSpecification} from "maplibre-gl";
|
import type {LayerSpecification} from "maplibre-gl";
|
||||||
|
import { type MappedLayerErrors } from "../libs/definitions";
|
||||||
|
|
||||||
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"];
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ type PropertyGroupProps = {
|
|||||||
groupFields: string[]
|
groupFields: string[]
|
||||||
onChange(...args: unknown[]): unknown
|
onChange(...args: unknown[]): unknown
|
||||||
spec: any
|
spec: any
|
||||||
errors?: {[key: string]: {message: string}}
|
errors?: MappedLayerErrors
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class PropertyGroup extends React.Component<PropertyGroupProps> {
|
export default class PropertyGroup extends React.Component<PropertyGroupProps> {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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";
|
||||||
|
import { type MappedLayerErrors } from "../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ type DataPropertyInternalProps = {
|
|||||||
fieldType?: string
|
fieldType?: string
|
||||||
fieldSpec?: object
|
fieldSpec?: object
|
||||||
value?: DataPropertyValue
|
value?: DataPropertyValue
|
||||||
errors?: object
|
errors?: MappedLayerErrors
|
||||||
} & WithTranslation;
|
} & WithTranslation;
|
||||||
|
|
||||||
type DataPropertyState = {
|
type DataPropertyState = {
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ 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";
|
||||||
import type { StylePropertySpecification } from "maplibre-gl";
|
import type { StylePropertySpecification } from "maplibre-gl";
|
||||||
|
import { type MappedLayerErrors } from "../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
type ExpressionPropertyInternalProps = {
|
type ExpressionPropertyInternalProps = {
|
||||||
fieldName: string
|
fieldName: string
|
||||||
|
fieldType?: string
|
||||||
fieldSpec?: StylePropertySpecification
|
fieldSpec?: StylePropertySpecification
|
||||||
value?: any
|
value?: any
|
||||||
errors?: {[key: string]: {message: string}}
|
errors?: MappedLayerErrors
|
||||||
onDelete?(...args: unknown[]): unknown
|
onDelete?(...args: unknown[]): unknown
|
||||||
onChange(value: object): void
|
onChange(value: object): void
|
||||||
onUndo?(...args: unknown[]): unknown
|
onUndo?(...args: unknown[]): unknown
|
||||||
@@ -63,12 +65,17 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
</InputButton>
|
</InputButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
let error = undefined;
|
||||||
|
if (this.props.errors) {
|
||||||
|
const fieldKey = this.props.fieldType ? this.props.fieldType + "." + this.props.fieldName : this.props.fieldName;
|
||||||
|
error = this.props.errors[fieldKey];
|
||||||
|
}
|
||||||
return <Block
|
return <Block
|
||||||
fieldSpec={this.props.fieldSpec}
|
fieldSpec={this.props.fieldSpec}
|
||||||
label={t(labelFromFieldName(this.props.fieldName))}
|
label={t(labelFromFieldName(this.props.fieldName))}
|
||||||
action={deleteStopBtn}
|
action={deleteStopBtn}
|
||||||
wideMode={true}
|
wideMode={true}
|
||||||
|
error={error}
|
||||||
>
|
>
|
||||||
<FieldJson
|
<FieldJson
|
||||||
lintType="expression"
|
lintType="expression"
|
||||||
@@ -77,9 +84,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
|
|||||||
onFocus={this.props.onFocus}
|
onFocus={this.props.onFocus}
|
||||||
onBlur={this.props.onBlur}
|
onBlur={this.props.onBlur}
|
||||||
value={value}
|
value={value}
|
||||||
lineNumbers={false}
|
|
||||||
maxHeight={200}
|
maxHeight={200}
|
||||||
lineWrapping={true}
|
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
/>
|
/>
|
||||||
</Block>;
|
</Block>;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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";
|
||||||
|
import { type MappedLayerErrors } from "../libs/definitions";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +60,7 @@ type ZoomPropertyInternalProps = {
|
|||||||
"property-type"?: string
|
"property-type"?: string
|
||||||
"function-type"?: string
|
"function-type"?: string
|
||||||
}
|
}
|
||||||
errors?: object
|
errors?: MappedLayerErrors
|
||||||
value?: ZoomWithStops
|
value?: ZoomWithStops
|
||||||
} & WithTranslation;
|
} & WithTranslation;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { type 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, { type Result } from "react-file-reader-input";
|
|
||||||
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
import { Trans, type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import ModalLoading from "./ModalLoading";
|
import ModalLoading from "./ModalLoading";
|
||||||
@@ -171,8 +170,10 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
|
|
||||||
// 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.
|
||||||
onFileChanged = async (_: any, files: Result[]) => {
|
onFileChanged = (files: FileList | null) => {
|
||||||
const [, file] = files[0];
|
if (!files) return;
|
||||||
|
if (files.length === 0) return;
|
||||||
|
const file = files[0];
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
this.clearError();
|
this.clearError();
|
||||||
|
|
||||||
@@ -246,13 +247,15 @@ class ModalOpenInternal extends React.Component<ModalOpenInternalProps, ModalOpe
|
|||||||
<div>
|
<div>
|
||||||
{typeof window.showOpenFilePicker === "function" ? (
|
{typeof window.showOpenFilePicker === "function" ? (
|
||||||
<InputButton
|
<InputButton
|
||||||
|
data-wd-key="modal:open.file.button"
|
||||||
className="maputnik-big-button"
|
className="maputnik-big-button"
|
||||||
onClick={this.onOpenFile}><MdFileUpload/> {t("Open Style")}
|
onClick={this.onOpenFile}><MdFileUpload/> {t("Open Style")}
|
||||||
</InputButton>
|
</InputButton>
|
||||||
) : (
|
) : (
|
||||||
<FileReaderInput onChange={this.onFileChanged} tabIndex={-1} aria-label={t("Open Style")}>
|
<label>
|
||||||
<InputButton className="maputnik-upload-button"><MdFileUpload /> {t("Open Style")}</InputButton>
|
<a className="maputnik-button maputnik-upload-button" aria-label={t("Open Style")}><MdFileUpload /> {t("Open Style")}</a>
|
||||||
</FileReaderInput>
|
<input data-wd-key="modal:open.file.input" type="file" style={{ display: "none" }} onChange={(e) => this.onFileChanged(e.target.files)} />
|
||||||
|
</label>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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, ProjectionSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from "maplibre-gl";
|
||||||
import { type WithTranslation, withTranslation } from "react-i18next";
|
import { type WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
import FieldArray from "../FieldArray";
|
import FieldArray from "../FieldArray";
|
||||||
@@ -79,6 +79,24 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeProjectionType(value: any) {
|
||||||
|
const projection = {
|
||||||
|
...this.props.mapStyle.projection,
|
||||||
|
} as ProjectionSpecification;
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
delete projection.type;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
projection.type = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onStyleChanged({
|
||||||
|
...this.props.mapStyle,
|
||||||
|
projection,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
changeStyleProperty(property: keyof StyleSpecification | "owner", value: any) {
|
changeStyleProperty(property: keyof StyleSpecification | "owner", value: any) {
|
||||||
const changedStyle = {
|
const changedStyle = {
|
||||||
...this.props.mapStyle,
|
...this.props.mapStyle,
|
||||||
@@ -103,6 +121,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
const light = this.props.mapStyle.light || {};
|
const light = this.props.mapStyle.light || {};
|
||||||
const transition = this.props.mapStyle.transition || {};
|
const transition = this.props.mapStyle.transition || {};
|
||||||
const terrain = this.props.mapStyle.terrain || {} as TerrainSpecification;
|
const terrain = this.props.mapStyle.terrain || {} as TerrainSpecification;
|
||||||
|
const projection = this.props.mapStyle.projection || {} as ProjectionSpecification;
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
data-wd-key="modal:settings"
|
data-wd-key="modal:settings"
|
||||||
@@ -116,21 +135,21 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.$root.name}
|
fieldSpec={latest.$root.name}
|
||||||
data-wd-key="modal:settings.name"
|
data-wd-key="modal:settings.name"
|
||||||
value={this.props.mapStyle.name}
|
value={this.props.mapStyle.name}
|
||||||
onChange={this.changeStyleProperty.bind(this, "name")}
|
onChange={(value) => this.changeStyleProperty("name", value)}
|
||||||
/>
|
/>
|
||||||
<FieldString
|
<FieldString
|
||||||
label={t("Owner")}
|
label={t("Owner")}
|
||||||
fieldSpec={{doc: t("Owner ID of the style. Used by Mapbox or future style APIs.")}}
|
fieldSpec={{doc: t("Owner ID of the style. Used by Mapbox or future style APIs.")}}
|
||||||
data-wd-key="modal:settings.owner"
|
data-wd-key="modal:settings.owner"
|
||||||
value={(this.props.mapStyle as any).owner}
|
value={(this.props.mapStyle as any).owner}
|
||||||
onChange={this.changeStyleProperty.bind(this, "owner")}
|
onChange={(value) => this.changeStyleProperty("owner", value)}
|
||||||
/>
|
/>
|
||||||
<FieldUrl
|
<FieldUrl
|
||||||
fieldSpec={latest.$root.sprite}
|
fieldSpec={latest.$root.sprite}
|
||||||
label={t("Sprite URL")}
|
label={t("Sprite URL")}
|
||||||
data-wd-key="modal:settings.sprite"
|
data-wd-key="modal:settings.sprite"
|
||||||
value={this.props.mapStyle.sprite as string}
|
value={this.props.mapStyle.sprite as string}
|
||||||
onChange={this.changeStyleProperty.bind(this, "sprite")}
|
onChange={(value) => this.changeStyleProperty("sprite", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldUrl
|
<FieldUrl
|
||||||
@@ -138,7 +157,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.$root.glyphs}
|
fieldSpec={latest.$root.glyphs}
|
||||||
data-wd-key="modal:settings.glyphs"
|
data-wd-key="modal:settings.glyphs"
|
||||||
value={this.props.mapStyle.glyphs as string}
|
value={this.props.mapStyle.glyphs as string}
|
||||||
onChange={this.changeStyleProperty.bind(this, "glyphs")}
|
onChange={(value) => this.changeStyleProperty("glyphs", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
@@ -146,7 +165,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
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={(value) => onChangeMetadataProperty("maputnik:openmaptiles_access_token", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
@@ -154,7 +173,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
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={(value) => onChangeMetadataProperty("maputnik:thunderforest_access_token", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
@@ -162,7 +181,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
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={(value) => onChangeMetadataProperty("maputnik:stadia_access_token", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
@@ -170,7 +189,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
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={(value) => onChangeMetadataProperty("maputnik:locationiq_access_token", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldArray
|
<FieldArray
|
||||||
@@ -180,7 +199,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
type="number"
|
type="number"
|
||||||
value={mapStyle.center || []}
|
value={mapStyle.center || []}
|
||||||
default={[0, 0]}
|
default={[0, 0]}
|
||||||
onChange={this.changeStyleProperty.bind(this, "center")}
|
onChange={(value) => this.changeStyleProperty("center", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
@@ -188,7 +207,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.$root.zoom}
|
fieldSpec={latest.$root.zoom}
|
||||||
value={mapStyle.zoom}
|
value={mapStyle.zoom}
|
||||||
default={0}
|
default={0}
|
||||||
onChange={this.changeStyleProperty.bind(this, "zoom")}
|
onChange={(value) => this.changeStyleProperty("zoom", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
@@ -196,7 +215,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.$root.bearing}
|
fieldSpec={latest.$root.bearing}
|
||||||
value={mapStyle.bearing}
|
value={mapStyle.bearing}
|
||||||
default={latest.$root.bearing.default}
|
default={latest.$root.bearing.default}
|
||||||
onChange={this.changeStyleProperty.bind(this, "bearing")}
|
onChange={(value) => this.changeStyleProperty("bearing", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
@@ -204,7 +223,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.$root.pitch}
|
fieldSpec={latest.$root.pitch}
|
||||||
value={mapStyle.pitch}
|
value={mapStyle.pitch}
|
||||||
default={latest.$root.pitch.default}
|
default={latest.$root.pitch.default}
|
||||||
onChange={this.changeStyleProperty.bind(this, "pitch")}
|
onChange={(value) => this.changeStyleProperty("pitch", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldEnum
|
<FieldEnum
|
||||||
@@ -214,7 +233,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
value={light.anchor as string}
|
value={light.anchor as string}
|
||||||
options={Object.keys(latest.light.anchor.values)}
|
options={Object.keys(latest.light.anchor.values)}
|
||||||
default={latest.light.anchor.default}
|
default={latest.light.anchor.default}
|
||||||
onChange={this.changeLightProperty.bind(this, "anchor")}
|
onChange={(value) => this.changeLightProperty("anchor", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldColor
|
<FieldColor
|
||||||
@@ -222,7 +241,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.light.color}
|
fieldSpec={latest.light.color}
|
||||||
value={light.color as string}
|
value={light.color as string}
|
||||||
default={latest.light.color.default}
|
default={latest.light.color.default}
|
||||||
onChange={this.changeLightProperty.bind(this, "color")}
|
onChange={(value) => this.changeLightProperty("color", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
@@ -230,7 +249,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.light.intensity}
|
fieldSpec={latest.light.intensity}
|
||||||
value={light.intensity as number}
|
value={light.intensity as number}
|
||||||
default={latest.light.intensity.default}
|
default={latest.light.intensity.default}
|
||||||
onChange={this.changeLightProperty.bind(this, "intensity")}
|
onChange={(value) => this.changeLightProperty("intensity", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldArray
|
<FieldArray
|
||||||
@@ -240,7 +259,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
length={latest.light.position.length}
|
length={latest.light.position.length}
|
||||||
value={light.position as number[]}
|
value={light.position as number[]}
|
||||||
default={latest.light.position.default}
|
default={latest.light.position.default}
|
||||||
onChange={this.changeLightProperty.bind(this, "position")}
|
onChange={(value) => this.changeLightProperty("position", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldString
|
<FieldString
|
||||||
@@ -248,7 +267,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.terrain.source}
|
fieldSpec={latest.terrain.source}
|
||||||
data-wd-key="modal:settings.maputnik:terrain_source"
|
data-wd-key="modal:settings.maputnik:terrain_source"
|
||||||
value={terrain.source}
|
value={terrain.source}
|
||||||
onChange={this.changeTerrainProperty.bind(this, "source")}
|
onChange={(value) => this.changeTerrainProperty("source", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
@@ -256,7 +275,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.terrain.exaggeration}
|
fieldSpec={latest.terrain.exaggeration}
|
||||||
value={terrain.exaggeration}
|
value={terrain.exaggeration}
|
||||||
default={latest.terrain.exaggeration.default}
|
default={latest.terrain.exaggeration.default}
|
||||||
onChange={this.changeTerrainProperty.bind(this, "exaggeration")}
|
onChange={(value) => this.changeTerrainProperty("exaggeration", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
@@ -264,7 +283,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.transition.delay}
|
fieldSpec={latest.transition.delay}
|
||||||
value={transition.delay}
|
value={transition.delay}
|
||||||
default={latest.transition.delay.default}
|
default={latest.transition.delay.default}
|
||||||
onChange={this.changeTransitionProperty.bind(this, "delay")}
|
onChange={(value) => this.changeTransitionProperty("delay", value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldNumber
|
<FieldNumber
|
||||||
@@ -272,7 +291,20 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
fieldSpec={latest.transition.duration}
|
fieldSpec={latest.transition.duration}
|
||||||
value={transition.duration}
|
value={transition.duration}
|
||||||
default={latest.transition.duration.default}
|
default={latest.transition.duration.default}
|
||||||
onChange={this.changeTransitionProperty.bind(this, "duration")}
|
onChange={(value) => this.changeTransitionProperty("duration", value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FieldSelect
|
||||||
|
label={t("Projection")}
|
||||||
|
data-wd-key="modal:settings.projection"
|
||||||
|
options={[
|
||||||
|
["", "Undefined"],
|
||||||
|
["mercator", "Mercator"],
|
||||||
|
["globe", "Globe"],
|
||||||
|
["vertical-perspective", "Vertical Perspective"]
|
||||||
|
]}
|
||||||
|
value={projection?.type?.toString() || ""}
|
||||||
|
onChange={(value) => this.changeProjectionType(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
@@ -284,7 +316,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
|||||||
["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={(value) => onChangeMetadataProperty("maputnik:renderer", value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>;
|
</Modal>;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { basicSetup } from "codemirror";
|
import { basicSetup } from "codemirror";
|
||||||
import { EditorView } from "@codemirror/view";
|
import { EditorView } from "@codemirror/view";
|
||||||
|
import { EditorState, Compartment } from "@codemirror/state";
|
||||||
import { json, jsonParseLinter } from "@codemirror/lang-json";
|
import { json, jsonParseLinter } from "@codemirror/lang-json";
|
||||||
import { linter, lintGutter, type Diagnostic } from "@codemirror/lint";
|
import { linter, lintGutter, type Diagnostic } from "@codemirror/lint";
|
||||||
import { oneDark } from "@codemirror/theme-one-dark";
|
import { oneDark } from "@codemirror/theme-one-dark";
|
||||||
@@ -150,15 +151,17 @@ function createMaplibreExpressionLinter(spec: StylePropertySpecification) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEditor(
|
export function createEditor(props: {
|
||||||
parent: HTMLElement,
|
parent: HTMLElement,
|
||||||
value: string,
|
value: string,
|
||||||
lintType: LintType,
|
lintType: LintType,
|
||||||
onChange: (value: string) => void,
|
onChange: (value: string) => void,
|
||||||
|
onFocus: () => void,
|
||||||
|
onBlur: () => void,
|
||||||
spec?: StylePropertySpecification,
|
spec?: StylePropertySpecification,
|
||||||
): EditorView {
|
}): EditorView {
|
||||||
let specificLinter: (view: EditorView) => Diagnostic[] = () => [];
|
let specificLinter: (view: EditorView) => Diagnostic[] = () => [];
|
||||||
switch (lintType) {
|
switch (props.lintType) {
|
||||||
case "style":
|
case "style":
|
||||||
specificLinter = createMaplibreStyleLinter();
|
specificLinter = createMaplibreStyleLinter();
|
||||||
break;
|
break;
|
||||||
@@ -166,26 +169,40 @@ export function createEditor(
|
|||||||
specificLinter = createMaplibreLayerLinter();
|
specificLinter = createMaplibreLayerLinter();
|
||||||
break;
|
break;
|
||||||
case "expression":
|
case "expression":
|
||||||
if (!spec) {
|
if (!props.spec) {
|
||||||
throw new Error("spec is required for expression mode");
|
throw new Error("spec is required for expression mode");
|
||||||
}
|
}
|
||||||
specificLinter = createMaplibreExpressionLinter(spec);
|
specificLinter = createMaplibreExpressionLinter(props.spec);
|
||||||
break;
|
break;
|
||||||
case "json":
|
case "json":
|
||||||
specificLinter = () => [];
|
specificLinter = () => [];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EditorView({
|
return new EditorView({
|
||||||
doc: value,
|
doc: props.value,
|
||||||
extensions: [
|
extensions: [
|
||||||
basicSetup,
|
basicSetup,
|
||||||
json(),
|
json(),
|
||||||
oneDark,
|
oneDark,
|
||||||
|
new Compartment().of(EditorState.tabSize.of(2)),
|
||||||
|
EditorView.theme({
|
||||||
|
"&": {
|
||||||
|
fontSize: "9pt"
|
||||||
|
}
|
||||||
|
}),
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
const doc = update.state.doc;
|
const doc = update.state.doc;
|
||||||
const value = doc.toString();
|
const value = doc.toString();
|
||||||
onChange(value);
|
props.onChange(value);
|
||||||
|
}
|
||||||
|
if (update.focusChanged) {
|
||||||
|
if (update.view.hasFocus) {
|
||||||
|
props.onFocus();
|
||||||
|
} else {
|
||||||
|
props.onBlur();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
lintGutter(),
|
lintGutter(),
|
||||||
@@ -197,6 +214,6 @@ export function createEditor(
|
|||||||
return specificLinter(view);
|
return specificLinter(view);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
parent,
|
parent: props.parent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Vendored
+16
@@ -16,3 +16,19 @@ export interface IStyleStore {
|
|||||||
getLatestStyle(): Promise<StyleSpecificationWithId>;
|
getLatestStyle(): Promise<StyleSpecificationWithId>;
|
||||||
save(mapStyle: StyleSpecificationWithId): StyleSpecificationWithId;
|
save(mapStyle: StyleSpecificationWithId): StyleSpecificationWithId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MappedError = {
|
||||||
|
message: string
|
||||||
|
parsed?: {
|
||||||
|
type: "layer"
|
||||||
|
data: {
|
||||||
|
index: number
|
||||||
|
key: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MappedLayerErrors = {
|
||||||
|
[key in LayerSpecification as string]: {message: string}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
@use "vars";
|
|
||||||
|
|
||||||
.CodeMirror-lint-tooltip {
|
|
||||||
z-index: 2000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codemirror-container {
|
|
||||||
max-width: 100%;
|
|
||||||
position: relative;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik.CodeMirror {
|
|
||||||
height: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik.CodeMirror, .cm-s-maputnik .CodeMirror-gutters {
|
|
||||||
color: #8e8e8e;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-gutters {
|
|
||||||
background: #212328;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-cursor {
|
|
||||||
border-left: solid thin #f0f0f0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik.CodeMirror-focused div.CodeMirror-selected {
|
|
||||||
background: rgba(255, 255, 255, 0.10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-line::selection,
|
|
||||||
.cm-s-maputnik .CodeMirror-line > span::selection,
|
|
||||||
.cm-s-maputnik .CodeMirror-line > span > span::selection {
|
|
||||||
background: rgba(255, 255, 255, 0.10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-line::-moz-selection,
|
|
||||||
.cm-s-maputnik .CodeMirror-line > span::-moz-selection,
|
|
||||||
.cm-s-maputnik .CodeMirror-line > span > span::-moz-selection {
|
|
||||||
background: rgba(255, 255, 255, 0.10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik span.cm-string, .cm-s-maputnik span.cm-string-2 {
|
|
||||||
color: #8f9d6a;
|
|
||||||
}
|
|
||||||
.cm-s-maputnik span.cm-number { color: #91675f; }
|
|
||||||
.cm-s-maputnik span.cm-property { color: #b8a077; }
|
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-activeline-background {
|
|
||||||
background: rgba(255,255,255,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-matchingbracket {
|
|
||||||
background: hsla(223, 12%, 35%, 1);
|
|
||||||
color: vars.$color-white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-s-maputnik .CodeMirror-nonmatchingbracket {
|
|
||||||
background-color: #bb0000;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes JSONEditor__animation-fade {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.JSONEditor__message {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
font-size: 0.85em;
|
|
||||||
z-index: 99999;
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
background: hsla(0, 0%, 0%, 0.3);
|
|
||||||
color: vars.$color-lowgray;
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
transition: opacity 320ms ease;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
&--on {
|
|
||||||
opacity: 1;
|
|
||||||
animation: 320ms ease 0s JSONEditor__animation-fade;
|
|
||||||
animation-delay: 2000ms;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -290,6 +290,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.maputnik-input-block--error {
|
||||||
|
.maputnik-input-block-label {
|
||||||
|
color: vars.$color-red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.maputnik-expr-infobox {
|
.maputnik-expr-infobox {
|
||||||
font-size: vars.$font-size-6;
|
font-size: vars.$font-size-6;
|
||||||
background: vars.$color-midgray;
|
background: vars.$color-midgray;
|
||||||
|
|||||||
@@ -252,7 +252,7 @@
|
|||||||
margin-top: vars.$margin-3;
|
margin-top: vars.$margin-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SpecDoc__values code {
|
.SpecDoc__values code, .SpecDoc__doc code {
|
||||||
background: vars.$color-midgray;
|
background: vars.$color-midgray;
|
||||||
padding: 0.1em 0.3em;
|
padding: 0.1em 0.3em;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 1;
|
z-index: 10;
|
||||||
width: vars.$layout-map-width;
|
width: vars.$layout-map-width;
|
||||||
background-color: vars.$color-black;
|
background-color: vars.$color-black;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
@use 'zoomproperty';
|
@use 'zoomproperty';
|
||||||
@use 'popup';
|
@use 'popup';
|
||||||
@use 'map';
|
@use 'map';
|
||||||
@use 'codemirror';
|
|
||||||
@use 'react-collapse';
|
@use 'react-collapse';
|
||||||
|
|
||||||
.maputnik-layout {
|
.maputnik-layout {
|
||||||
|
|||||||
Reference in New Issue
Block a user