mirror of
https://github.com/maputnik/editor.git
synced 2026-09-11 06:47:25 +00:00
Compare commits
18 Commits
c0f76dfff8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 80c0d08179 | |||
| 1f475cf715 | |||
| 1301dc03e7 | |||
| 0a4c1710e2 | |||
| 1dc80b1313 | |||
| 139cf07991 | |||
| 8d400b73e3 | |||
| 6e1a0c19c3 | |||
| 4cf63c1573 | |||
| a16d38abba | |||
| ce171f1ce2 | |||
| fa5a88c658 | |||
| 636cdc6cc5 | |||
| 31cda1dbe7 | |||
| 7f83abf773 | |||
| 07d02058e8 | |||
| 858b0b5ac2 | |||
| 35585bbeef |
@@ -8,6 +8,9 @@
|
|||||||
- _...Add new stuff here..._
|
- _...Add new stuff here..._
|
||||||
|
|
||||||
### 🐞 Bug fixes
|
### 🐞 Bug fixes
|
||||||
|
- Prevent a crash when converting a function to an expression
|
||||||
|
- Prevent a crash when deleting either stop from a function with two stops
|
||||||
|
- Preserve expanded layer groups when deleting layers, including the first layer of a group
|
||||||
- The map's data listener now fires on tile loads again, so source and vector layer field autocompletion is populated
|
- The map's data listener now fires on tile loads again, so source and vector layer field autocompletion is populated
|
||||||
- The `maputnik` desktop binary now opens the default browser automatically on startup (opt out with `--no-browser`)
|
- The `maputnik` desktop binary now opens the default browser automatically on startup (opt out with `--no-browser`)
|
||||||
- _...Add new stuff here..._
|
- _...Add new stuff here..._
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": 8,
|
||||||
|
"name": "Grouped layers",
|
||||||
|
"sources": {},
|
||||||
|
"layers": [
|
||||||
|
{ "id": "background", "type": "background" },
|
||||||
|
{ "id": "park-a", "type": "background" },
|
||||||
|
{ "id": "park-b", "type": "background" },
|
||||||
|
{ "id": "park-c", "type": "background" },
|
||||||
|
{ "id": "road", "type": "background" },
|
||||||
|
{ "id": "park-d", "type": "background" },
|
||||||
|
{ "id": "park-e", "type": "background" },
|
||||||
|
{ "id": "water-a", "type": "background" },
|
||||||
|
{ "id": "water-b", "type": "background" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -289,6 +289,20 @@ describe("layer editor", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should leave a plain editable value after deleting a stop", async () => {
|
||||||
|
await when.setFunctionStopValue("circle-radius", "Output value", 1, "0");
|
||||||
|
await when.deleteFunctionStop("circle-radius");
|
||||||
|
await then(get.elementByTestId("spec-field:circle-radius")).shouldBeVisible();
|
||||||
|
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
|
layers: [{ id, paint: { "circle-radius": 0 } }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await when.setValue("spec-field-input:circle-radius", "7");
|
||||||
|
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
|
layers: [{ id, paint: { "circle-radius": 7 } }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("should set the base", async () => {
|
test("should set the base", async () => {
|
||||||
await when.setFunctionBase("circle-radius", "2");
|
await when.setFunctionBase("circle-radius", "2");
|
||||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
@@ -310,8 +324,9 @@ describe("layer editor", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should convert to an expression", async () => {
|
test("should convert to an expression without crashing", async () => {
|
||||||
await when.makeExpression("circle-radius");
|
await when.makeExpression("circle-radius");
|
||||||
|
await then(get.element("[data-wd-key='spec-field-container:circle-radius'] .maputnik-expression-editor")).shouldBeVisible();
|
||||||
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
|
||||||
layers: [{ id, paint: { "circle-radius": ["interpolate", ["linear"], ["zoom"], 6, 5, 10, 5] } }],
|
layers: [{ id, paint: { "circle-radius": ["interpolate", ["linear"], ["zoom"], 6, 5, 10, 5] } }],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -306,6 +306,75 @@ describe("layers list", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("groups", () => {
|
describe("groups", () => {
|
||||||
|
for (const control of ["menu", "trash"] as const) {
|
||||||
|
describe(`after deletion using ${control}`, () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await when.setStyle("grouped_layers");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("keeps all groups expanded when an earlier layer is deleted", async () => {
|
||||||
|
await when.click("skip-target-layer-list");
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-b")).shouldBeVisible();
|
||||||
|
await when.deleteLayer("background", control);
|
||||||
|
await then(get.elementByTestId("layer-list-item:background")).shouldNotExist();
|
||||||
|
await then(get.elementByTestId("layer-list-group:park-0")).shouldExist();
|
||||||
|
await expect(get.elementByTestId("layer-list-group:water-6").getByRole("button")).toHaveAttribute("aria-expanded", "true");
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-b")).shouldBeVisible();
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-e")).shouldBeVisible();
|
||||||
|
await then(get.elementByTestId("layer-list-item:water-b")).shouldBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("preserves separate states for groups with the same prefix", async () => {
|
||||||
|
await when.click("layer-list-group:park-1");
|
||||||
|
await when.deleteLayer("background", control);
|
||||||
|
await then(get.elementByTestId("layer-list-group:park-0")).shouldExist();
|
||||||
|
await when.click("layer-list-item:road");
|
||||||
|
await expect(get.elementByTestId("layer-list-group:park-0").getByRole("button")).toHaveAttribute("aria-expanded", "true");
|
||||||
|
await expect(get.elementByTestId("layer-list-group:park-4").getByRole("button")).toHaveAttribute("aria-expanded", "false");
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-b")).shouldBeVisible();
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-e")).shouldNotBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("keeps a group expanded when its first layer is deleted", async () => {
|
||||||
|
await when.click("layer-list-group:park-1");
|
||||||
|
await when.deleteLayer("park-a", control);
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-a")).shouldNotExist();
|
||||||
|
await when.click("layer-list-item:road");
|
||||||
|
await expect(get.elementByTestId("layer-list-group:park-1").getByRole("button")).toHaveAttribute("aria-expanded", "true");
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-c")).shouldBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const expandedGroup of ["park-1", "park-5"]) {
|
||||||
|
test(`keeps a merged group expanded when ${expandedGroup} was expanded`, async () => {
|
||||||
|
await when.click("layer-list-group:" + expandedGroup);
|
||||||
|
await when.deleteLayer("road", control);
|
||||||
|
await then(get.elementByTestId("layer-list-item:road")).shouldNotExist();
|
||||||
|
await expect(get.elementByTestId("layer-list-group:park-1").getByRole("button")).toHaveAttribute("aria-expanded", "true");
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-b")).shouldBeVisible();
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-e")).shouldBeVisible();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test("keeps a merged group collapsed when both groups were collapsed", async () => {
|
||||||
|
await when.deleteLayer("road", control);
|
||||||
|
await then(get.elementByTestId("layer-list-item:road")).shouldNotExist();
|
||||||
|
await when.click("layer-list-item:background");
|
||||||
|
await expect(get.elementByTestId("layer-list-group:park-1").getByRole("button")).toHaveAttribute("aria-expanded", "false");
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-b")).shouldNotBeVisible();
|
||||||
|
await then(get.elementByTestId("layer-list-item:park-e")).shouldNotBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("preserves later groups after deleting an entire group", async () => {
|
||||||
|
await when.click("skip-target-layer-list");
|
||||||
|
await when.deleteLayer("park-a", control);
|
||||||
|
await when.deleteLayer("park-b", control);
|
||||||
|
await when.deleteLayer("park-c", control);
|
||||||
|
await expect(get.elementByTestId("layer-list-group:water-4").getByRole("button")).toHaveAttribute("aria-expanded", "true");
|
||||||
|
await then(get.elementByTestId("layer-list-item:water-b")).shouldBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test("simple", async () => {
|
test("simple", async () => {
|
||||||
await when.setStyle("geojson");
|
await when.setStyle("geojson");
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export class MaputnikDriver {
|
|||||||
"example-style-with-zoom-7-and-center-0-51.json",
|
"example-style-with-zoom-7-and-center-0-51.json",
|
||||||
"example-style-with-zoom-5-and-center-50-50.json",
|
"example-style-with-zoom-5-and-center-50-50.json",
|
||||||
"access-token-style.json",
|
"access-token-style.json",
|
||||||
|
"grouped-layers-style.json",
|
||||||
];
|
];
|
||||||
for (const fixture of styleFixtures) {
|
for (const fixture of styleFixtures) {
|
||||||
await this.helper.given.interceptAndMockResponse({
|
await this.helper.given.interceptAndMockResponse({
|
||||||
@@ -63,6 +64,18 @@ export class MaputnikDriver {
|
|||||||
|
|
||||||
modal: this.modalDriver.when,
|
modal: this.modalDriver.when,
|
||||||
|
|
||||||
|
deleteLayer: async (id: string, control: "menu" | "trash" = "menu") => {
|
||||||
|
if (control === "trash") {
|
||||||
|
await this.helper.when.hover("layer-list-item:" + id);
|
||||||
|
await this.helper.when.click("layer-list-item:" + id + ":delete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.helper.when.click("layer-list-item:" + id);
|
||||||
|
await this.helper.when.click("skip-target-layer-editor");
|
||||||
|
await this.helper.when.click("menu-delete-layer");
|
||||||
|
},
|
||||||
|
|
||||||
setStyle: async (
|
setStyle: async (
|
||||||
styleProperties:
|
styleProperties:
|
||||||
| "geojson"
|
| "geojson"
|
||||||
@@ -73,6 +86,7 @@ export class MaputnikDriver {
|
|||||||
| "font"
|
| "font"
|
||||||
| "zoom_7_center_0_51"
|
| "zoom_7_center_0_51"
|
||||||
| "access_tokens"
|
| "access_tokens"
|
||||||
|
| "grouped_layers"
|
||||||
| "",
|
| "",
|
||||||
zoom?: number
|
zoom?: number
|
||||||
) => {
|
) => {
|
||||||
@@ -85,6 +99,7 @@ export class MaputnikDriver {
|
|||||||
font: "example-style-with-fonts.json",
|
font: "example-style-with-fonts.json",
|
||||||
zoom_7_center_0_51: "example-style-with-zoom-7-and-center-0-51.json",
|
zoom_7_center_0_51: "example-style-with-zoom-7-and-center-0-51.json",
|
||||||
access_tokens: "access-token-style.json",
|
access_tokens: "access-token-style.json",
|
||||||
|
grouped_layers: "grouped-layers-style.json",
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = new URL(baseUrl);
|
const url = new URL(baseUrl);
|
||||||
|
|||||||
Generated
+253
-423
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -29,16 +29,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-json": "^6.0.2",
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
"@codemirror/lint": "^6.9.7",
|
"@codemirror/lint": "^6.9.7",
|
||||||
"@codemirror/state": "^6.7.2",
|
"@codemirror/state": "^6.7.4",
|
||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@codemirror/view": "^6.43.10",
|
"@codemirror/view": "^6.43.11",
|
||||||
"@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",
|
||||||
"@mapbox/mapbox-gl-rtl-text": "^0.4.0",
|
"@mapbox/mapbox-gl-rtl-text": "^0.4.0",
|
||||||
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
||||||
"@maplibre/maplibre-gl-inspect": "^1.9.0",
|
"@maplibre/maplibre-gl-inspect": "^1.9.0",
|
||||||
"@maplibre/maplibre-gl-style-spec": "^26.4.1",
|
"@maplibre/maplibre-gl-style-spec": "^26.4.2",
|
||||||
"array-move": "^4.0.0",
|
"array-move": "^4.0.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"downshift": "^9.4.0",
|
"downshift": "^9.4.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"i18next": "^26.4.1",
|
"i18next": "^26.4.2",
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
"i18next-resources-to-backend": "^1.2.3",
|
"i18next-resources-to-backend": "^1.2.3",
|
||||||
"json-stringify-pretty-compact": "^4.0.0",
|
"json-stringify-pretty-compact": "^4.0.0",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"maplibre-gl": "^6.6.0",
|
"maplibre-gl": "^6.7.0",
|
||||||
"maputnik-design": "github:maputnik/design#172b06c",
|
"maputnik-design": "github:maputnik/design#172b06c",
|
||||||
"ol": "^10.10.0",
|
"ol": "^10.10.0",
|
||||||
"ol-mapbox-style": "^13.4.3",
|
"ol-mapbox-style": "^13.4.3",
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||||
"@playwright/test": "^1.62.1",
|
"@playwright/test": "^1.63.0",
|
||||||
"@stylistic/eslint-plugin": "^5.10.0",
|
"@stylistic/eslint-plugin": "^5.10.0",
|
||||||
"@types/codemirror": "^5.60.18",
|
"@types/codemirror": "^5.60.18",
|
||||||
"@types/color": "^4.2.1",
|
"@types/color": "^4.2.1",
|
||||||
@@ -121,24 +121,24 @@
|
|||||||
"@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.2.5",
|
"@types/react-dom": "^19.2.7",
|
||||||
"@types/string-hash": "^1.1.3",
|
"@types/string-hash": "^1.1.3",
|
||||||
"@types/wicg-file-system-access": "^2023.10.7",
|
"@types/wicg-file-system-access": "^2023.10.7",
|
||||||
"@vitejs/plugin-react": "^6.1.1",
|
"@vitejs/plugin-react": "^6.1.1",
|
||||||
"@vitest/coverage-v8": "^4.1.11",
|
"@vitest/coverage-v8": "^5.0.0",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"eslint": "^10.9.1",
|
"eslint": "^10.10.0",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.5",
|
"eslint-plugin-react-refresh": "^0.5.6",
|
||||||
"i18next-cli": "^1.72.1",
|
"i18next-cli": "^1.73.1",
|
||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"istanbul-lib-coverage": "^3.2.2",
|
"istanbul-lib-coverage": "^3.2.2",
|
||||||
"nyc": "^18.0.0",
|
"nyc": "^18.0.0",
|
||||||
"postcss": "^8.5.26",
|
"postcss": "^8.5.28",
|
||||||
"react-hot-loader": "^4.13.1",
|
"react-hot-loader": "^4.13.1",
|
||||||
"sass": "^1.103.1",
|
"sass": "^1.104.0",
|
||||||
"stylelint": "^17.14.1",
|
"stylelint": "^17.15.0",
|
||||||
"stylelint-config-recommended-scss": "^17.0.1",
|
"stylelint-config-recommended-scss": "^17.0.1",
|
||||||
"stylelint-scss": "^7.2.0",
|
"stylelint-scss": "^7.2.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
@@ -146,6 +146,6 @@
|
|||||||
"uuid": "^14.0.2",
|
"uuid": "^14.0.2",
|
||||||
"vite": "^8.2.2",
|
"vite": "^8.2.2",
|
||||||
"vite-plugin-istanbul": "^9.0.1",
|
"vite-plugin-istanbul": "^9.0.1",
|
||||||
"vitest": "^4.1.11"
|
"vitest": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,16 +129,10 @@ type FieldFunctionProps = {
|
|||||||
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
|
||||||
*/
|
*/
|
||||||
export const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
export const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
||||||
const [dataType, setDataType] = React.useState(
|
|
||||||
getDataType(props.value, props.fieldSpec)
|
|
||||||
);
|
|
||||||
const [isEditing, setIsEditing] = React.useState(false);
|
const [isEditing, setIsEditing] = React.useState(false);
|
||||||
|
// Keep the expression editor mounted while typing, but otherwise select the
|
||||||
React.useEffect(() => {
|
// editor from the current value so a collapsed function never renders as stops.
|
||||||
if (!isEditing) {
|
const dataType = isEditing ? "expression" : getDataType(props.value, props.fieldSpec);
|
||||||
setDataType(getDataType(props.value, props.fieldSpec));
|
|
||||||
}
|
|
||||||
}, [props.value, props.fieldSpec, isEditing]);
|
|
||||||
|
|
||||||
const getFieldFunctionType = (fieldSpec: any) => {
|
const getFieldFunctionType = (fieldSpec: any) => {
|
||||||
if (fieldSpec.expression.interpolated) {
|
if (fieldSpec.expression.interpolated) {
|
||||||
@@ -173,7 +167,7 @@ export 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");
|
setIsEditing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteStop = (stopIdx: number) => {
|
const deleteStop = (stopIdx: number) => {
|
||||||
@@ -233,10 +227,10 @@ export const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
|
|||||||
type: "identity",
|
type: "identity",
|
||||||
property: value[1],
|
property: value[1],
|
||||||
});
|
});
|
||||||
setDataType("value");
|
setIsEditing(false);
|
||||||
} else if (isLiteralExpression(value)) {
|
} else if (isLiteralExpression(value)) {
|
||||||
props.onChange(fieldName, value[1]);
|
props.onChange(fieldName, value[1]);
|
||||||
setDataType("value");
|
setIsEditing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -106,13 +106,13 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
groupedLayers(): (LayerSpecification & {key: string})[][] {
|
groupedLayers(layers = this.props.layers): (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 < layers.length; i++) {
|
||||||
const origLayer = this.props.layers[i];
|
const origLayer = layers[i];
|
||||||
const previousLayer = this.props.layers[i-1];
|
const previousLayer = 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
|
||||||
);
|
);
|
||||||
@@ -192,7 +192,39 @@ class LayerListContainerInternal extends React.Component<LayerListContainerInter
|
|||||||
return propsChanged;
|
return propsChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preserves group state through surviving layer IDs when group indices change.
|
||||||
|
* If deletion joins groups, the result stays expanded when either group was.
|
||||||
|
*/
|
||||||
|
private preserveLayerGroupState(previousLayers: LayerSpecification[]) {
|
||||||
|
if (previousLayers === this.props.layers) return;
|
||||||
|
|
||||||
|
const collapsedByLayer = new Map<string, boolean>();
|
||||||
|
let idx = 0;
|
||||||
|
for (const layers of this.groupedLayers(previousLayers)) {
|
||||||
|
const collapsed = this.isCollapsed(layerPrefix(layers[0].id), idx);
|
||||||
|
for (const layer of layers) {
|
||||||
|
collapsedByLayer.set(layer.id, collapsed);
|
||||||
|
}
|
||||||
|
idx += layers.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapsedGroups: {[key: string]: boolean} = {};
|
||||||
|
idx = 0;
|
||||||
|
for (const layers of this.groupedLayers()) {
|
||||||
|
const lookupKey = [layerPrefix(layers[0].id), idx].join("-");
|
||||||
|
collapsedGroups[lookupKey] = layers.every(layer => collapsedByLayer.get(layer.id) !== false);
|
||||||
|
idx += layers.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lodash.isEqual(collapsedGroups, this.state.collapsedGroups)) {
|
||||||
|
this.setState({ collapsedGroups });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps: LayerListContainerProps) {
|
componentDidUpdate (prevProps: LayerListContainerProps) {
|
||||||
|
this.preserveLayerGroupState(prevProps.layers);
|
||||||
|
|
||||||
if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) {
|
if (prevProps.selectedLayerIndex !== this.props.selectedLayerIndex) {
|
||||||
const selectedItemNode = this.selectedItemRef.current;
|
const selectedItemNode = this.selectedItemRef.current;
|
||||||
if (selectedItemNode && selectedItemNode.node) {
|
if (selectedItemNode && selectedItemNode.node) {
|
||||||
|
|||||||
Reference in New Issue
Block a user