diff --git a/web/src/core/mantle/evaluator/simple/index.test.ts b/web/src/core/mantle/evaluator/simple/index.test.ts index 101cb4a9a..e1ee8094b 100644 --- a/web/src/core/mantle/evaluator/simple/index.test.ts +++ b/web/src/core/mantle/evaluator/simple/index.test.ts @@ -485,4 +485,51 @@ describe("Conditional styling", () => { }, }); }); + + test("Nested styling at 2nd degree", () => { + expect( + evalLayerAppearances( + { + marker: { + pointColor: "#FF0000", + pointSize: { + expression: { + conditions: [ + ["Number('0.32423%') < 1", "200"], + ["true", "1"], + ], + }, + }, + labelTypography: { + fontSize: 20, + color: { + expression: { + conditions: [ + ["${blah}==='value'", "color('#ff0000')"], + ["true", "color('#ffffff')"], + ], + }, + }, + }, + }, + }, + { + id: "x", + type: "simple", + properties: { + blah: "value", + }, + }, + ), + ).toEqual({ + marker: { + pointColor: "#FF0000", // blue + pointSize: 200, + labelTypography: { + fontSize: 20, + color: "#FF0000", + }, + }, + }); + }); }); diff --git a/web/src/core/mantle/evaluator/simple/index.ts b/web/src/core/mantle/evaluator/simple/index.ts index 5552129f2..657256fd1 100644 --- a/web/src/core/mantle/evaluator/simple/index.ts +++ b/web/src/core/mantle/evaluator/simple/index.ts @@ -59,15 +59,22 @@ export function evalLayerAppearances( properties: layer.properties || {}, }; } + + return Object.fromEntries( + Object.entries(appearance).map(([k, v]) => [k, recursiveValEval(v, layer, feature)]), + ); +} + +function recursiveValEval(obj: any, layer: LayerSimple, feature?: Feature): any { return Object.fromEntries( - Object.entries(appearance).map(([k, v]) => [ - k, - Object.fromEntries( - Object.entries(v).map(([k, v]) => { - return [k, evalExpression(v, layer, feature)]; - }), - ), - ]), + Object.entries(obj).map(([k, v]) => { + // if v is an object itself and not a null, recurse deeper + if (hasNonExpressionObject(v)) { + return [k, recursiveValEval(v, layer, feature)]; + } + // if v is not an object, apply the evalExpression function + return [k, evalExpression(v, layer, feature)]; + }), ); } @@ -76,23 +83,30 @@ export function clearAllExpressionCaches( feature: Feature | undefined, ) { const appearances: Partial = pick(layer, appearanceKeys); - Object.entries(appearances).forEach(([, v]) => { - Object.entries(v).forEach(([, expressionContainer]) => { - if (hasExpression(expressionContainer)) { - const styleExpression = expressionContainer.expression; - if (typeof styleExpression === "object" && styleExpression.conditions) { - styleExpression.conditions.forEach(([expression1, expression2]) => { - clearExpressionCaches(expression1, feature, layer?.defines); - clearExpressionCaches(expression2, feature, layer?.defines); - }); - } else if (typeof styleExpression === "boolean" || typeof styleExpression === "number") { - clearExpressionCaches(String(styleExpression), feature, layer?.defines); - } else if (typeof styleExpression === "string") { - clearExpressionCaches(styleExpression, feature, layer?.defines); - } + recursiveClear(v, layer, feature); + }); +} + +function recursiveClear(obj: any, layer: LayerSimple | undefined, feature: Feature | undefined) { + Object.entries(obj).forEach(([, v]) => { + // if v is an object itself and not a null, recurse deeper + if (hasNonExpressionObject(v)) { + recursiveClear(v, layer, feature); + } else if (hasExpression(v)) { + // if v is not an object, apply the clearExpressionCaches function + const styleExpression = v.expression; + if (typeof styleExpression === "object" && styleExpression.conditions) { + styleExpression.conditions.forEach(([expression1, expression2]) => { + clearExpressionCaches(expression1, feature, layer?.defines); + clearExpressionCaches(expression2, feature, layer?.defines); + }); + } else if (typeof styleExpression === "boolean" || typeof styleExpression === "number") { + clearExpressionCaches(String(styleExpression), feature, layer?.defines); + } else if (typeof styleExpression === "string") { + clearExpressionCaches(styleExpression, feature, layer?.defines); } - }); + } }); } @@ -100,6 +114,10 @@ function hasExpression(e: any): e is ExpressionContainer { return typeof e === "object" && e && "expression" in e; } +function hasNonExpressionObject(v: any): boolean { + return typeof v === "object" && v && !("expression" in v); +} + function evalExpression( expressionContainer: any, layer: LayerSimple, diff --git a/web/src/core/mantle/types/appearance.ts b/web/src/core/mantle/types/appearance.ts index 1dfafb951..1824860aa 100644 --- a/web/src/core/mantle/types/appearance.ts +++ b/web/src/core/mantle/types/appearance.ts @@ -10,7 +10,7 @@ import type { } from "./value"; export type LayerAppearance = { - [K in keyof T]?: T[K] | ExpressionContainer; + [K in keyof T]?: T[K] | LayerAppearance | ExpressionContainer; }; export type LayerAppearanceTypes = {