@emuanalytics/flow-rdf-core 4.0.4 → 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -64
- package/dist/index.d.mts +36 -31
- package/dist/index.d.ts +36 -31
- package/dist/index.mjs +20 -3249
- package/dist/lib/context/context-registry.mjs +63 -0
- package/dist/lib/context/debugger.mjs +240 -0
- package/dist/lib/context/flow-context.mjs +61 -0
- package/dist/lib/context/global-state.mjs +70 -0
- package/dist/lib/context/map-registry.mjs +59 -0
- package/dist/lib/context/query-registry.mjs +53 -0
- package/dist/lib/errors/flow-error.mjs +10 -0
- package/dist/lib/errors/flow-expression-error.mjs +14 -0
- package/dist/lib/errors/flow-map-error.mjs +9 -0
- package/dist/lib/expressions/expression-parser.mjs +198 -0
- package/dist/lib/expressions/functions/from-palette.mjs +75 -0
- package/dist/lib/expressions/functions/regex.mjs +25 -0
- package/dist/lib/expressions/functions/state.mjs +25 -0
- package/dist/lib/expressions/parser-helpers.mjs +37 -0
- package/dist/lib/expressions/reactive-expression.mjs +253 -0
- package/dist/lib/map/flow-core-map.mjs +749 -0
- package/dist/lib/map/plugins/annotations/annotation-layers.mjs +163 -0
- package/dist/lib/map/plugins/annotations/annotation.events.mjs +27 -0
- package/dist/lib/map/plugins/annotations/annotation.plugin.mjs +498 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/modes/DirectModeOverride.mjs +67 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/modes/DragCircleMode.mjs +65 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/utils/create_supplementary_points_circle.mjs +15 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/utils/drag_pan.mjs +20 -0
- package/dist/lib/map/plugins/data-join.plugin.mjs +173 -0
- package/dist/lib/map/plugins/export/export.plugin.mjs +21 -0
- package/dist/lib/map/plugins/export/map-exporter.mjs +177 -0
- package/dist/lib/map/plugins/hover-effect.plugin.mjs +58 -0
- package/dist/lib/map/plugins/index.d.mts +37 -72
- package/dist/lib/map/plugins/index.d.ts +37 -72
- package/dist/lib/map/plugins/index.mjs +11 -1810
- package/dist/lib/map/plugins/keyboard-shortcut-manager.mjs +120 -0
- package/dist/lib/map/plugins/map-plugin-base.mjs +25 -0
- package/dist/lib/map/plugins/plugin-manager.mjs +135 -0
- package/dist/lib/map/plugins/popup.plugin.mjs +55 -0
- package/dist/lib/map/plugins/selection/selection.events.mjs +21 -0
- package/dist/lib/map/plugins/selection/selection.plugin.mjs +136 -0
- package/dist/lib/map/plugins/selection/source-selection.mjs +156 -0
- package/dist/lib/map/plugins/url-sync.plugin.mjs +55 -0
- package/dist/lib/map/support/image-registry.mjs +82 -0
- package/dist/lib/map/support/map-events.mjs +71 -0
- package/dist/lib/map/support/map-feature-stream.mjs +158 -0
- package/dist/lib/map/support/map-popup.mjs +159 -0
- package/dist/lib/map/types.mjs +7 -0
- package/dist/lib/map-style/map-data-source.mjs +192 -0
- package/dist/lib/map-style/map-layer-style-builder-generated.mjs +1307 -0
- package/dist/lib/map-style/map-layer-style-builder.mjs +86 -0
- package/dist/lib/map-style/map-layer.mjs +223 -0
- package/dist/lib/map-style/map-style.mjs +178 -0
- package/dist/lib/queries/async-committer.mjs +30 -0
- package/dist/lib/queries/query.mjs +172 -0
- package/dist/lib/state-store/decorators.mjs +83 -0
- package/dist/lib/state-store/state-store.mjs +534 -0
- package/dist/lib/state-store/utils.mjs +7 -0
- package/dist/lib/utils/announce.mjs +12 -0
- package/dist/lib/utils/assert.mjs +12 -0
- package/dist/{shared/flow-rdf-core.187cb345.mjs → lib/utils/debounce.mjs} +3 -39
- package/dist/lib/utils/decorators.mjs +59 -0
- package/dist/lib/utils/deferred.mjs +11 -0
- package/dist/lib/utils/ensure-array.mjs +16 -0
- package/dist/{shared/flow-rdf-core.1f0901c5.mjs → lib/utils/feature-type-checks.mjs} +2 -2
- package/dist/lib/utils/functional.mjs +22 -0
- package/dist/lib/utils/global.mjs +18 -0
- package/dist/lib/utils/index.mjs +8 -5
- package/dist/lib/utils/random-id.mjs +9 -0
- package/dist/lib/utils/string-utils.mjs +3 -0
- package/dist/lib/utils/throttle.mjs +20 -0
- package/dist/{shared/flow-rdf-core.25e5f6d7.mjs → lib/utils/type-checks.mjs} +1 -26
- package/dist/lib/utils/watchable-promise.mjs +55 -0
- package/dist/package.json.mjs +3 -0
- package/dist/shared/{flow-rdf-core.4dde996e.d.cts → flow-rdf-core.8b5acf14.d.mts} +1315 -172
- package/dist/shared/{flow-rdf-core.4dde996e.d.ts → flow-rdf-core.8b5acf14.d.ts} +1315 -172
- package/package.json +4 -8
- package/dist/index.cjs +0 -3316
- package/dist/index.d.cts +0 -90
- package/dist/lib/map/plugins/index.cjs +0 -1850
- package/dist/lib/map/plugins/index.d.cts +0 -345
- package/dist/lib/utils/index.cjs +0 -48
- package/dist/lib/utils/index.d.cts +0 -160
- package/dist/shared/flow-rdf-core.06e85a6c.cjs +0 -1394
- package/dist/shared/flow-rdf-core.4dde996e.d.mts +0 -1916
- package/dist/shared/flow-rdf-core.55c169ca.cjs +0 -150
- package/dist/shared/flow-rdf-core.63162669.d.cts +0 -10
- package/dist/shared/flow-rdf-core.65045caf.mjs +0 -1360
- package/dist/shared/flow-rdf-core.a82b9be4.cjs +0 -34
- package/dist/shared/flow-rdf-core.f50908f0.cjs +0 -102
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import P from 'parsimmon';
|
|
2
|
+
import { PREFIX, BINARY_RIGHT, BINARY_LEFT, TERNARY_RIGHT } from './parser-helpers.mjs';
|
|
3
|
+
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __publicField = (obj, key, value) => {
|
|
7
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
class ExpressionParser {
|
|
11
|
+
constructor() {
|
|
12
|
+
__publicField(this, "language");
|
|
13
|
+
this.language = this.createLanguage();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse an expression
|
|
17
|
+
*
|
|
18
|
+
* @param expr Expression to parse
|
|
19
|
+
*/
|
|
20
|
+
parseExpression(expr) {
|
|
21
|
+
return this.language.Expr.parse(expr);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse a complete assignment program of the form:
|
|
25
|
+
* a: expr1;
|
|
26
|
+
* b: expr2;
|
|
27
|
+
*
|
|
28
|
+
* @param program Program to parse
|
|
29
|
+
*/
|
|
30
|
+
parseProgram(program) {
|
|
31
|
+
return this.language.ProgramOrEmpty.parse(program);
|
|
32
|
+
}
|
|
33
|
+
createLanguage() {
|
|
34
|
+
const operatorTable = [
|
|
35
|
+
{ type: PREFIX, ops: this.operators({ "-": "-", "!": "!" }) },
|
|
36
|
+
{ type: BINARY_RIGHT, ops: this.operators({ "^": "**" }) },
|
|
37
|
+
{ type: BINARY_LEFT, ops: this.operators({ "*": "*", "/": "/", "%": "%" }) },
|
|
38
|
+
{ type: BINARY_LEFT, ops: this.operators({ "+": "+", "-": "-" }) },
|
|
39
|
+
{ type: BINARY_LEFT, ops: this.operators({ ">=": ">=", "<=": "<=" }) },
|
|
40
|
+
{ type: BINARY_LEFT, ops: this.operators({ "<": "<", ">": ">" }) },
|
|
41
|
+
{
|
|
42
|
+
type: BINARY_LEFT,
|
|
43
|
+
ops: this.operators({ regexi: "~*", equali: "=*", "==": "==", "!=": "!=", "~": "~" })
|
|
44
|
+
},
|
|
45
|
+
{ type: BINARY_LEFT, ops: this.operators({ all: "&&" }) },
|
|
46
|
+
{ type: BINARY_LEFT, ops: this.operators({ any: "||" }) },
|
|
47
|
+
{ type: TERNARY_RIGHT, ops: this.operators({ case: "?" }) }
|
|
48
|
+
];
|
|
49
|
+
return P.createLanguage({
|
|
50
|
+
Whitespace: () => P.regexp(/[ \n]*/).desc("whitespace"),
|
|
51
|
+
NotNewline: () => P.regexp(/[^\n]*/).desc("non-newline"),
|
|
52
|
+
LineComment: (r) => r.NotNewline.wrap(P.string("//"), P.alt(P.string("\n"), P.eof)),
|
|
53
|
+
BlockComment: () => P.regexp(/\/\*[\s\S]*?\*\//),
|
|
54
|
+
Comment: (r) => P.alt(r.LineComment, r.BlockComment).desc("comment"),
|
|
55
|
+
End: () => P.alt(P.string(";"), P.eof),
|
|
56
|
+
_: (r) => r.Comment.sepBy(r.Whitespace).trim(r.Whitespace),
|
|
57
|
+
Integer: () => {
|
|
58
|
+
return P.regexp(/-?[0-9]+/).map(Number);
|
|
59
|
+
},
|
|
60
|
+
Decimal: () => {
|
|
61
|
+
return P.regexp(/-?[0-9]+\.[0-9]+/).map(Number);
|
|
62
|
+
},
|
|
63
|
+
Number: (r) => {
|
|
64
|
+
return P.alt(r.Decimal, r.Integer).desc("number");
|
|
65
|
+
},
|
|
66
|
+
Boolean: () => {
|
|
67
|
+
return P.alt(P.string("true"), P.string("false")).map((val) => val === "true").desc("boolean");
|
|
68
|
+
},
|
|
69
|
+
Null: () => {
|
|
70
|
+
return P.string("null").result(null).desc("null");
|
|
71
|
+
},
|
|
72
|
+
DoubleQuoteString: () => {
|
|
73
|
+
return P.regexp(/"((?:\\.|.)*?)"/, 1).desc("string");
|
|
74
|
+
},
|
|
75
|
+
SingleQuoteString: () => {
|
|
76
|
+
return P.regexp(/'((?:\\.|.)*?)'/, 1).desc("string");
|
|
77
|
+
},
|
|
78
|
+
UnquotedString: () => {
|
|
79
|
+
return P.regexp(/[a-zA-Z_][a-zA-Z0-9_-]*/).desc("string");
|
|
80
|
+
},
|
|
81
|
+
HexColor: () => {
|
|
82
|
+
return P.regexp(/#(?:[0-9a-f]{3}){1,2}/i).desc("hex color");
|
|
83
|
+
},
|
|
84
|
+
String: (r) => {
|
|
85
|
+
return P.alt(r.SingleQuoteString, r.DoubleQuoteString, r.HexColor, r.UnquotedString).map(
|
|
86
|
+
(s) => s.replace(/\\n/gm, "\n")
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
Symbol: () => {
|
|
90
|
+
return P.regexp(/[a-zA-Z_][a-zA-Z0-9_-]*/).desc("symbol");
|
|
91
|
+
},
|
|
92
|
+
PartialSymbol: () => {
|
|
93
|
+
return P.regexp(/[a-zA-Z0-9_-]+/).desc("symbol");
|
|
94
|
+
},
|
|
95
|
+
Feature: (r) => {
|
|
96
|
+
return P.seqMap(
|
|
97
|
+
P.string("@"),
|
|
98
|
+
r.PartialSymbol.sepBy(P.string(".")),
|
|
99
|
+
(_feat, symbols) => ["get"].concat(symbols)
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
FeatureState: (r) => {
|
|
103
|
+
return P.seqMap(
|
|
104
|
+
P.string("@@"),
|
|
105
|
+
r.PartialSymbol.sepBy(P.string(".")),
|
|
106
|
+
(_feat, symbols) => this.expandFeatureState(symbols)
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
Array: (r) => P.string("[").trim(r._).then(r.Value.trim(r._).sepBy(P.string(","))).skip(P.string("]")),
|
|
110
|
+
State: (r) => {
|
|
111
|
+
return P.seqMap(
|
|
112
|
+
P.string("$"),
|
|
113
|
+
r.PartialSymbol.sepBy(P.string(".")),
|
|
114
|
+
(_state, symbols) => ["state"].concat(symbols)
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
Paren: (r) => {
|
|
118
|
+
return P.seqMap(P.string("("), r.Expr.trim(r._), P.string(")"), (_lb, expr, _rb) => expr);
|
|
119
|
+
},
|
|
120
|
+
Func: (r) => {
|
|
121
|
+
return P.seqMap(
|
|
122
|
+
r.Symbol.trim(r._),
|
|
123
|
+
P.string("("),
|
|
124
|
+
r.Expr.trim(r._).sepBy(P.string(",")),
|
|
125
|
+
P.string(")"),
|
|
126
|
+
(func, _lb, args, _rb) => [func, ...args]
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
Value: (r) => {
|
|
130
|
+
return P.alt(
|
|
131
|
+
r.Paren,
|
|
132
|
+
r.Array,
|
|
133
|
+
r.Func,
|
|
134
|
+
r.Boolean,
|
|
135
|
+
r.Null,
|
|
136
|
+
r.Number,
|
|
137
|
+
r.String,
|
|
138
|
+
r.FeatureState,
|
|
139
|
+
r.Feature,
|
|
140
|
+
r.State
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
Expr: (r) => {
|
|
144
|
+
return this.tableParser(operatorTable, r.Value);
|
|
145
|
+
},
|
|
146
|
+
PropertyName: () => {
|
|
147
|
+
return P.regexp(/[a-zA-Z_-][a-zA-Z0-9_-]*/).desc("symbol");
|
|
148
|
+
},
|
|
149
|
+
Assignment: (r) => {
|
|
150
|
+
return P.seqMap(
|
|
151
|
+
r.PropertyName,
|
|
152
|
+
P.string(":").trim(r._),
|
|
153
|
+
r.Expr.trim(r._).node("expr"),
|
|
154
|
+
r.End,
|
|
155
|
+
(key, _colon, node) => [key, { expr: node.value, start: node.start, end: node.end }]
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
Program: (r) => {
|
|
159
|
+
return r.Assignment.trim(r._).many().map((statements) => {
|
|
160
|
+
return statements.filter((s) => s !== "").reduce((acc, s) => {
|
|
161
|
+
acc[s[0]] = s[1];
|
|
162
|
+
return acc;
|
|
163
|
+
}, {});
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
EmptyProgram: (r) => {
|
|
167
|
+
return P.seqMap(r._, P.eof, () => null);
|
|
168
|
+
},
|
|
169
|
+
ProgramOrEmpty: (r) => {
|
|
170
|
+
return P.alt(r.EmptyProgram, r.Program);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Helper function to create a parser that parsers a set of operators at the same precedence level
|
|
176
|
+
*
|
|
177
|
+
* @param ops Operator table
|
|
178
|
+
*/
|
|
179
|
+
operators(ops) {
|
|
180
|
+
const keys = Object.keys(ops);
|
|
181
|
+
const ps = keys.map((k) => P.string(ops[k]).trim(P.optWhitespace).result(k));
|
|
182
|
+
return P.alt.apply(null, ps);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Helper function to create a table-driver operator parser
|
|
186
|
+
*
|
|
187
|
+
*/
|
|
188
|
+
tableParser(operatorTable, valueParser) {
|
|
189
|
+
return operatorTable.reduce((acc, level) => level.type(level.ops, acc), valueParser);
|
|
190
|
+
}
|
|
191
|
+
expandFeatureState(symbols) {
|
|
192
|
+
let expandedFeatureState = ["feature-state", symbols.shift()];
|
|
193
|
+
symbols.forEach((s) => expandedFeatureState = ["get", s, expandedFeatureState]);
|
|
194
|
+
return expandedFeatureState;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export { ExpressionParser };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import colorbrewer from 'colorbrewer';
|
|
2
|
+
import { isString, isArray } from '../../utils/type-checks.mjs';
|
|
3
|
+
import { FlowCoreExpressionError } from '../../errors/flow-expression-error.mjs';
|
|
4
|
+
|
|
5
|
+
const palettes = {};
|
|
6
|
+
function registerColorBrewerPalettes() {
|
|
7
|
+
Object.keys(colorbrewer).filter((key) => key !== "schemeGroups").forEach(
|
|
8
|
+
(key) => palettes[key.toLowerCase()] = {
|
|
9
|
+
id: key,
|
|
10
|
+
colors: colorbrewer[key][5]
|
|
11
|
+
}
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function fromPalette(expr, parentExpression) {
|
|
15
|
+
const paletteIdOrArray = expr[1];
|
|
16
|
+
const property = expr[2];
|
|
17
|
+
let colorStops = expr.slice(3);
|
|
18
|
+
let palette;
|
|
19
|
+
if (isString(paletteIdOrArray)) {
|
|
20
|
+
palette = palettes[paletteIdOrArray.toLowerCase()]?.colors || palettes["Greys"].colors;
|
|
21
|
+
} else if (isArray(paletteIdOrArray) && paletteIdOrArray[0] === "literal") {
|
|
22
|
+
palette = paletteIdOrArray[1];
|
|
23
|
+
} else {
|
|
24
|
+
throw new FlowCoreExpressionError(parentExpression.expression, "evaluationError", [
|
|
25
|
+
"from-palette requires palette id as string or custom palette array"
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
const numPaletteEntries = palette.length;
|
|
29
|
+
let reversePalette = false;
|
|
30
|
+
if (numPaletteEntries < 5) {
|
|
31
|
+
throw new FlowCoreExpressionError(parentExpression.expression, "evaluationError", [
|
|
32
|
+
"custom palette requires at least 5 color entries"
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
switch (colorStops.length) {
|
|
36
|
+
case 2:
|
|
37
|
+
break;
|
|
38
|
+
case 3:
|
|
39
|
+
reversePalette = !!colorStops[2];
|
|
40
|
+
colorStops = colorStops.slice(0, 2);
|
|
41
|
+
break;
|
|
42
|
+
case numPaletteEntries:
|
|
43
|
+
break;
|
|
44
|
+
case numPaletteEntries + 1:
|
|
45
|
+
reversePalette = !!colorStops[numPaletteEntries];
|
|
46
|
+
colorStops = colorStops.slice(0, numPaletteEntries);
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
throw new FlowCoreExpressionError(parentExpression.expression, "evaluationError", [
|
|
50
|
+
"incorrect color stop arguments"
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
if (colorStops.length === 2) {
|
|
54
|
+
const min = colorStops[0];
|
|
55
|
+
const max = colorStops[1];
|
|
56
|
+
colorStops = [];
|
|
57
|
+
const step = (max - min) / (numPaletteEntries - 1);
|
|
58
|
+
for (let i = 0; i < numPaletteEntries; i++) {
|
|
59
|
+
colorStops.push(min + i * step);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
expr = ["interpolate-lab", ["linear"], property];
|
|
63
|
+
for (let i = 0; i < colorStops.length; i++) {
|
|
64
|
+
expr.push(colorStops[i]);
|
|
65
|
+
expr.push(reversePalette ? palette[colorStops.length - i - 1] : palette[i]);
|
|
66
|
+
}
|
|
67
|
+
return expr;
|
|
68
|
+
}
|
|
69
|
+
const customFromPaletteFn = {
|
|
70
|
+
name: "from-palette",
|
|
71
|
+
initialize: registerColorBrewerPalettes,
|
|
72
|
+
fn: fromPalette
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export { customFromPaletteFn as default };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function regexFunction(operator, caseSensitive = true) {
|
|
2
|
+
return (expr, _) => {
|
|
3
|
+
expr[0] = operator;
|
|
4
|
+
expr.push(["collator", { "case-sensitive": caseSensitive }]);
|
|
5
|
+
return expr;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
const equal = {
|
|
9
|
+
name: "equal",
|
|
10
|
+
fn: regexFunction("==", true)
|
|
11
|
+
};
|
|
12
|
+
const equali = {
|
|
13
|
+
name: "iequal",
|
|
14
|
+
fn: regexFunction("==", false)
|
|
15
|
+
};
|
|
16
|
+
const regex = {
|
|
17
|
+
name: "regex",
|
|
18
|
+
fn: regexFunction("~", true)
|
|
19
|
+
};
|
|
20
|
+
const regexi = {
|
|
21
|
+
name: "regexi",
|
|
22
|
+
fn: regexFunction("~", false)
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { equal, equali, regex, regexi };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isNil } from '../../utils/type-checks.mjs';
|
|
2
|
+
|
|
3
|
+
function stateCustomFunction(expr, parentExpression) {
|
|
4
|
+
if (!parentExpression.store) {
|
|
5
|
+
console.warn(
|
|
6
|
+
`The reactive expression '${parentExpression.expression}' references state properties but is not bound to a state store. The expression cannot be evaluated - returning null.`
|
|
7
|
+
);
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const path = expr.slice(1).join(".");
|
|
11
|
+
const val = parentExpression.store.get(path);
|
|
12
|
+
if (isNil(val)) {
|
|
13
|
+
return null;
|
|
14
|
+
} else if (typeof val === "object") {
|
|
15
|
+
return ["literal", val];
|
|
16
|
+
} else {
|
|
17
|
+
return val;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const customStateFn = {
|
|
21
|
+
name: "state",
|
|
22
|
+
fn: stateCustomFunction
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { customStateFn as default };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import P from 'parsimmon';
|
|
2
|
+
|
|
3
|
+
function PREFIX(operatorsParser, nextParser) {
|
|
4
|
+
const parser = P.lazy(() => {
|
|
5
|
+
return P.seq(operatorsParser, parser).or(nextParser);
|
|
6
|
+
});
|
|
7
|
+
return parser;
|
|
8
|
+
}
|
|
9
|
+
function BINARY_RIGHT(operatorsParser, nextParser) {
|
|
10
|
+
const parser = P.lazy(() => nextParser.chain((next) => P.seq(operatorsParser, P.of(next), parser).or(P.of(next))));
|
|
11
|
+
return parser;
|
|
12
|
+
}
|
|
13
|
+
function TERNARY_RIGHT(operatorsParser, nextParser) {
|
|
14
|
+
const parser = P.lazy(
|
|
15
|
+
() => nextParser.chain(
|
|
16
|
+
(next) => P.seqMap(
|
|
17
|
+
operatorsParser,
|
|
18
|
+
P.of(next),
|
|
19
|
+
parser,
|
|
20
|
+
P.string(":").trim(P.optWhitespace),
|
|
21
|
+
parser,
|
|
22
|
+
(op, cond, val1, _colon, val2) => [op, ["to-boolean", cond], val1, val2]
|
|
23
|
+
).or(P.of(next))
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
return parser;
|
|
27
|
+
}
|
|
28
|
+
function BINARY_LEFT(operatorsParser, nextParser) {
|
|
29
|
+
return P.seqMap(nextParser, P.seq(operatorsParser, nextParser).many(), (first, rest) => {
|
|
30
|
+
return rest.reduce((acc, ch) => {
|
|
31
|
+
const [op, another] = ch;
|
|
32
|
+
return [op, acc, another];
|
|
33
|
+
}, first);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { BINARY_LEFT, BINARY_RIGHT, PREFIX, TERNARY_RIGHT };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import * as maplibreSpec from '@emuanalytics/maplibre-gl-style-spec';
|
|
2
|
+
import { makeObservable, observable, computed, action } from 'mobx';
|
|
3
|
+
import { toStream } from 'mobx-utils';
|
|
4
|
+
import { FlowCoreExpressionError } from '../errors/flow-expression-error.mjs';
|
|
5
|
+
import { isDefined, isString } from '../utils/type-checks.mjs';
|
|
6
|
+
import { ExpressionParser } from './expression-parser.mjs';
|
|
7
|
+
import customFromPaletteFn from './functions/from-palette.mjs';
|
|
8
|
+
import { regex, regexi, equal, equali } from './functions/regex.mjs';
|
|
9
|
+
import customStateFn from './functions/state.mjs';
|
|
10
|
+
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __publicField = (obj, key, value) => {
|
|
14
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
const _ReactiveExpression = class _ReactiveExpression {
|
|
18
|
+
/**
|
|
19
|
+
* Constructs a new `ReactiveExpression`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```js
|
|
23
|
+
* const ex = new ReactiveExpression('1 + 2');
|
|
24
|
+
* console.log(ex.value); // 3
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @param expression - the string representation of the expression
|
|
28
|
+
* @param parsedExpression - Used internally to bypass parsing when evaluating a program.
|
|
29
|
+
*/
|
|
30
|
+
constructor(expression, parsedExpression) {
|
|
31
|
+
__publicField(this, "_expression", "");
|
|
32
|
+
__publicField(this, "parsedResult");
|
|
33
|
+
__publicField(this, "_store", null);
|
|
34
|
+
makeObservable(
|
|
35
|
+
this,
|
|
36
|
+
{
|
|
37
|
+
_store: observable.ref,
|
|
38
|
+
maplibreExpression: computed,
|
|
39
|
+
value: computed,
|
|
40
|
+
bindState: action
|
|
41
|
+
},
|
|
42
|
+
{ name: this.formatName(expression) }
|
|
43
|
+
);
|
|
44
|
+
this._expression = expression;
|
|
45
|
+
if (isDefined(parsedExpression)) {
|
|
46
|
+
this.parsedResult = {
|
|
47
|
+
status: true,
|
|
48
|
+
value: parsedExpression
|
|
49
|
+
};
|
|
50
|
+
} else {
|
|
51
|
+
this.parsedResult = _ReactiveExpression.parser.parseExpression(expression);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns `true` if the specified custom function has been registered.
|
|
56
|
+
*
|
|
57
|
+
* @param fn - The custom function name
|
|
58
|
+
*/
|
|
59
|
+
static hasCustomFunction(fn) {
|
|
60
|
+
return !!this.customFunctions[fn];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Register a custom function to be used in reactive expressions.
|
|
64
|
+
*
|
|
65
|
+
* @param fn - The custom function to register
|
|
66
|
+
*/
|
|
67
|
+
static registerCustomFunction(fn) {
|
|
68
|
+
if (fn.initialize)
|
|
69
|
+
fn.initialize();
|
|
70
|
+
this.customFunctions[fn.name] = fn;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parses a program string into a map of properties and their associated {@link ReactiveExpression}.
|
|
74
|
+
*
|
|
75
|
+
* The program string should be a series of property/expression pairs separated by semicolons as follows:
|
|
76
|
+
*
|
|
77
|
+
* ```js
|
|
78
|
+
* const program = `
|
|
79
|
+
* prop1: 1 + 2;
|
|
80
|
+
* prop2: 'a string';
|
|
81
|
+
* prop3: $stateProp * 3;
|
|
82
|
+
* `;
|
|
83
|
+
*
|
|
84
|
+
* const parsedProgram = ReactiveExpression.parseProgram(program);
|
|
85
|
+
* // {
|
|
86
|
+
* // prop1: ReactiveExpression,
|
|
87
|
+
* // prop2: ReactiveExpression,
|
|
88
|
+
* // prop3: ReactiveExpression
|
|
89
|
+
* // }
|
|
90
|
+
*
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @param program - The program to parse
|
|
94
|
+
* @returns A map of property names and their associated expressions.
|
|
95
|
+
*/
|
|
96
|
+
static parseProgram(program) {
|
|
97
|
+
const parsedProgram = this.parser.parseProgram(program);
|
|
98
|
+
if (isString(program) && program.length === 0) {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
if (parsedProgram.status) {
|
|
102
|
+
const keys = Object.keys(parsedProgram.value);
|
|
103
|
+
const expressions = keys.reduce((accum, key) => {
|
|
104
|
+
const expr = parsedProgram.value[key];
|
|
105
|
+
const expressionString = program.slice(expr.start.offset, expr.end.offset);
|
|
106
|
+
accum[key] = new _ReactiveExpression(expressionString, expr.expr);
|
|
107
|
+
return accum;
|
|
108
|
+
}, {});
|
|
109
|
+
return expressions;
|
|
110
|
+
} else {
|
|
111
|
+
throw new FlowCoreExpressionError(program, "parseError", [
|
|
112
|
+
`expected ${parsedProgram.expected.join(" ")} at line ${parsedProgram.index.line}, column ${parsedProgram.index.column}]`
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Generates the MapLibre-compatible representation of this expression.
|
|
118
|
+
*
|
|
119
|
+
* This is used internally when creating MapLibre style specifications
|
|
120
|
+
* from Flo.w RDF {@link MapLayer} styles.
|
|
121
|
+
*
|
|
122
|
+
* This getter is a reactive `computed` and will re-evaluate within a
|
|
123
|
+
* reactive context when the expression value changes.
|
|
124
|
+
*
|
|
125
|
+
* @computed
|
|
126
|
+
*
|
|
127
|
+
* @returns The MapLibre-compatible representation of this expression
|
|
128
|
+
*/
|
|
129
|
+
get maplibreExpression() {
|
|
130
|
+
if (this.isParseError(this.parsedResult)) {
|
|
131
|
+
throw this.parseError;
|
|
132
|
+
}
|
|
133
|
+
return this.expandCustomFunctions(this.parsedResult.value);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Evaluates the expression and returns the result.
|
|
137
|
+
*
|
|
138
|
+
* This getter is a reactive `computed` and will re-evaluate within a
|
|
139
|
+
* reactive context when the expression value changes.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```js
|
|
143
|
+
* const ex = new ReactiveExpression('1 + 2');
|
|
144
|
+
* console.log(ex.value); // 3
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @computed
|
|
148
|
+
*
|
|
149
|
+
* @returns - The result of evaluating the expression
|
|
150
|
+
*/
|
|
151
|
+
get value() {
|
|
152
|
+
return this.evaluateExpression();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Returns details of parsing errors if the expression could not be parsed.
|
|
156
|
+
*/
|
|
157
|
+
get parseError() {
|
|
158
|
+
if (this.isParseError(this.parsedResult)) {
|
|
159
|
+
return new FlowCoreExpressionError(this._expression, "parseError", [
|
|
160
|
+
`index: ${this.parsedResult.index.offset}`,
|
|
161
|
+
`expected: ${this.parsedResult.expected.join(" ")}`
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Returns the original expression string passed to the constructor.
|
|
168
|
+
*/
|
|
169
|
+
get expression() {
|
|
170
|
+
return this._expression;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Returns the state store bound to the expression.
|
|
174
|
+
*/
|
|
175
|
+
get store() {
|
|
176
|
+
return this._store;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Binds a {@link StateStore} to the expression. The state store is used to resolve
|
|
180
|
+
* state variables in the expression.
|
|
181
|
+
*
|
|
182
|
+
* Note: If no state store is bound, the expression will not be able to resolve
|
|
183
|
+
* state variables and will return `null` when evaluated. A console warning will
|
|
184
|
+
* be logged.
|
|
185
|
+
*
|
|
186
|
+
* @param store {@link StateStore} to bind to the expression
|
|
187
|
+
* @returns
|
|
188
|
+
*/
|
|
189
|
+
bindState(store) {
|
|
190
|
+
this._store = store;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Returns a subscribable stream, which produces the value of the expression
|
|
195
|
+
* whenever a reactive update causes the expression to re-evaluate.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```js
|
|
199
|
+
* const ex = new ReactiveExpression('$val * 2');
|
|
200
|
+
* const subscription = ex.toStream().subscribe((value) => console.log(value));
|
|
201
|
+
* ...
|
|
202
|
+
* subscription.unsubscribe();
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* @param fireImmediately - If `true`, the stream will fire immediately with the current value.
|
|
206
|
+
* If `false` the stream will only fire when the value changes.
|
|
207
|
+
*
|
|
208
|
+
* @returns A subscribable stream of the expression value
|
|
209
|
+
*/
|
|
210
|
+
toStream(fireImmediately = true) {
|
|
211
|
+
return toStream(() => this.value, fireImmediately);
|
|
212
|
+
}
|
|
213
|
+
evaluateExpression() {
|
|
214
|
+
const { result, value } = maplibreSpec.expression.createExpression(this.maplibreExpression);
|
|
215
|
+
if (result === "success") {
|
|
216
|
+
return value.evaluate({ zoom: 0 });
|
|
217
|
+
}
|
|
218
|
+
throw new FlowCoreExpressionError(
|
|
219
|
+
this.expression,
|
|
220
|
+
"evaluationError",
|
|
221
|
+
value.map((value2) => value2.message)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
expandCustomFunctions(expr) {
|
|
225
|
+
if (this.isFunctionExpression(expr)) {
|
|
226
|
+
const func = expr[0];
|
|
227
|
+
const args = expr.slice(1).map((arg) => this.expandCustomFunctions(arg));
|
|
228
|
+
expr = [func, ...args];
|
|
229
|
+
return _ReactiveExpression.hasCustomFunction(func) ? _ReactiveExpression.customFunctions[func].fn(expr, this) : expr;
|
|
230
|
+
}
|
|
231
|
+
return expr;
|
|
232
|
+
}
|
|
233
|
+
isParseError(val) {
|
|
234
|
+
return !val.status;
|
|
235
|
+
}
|
|
236
|
+
isFunctionExpression(expr) {
|
|
237
|
+
return Array.isArray(expr);
|
|
238
|
+
}
|
|
239
|
+
formatName(expr) {
|
|
240
|
+
return `FlowCoreExpression<${expr}>}`;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
__publicField(_ReactiveExpression, "parser", new ExpressionParser());
|
|
244
|
+
__publicField(_ReactiveExpression, "customFunctions", {});
|
|
245
|
+
let ReactiveExpression = _ReactiveExpression;
|
|
246
|
+
ReactiveExpression.registerCustomFunction(customStateFn);
|
|
247
|
+
ReactiveExpression.registerCustomFunction(customFromPaletteFn);
|
|
248
|
+
ReactiveExpression.registerCustomFunction(regex);
|
|
249
|
+
ReactiveExpression.registerCustomFunction(regexi);
|
|
250
|
+
ReactiveExpression.registerCustomFunction(equal);
|
|
251
|
+
ReactiveExpression.registerCustomFunction(equali);
|
|
252
|
+
|
|
253
|
+
export { ReactiveExpression };
|