@aglyn/plugins-logic 1.0.0-beta.246 → 1.0.0-beta.249
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/package.json +8 -8
- package/src/lib/components/formula-builder-dialog.component.d.ts +33 -0
- package/src/lib/components/formula-builder-dialog.component.js +331 -0
- package/src/lib/components/formula-builder-dialog.component.js.map +1 -0
- package/src/lib/components/formula-builder.component.d.ts +30 -0
- package/src/lib/components/formula-builder.component.js +62 -0
- package/src/lib/components/formula-builder.component.js.map +1 -0
- package/src/lib/components/host-functions-card.component.js +167 -62
- package/src/lib/components/host-functions-card.component.js.map +1 -1
- package/src/lib/components/host-variables-card.component.js +14 -9
- package/src/lib/components/host-variables-card.component.js.map +1 -1
- package/src/lib/components/parameter-choices-editor.component.d.ts +31 -0
- package/src/lib/components/parameter-choices-editor.component.js +124 -0
- package/src/lib/components/parameter-choices-editor.component.js.map +1 -0
- package/src/lib/components/structured-value-editor.component.d.ts +30 -0
- package/src/lib/components/structured-value-editor.component.js +189 -0
- package/src/lib/components/structured-value-editor.component.js.map +1 -0
- package/src/lib/model/formula-names.d.ts +41 -0
- package/src/lib/model/formula-names.js +82 -0
- package/src/lib/model/formula-names.js.map +1 -0
- package/src/lib/model/formula.d.ts +72 -0
- package/src/lib/model/formula.js +264 -0
- package/src/lib/model/formula.js.map +1 -0
- package/src/lib/model/parameter-options.d.ts +16 -0
- package/src/lib/model/parameter-options.js +31 -0
- package/src/lib/model/parameter-options.js.map +1 -1
- package/src/lib/model/structured-value.d.ts +51 -0
- package/src/lib/model/structured-value.js +135 -0
- package/src/lib/model/structured-value.js.map +1 -0
|
@@ -34,8 +34,10 @@ import { collection, doc, getCountFromServer, getDocs, limit, query } from "fire
|
|
|
34
34
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
35
35
|
import { useFirestore, useFirestoreCollection, useHostResourceApi, useUser, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
36
36
|
import { writeSiteWideChange } from "@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change";
|
|
37
|
-
import {
|
|
37
|
+
import { ParameterChoicesEditor } from "./parameter-choices-editor.component.js";
|
|
38
38
|
import WhereUsedDialog from "./where-used-dialog.component.js";
|
|
39
|
+
import { FormulaBuilderButton } from "./formula-builder.component.js";
|
|
40
|
+
import { formulaNames } from "../model/formula-names.js";
|
|
39
41
|
import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
|
|
40
42
|
import { HOST_LOGIC_ZONE, LOGIC_FUNCTION_EDITOR_ZONE } from "./logic-zones.js";
|
|
41
43
|
import { fetchWhereUsed, summarizeDependents } from "@aglyn/aglyn/app-utils/where-used";
|
|
@@ -106,74 +108,118 @@ function emptyDraft() {
|
|
|
106
108
|
/**
|
|
107
109
|
* What a VISITOR meets for one parameter (AGL-3202): the label above the
|
|
108
110
|
* input, what it starts with, and — for a question with fixed answers — the
|
|
109
|
-
* choices. Empty fields are dropped from the
|
|
110
|
-
* as empty strings, so a function nobody has
|
|
111
|
-
* exactly the shape it always did.
|
|
111
|
+
* choices, one row each (AGL-3768). Empty fields are dropped from the
|
|
112
|
+
* definition rather than stored as empty strings, so a function nobody has
|
|
113
|
+
* dressed for visitors saves exactly the shape it always did.
|
|
112
114
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* value, the moment they were typed.
|
|
115
|
+
* "Starts as" is picked, not typed, wherever there is a list to pick from:
|
|
116
|
+
* the choices when there are any, on or off for a true/false.
|
|
116
117
|
*/ function ParameterAudienceFields(props) {
|
|
117
|
-
var _parameter_label, _parameter_defaultValue;
|
|
118
|
+
var _parameter_options, _parameter_label, _parameter_defaultValue;
|
|
118
119
|
const { parameter, onChange } = props;
|
|
119
|
-
const [choices, setChoices] = useState(()=>formatParameterOptions(parameter.options));
|
|
120
120
|
const assign = (field, value)=>{
|
|
121
121
|
const next = _extends({}, parameter);
|
|
122
122
|
if (value) next[field] = value;
|
|
123
123
|
else delete next[field];
|
|
124
124
|
onChange(next);
|
|
125
125
|
};
|
|
126
|
+
const options = (_parameter_options = parameter.options) != null ? _parameter_options : [];
|
|
127
|
+
const startsAsChoices = parameter.type === 'boolean' ? [
|
|
128
|
+
{
|
|
129
|
+
value: 'true',
|
|
130
|
+
label: 'On'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
value: 'false',
|
|
134
|
+
label: 'Off'
|
|
135
|
+
}
|
|
136
|
+
] : options.length ? options.map((option)=>{
|
|
137
|
+
var _option_label;
|
|
138
|
+
return {
|
|
139
|
+
value: option.value,
|
|
140
|
+
label: (_option_label = option.label) != null ? _option_label : option.value
|
|
141
|
+
};
|
|
142
|
+
}) : null;
|
|
126
143
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
127
|
-
direction: "row",
|
|
128
144
|
spacing: 1,
|
|
129
|
-
sx: {
|
|
130
|
-
alignItems: 'center'
|
|
131
|
-
},
|
|
132
145
|
children: [
|
|
133
|
-
/*#__PURE__*/
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
147
|
+
direction: "row",
|
|
148
|
+
spacing: 1,
|
|
136
149
|
sx: {
|
|
137
|
-
|
|
138
|
-
flexShrink: 0
|
|
150
|
+
alignItems: 'center'
|
|
139
151
|
},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
152
|
+
children: [
|
|
153
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
154
|
+
variant: "body2",
|
|
155
|
+
color: "text.secondary",
|
|
156
|
+
sx: {
|
|
157
|
+
width: 110,
|
|
158
|
+
flexShrink: 0
|
|
159
|
+
},
|
|
160
|
+
noWrap: true,
|
|
161
|
+
children: parameter.name || '—'
|
|
162
|
+
}),
|
|
163
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
164
|
+
label: "Label",
|
|
165
|
+
placeholder: parameter.name,
|
|
166
|
+
value: (_parameter_label = parameter.label) != null ? _parameter_label : '',
|
|
167
|
+
onChange: (event)=>assign('label', event.target.value.slice(0, 80)),
|
|
168
|
+
size: "small",
|
|
169
|
+
sx: {
|
|
170
|
+
flex: 2
|
|
171
|
+
}
|
|
172
|
+
}),
|
|
173
|
+
startsAsChoices ? /*#__PURE__*/ _jsxs(TextField, {
|
|
174
|
+
select: true,
|
|
175
|
+
label: "Starts as",
|
|
176
|
+
value: startsAsChoices.some((choice)=>choice.value === parameter.defaultValue) ? parameter.defaultValue : '',
|
|
177
|
+
onChange: (event)=>assign('defaultValue', event.target.value),
|
|
178
|
+
size: "small",
|
|
179
|
+
sx: {
|
|
180
|
+
flex: 1
|
|
181
|
+
},
|
|
182
|
+
children: [
|
|
183
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
184
|
+
value: "",
|
|
185
|
+
children: /*#__PURE__*/ _jsx("em", {
|
|
186
|
+
children: 'Nothing chosen'
|
|
187
|
+
})
|
|
188
|
+
}),
|
|
189
|
+
startsAsChoices.map((choice)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
190
|
+
value: choice.value,
|
|
191
|
+
children: choice.label
|
|
192
|
+
}, choice.value))
|
|
193
|
+
]
|
|
194
|
+
}) : /*#__PURE__*/ _jsx(TextField, {
|
|
195
|
+
label: "Starts as",
|
|
196
|
+
value: (_parameter_defaultValue = parameter.defaultValue) != null ? _parameter_defaultValue : '',
|
|
197
|
+
onChange: (event)=>assign('defaultValue', parameter.type === 'number' ? event.target.value.replace(/[^0-9.-]/g, '') : event.target.value.slice(0, 120)),
|
|
198
|
+
slotProps: parameter.type === 'number' ? {
|
|
199
|
+
htmlInput: {
|
|
200
|
+
inputMode: 'decimal'
|
|
201
|
+
}
|
|
202
|
+
} : undefined,
|
|
203
|
+
size: "small",
|
|
204
|
+
sx: {
|
|
205
|
+
flex: 1
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
]
|
|
161
209
|
}),
|
|
162
|
-
/*#__PURE__*/ _jsx(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
onChange: (
|
|
167
|
-
setChoices(event.target.value);
|
|
168
|
-
const options = parseParameterOptions(event.target.value);
|
|
210
|
+
parameter.type === 'boolean' ? null : /*#__PURE__*/ _jsx(ParameterChoicesEditor, {
|
|
211
|
+
parameterName: parameter.name,
|
|
212
|
+
type: parameter.type,
|
|
213
|
+
options: parameter.options,
|
|
214
|
+
onChange: (nextOptions)=>{
|
|
169
215
|
const next = _extends({}, parameter);
|
|
170
|
-
if (
|
|
216
|
+
if (nextOptions.length) next.options = nextOptions;
|
|
171
217
|
else delete next.options;
|
|
218
|
+
// A start that is no longer a choice would preselect nothing.
|
|
219
|
+
if (nextOptions.length && next.defaultValue !== undefined && !nextOptions.some((option)=>option.value === next.defaultValue)) {
|
|
220
|
+
delete next.defaultValue;
|
|
221
|
+
}
|
|
172
222
|
onChange(next);
|
|
173
|
-
},
|
|
174
|
-
size: "small",
|
|
175
|
-
sx: {
|
|
176
|
-
flex: 3
|
|
177
223
|
}
|
|
178
224
|
})
|
|
179
225
|
]
|
|
@@ -194,7 +240,7 @@ function emptyDraft() {
|
|
|
194
240
|
* the shared evaluator. Definitions persist to `hosts/{hostId}/functions`;
|
|
195
241
|
* prop/event wiring lands with AGL-93.
|
|
196
242
|
*/ export function HostFunctionsCard(props) {
|
|
197
|
-
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10;
|
|
243
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12;
|
|
198
244
|
var _functions_find;
|
|
199
245
|
const { hostId } = props;
|
|
200
246
|
const firestore = useFirestore();
|
|
@@ -340,6 +386,35 @@ function emptyDraft() {
|
|
|
340
386
|
...((_ref1 = draft == null ? void 0 : draft.parameters) != null ? _ref1 : []).map((parameter)=>parameter.name),
|
|
341
387
|
...((_ref2 = draft == null ? void 0 : draft.variables) != null ? _ref2 : []).map((variable)=>variable.name)
|
|
342
388
|
].filter((name)=>VARIABLE_NAME_PATTERN.test(name));
|
|
389
|
+
/**
|
|
390
|
+
* The site's variables, for the formula builder's value picker
|
|
391
|
+
* (AGL-3757). Read the first time a builder opens, like the test run's
|
|
392
|
+
* read below, so an author who never builds a formula costs nothing.
|
|
393
|
+
*/ const [siteVariables, setSiteVariables] = useState(null);
|
|
394
|
+
const loadSiteVariables = useCallback(()=>{
|
|
395
|
+
if (siteVariables) return;
|
|
396
|
+
setSiteVariables([]);
|
|
397
|
+
getDocs(query(collection(firestore, 'hosts', hostId, 'variables'), limit(100))).then((snapshot)=>{
|
|
398
|
+
const rows = [];
|
|
399
|
+
snapshot.forEach((row)=>{
|
|
400
|
+
const data = row.data();
|
|
401
|
+
if ((data == null ? void 0 : data.name) && !data.deletedAt) rows.push(data);
|
|
402
|
+
});
|
|
403
|
+
setSiteVariables(rows);
|
|
404
|
+
}).catch((error)=>{
|
|
405
|
+
// The builder still offers the function's own names.
|
|
406
|
+
console.error(error);
|
|
407
|
+
});
|
|
408
|
+
}, [
|
|
409
|
+
siteVariables,
|
|
410
|
+
firestore,
|
|
411
|
+
hostId
|
|
412
|
+
]);
|
|
413
|
+
const formulaNameOptions = formulaNames({
|
|
414
|
+
parameters: (_ref3 = draft == null ? void 0 : draft.parameters) != null ? _ref3 : [],
|
|
415
|
+
locals: (_ref4 = draft == null ? void 0 : draft.variables) != null ? _ref4 : [],
|
|
416
|
+
siteVariables: siteVariables != null ? siteVariables : []
|
|
417
|
+
});
|
|
343
418
|
// Case-insensitive uniqueness (AGL-185): function names must stay
|
|
344
419
|
// unambiguous for legacy {{fn:name(...)}} token resolution.
|
|
345
420
|
const nameTaken = Boolean(draft && functions.some((definition)=>{
|
|
@@ -566,6 +641,12 @@ function emptyDraft() {
|
|
|
566
641
|
flex: 1
|
|
567
642
|
}
|
|
568
643
|
}),
|
|
644
|
+
/*#__PURE__*/ _jsx(FormulaBuilderButton, {
|
|
645
|
+
current: row.expression,
|
|
646
|
+
names: formulaNameOptions,
|
|
647
|
+
onOpen: loadSiteVariables,
|
|
648
|
+
onUse: (expression)=>setRow(section, operationIndex, rowIndex, 'expression', expression)
|
|
649
|
+
}),
|
|
569
650
|
/*#__PURE__*/ _jsx(IconButton, {
|
|
570
651
|
size: "small",
|
|
571
652
|
"aria-label": "remove operation row",
|
|
@@ -752,7 +833,7 @@ function emptyDraft() {
|
|
|
752
833
|
label: "Name",
|
|
753
834
|
helperText: nameTaken ? 'A function with this name already exists' : 'Used to identify the function',
|
|
754
835
|
error: nameTaken,
|
|
755
|
-
value: (
|
|
836
|
+
value: (_ref5 = draft == null ? void 0 : draft.name) != null ? _ref5 : '',
|
|
756
837
|
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
757
838
|
name: event.target.value
|
|
758
839
|
})),
|
|
@@ -765,7 +846,7 @@ function emptyDraft() {
|
|
|
765
846
|
/*#__PURE__*/ _jsx(SectionLabel, {
|
|
766
847
|
children: 'Parameters'
|
|
767
848
|
}),
|
|
768
|
-
((
|
|
849
|
+
((_ref6 = draft == null ? void 0 : draft.parameters) != null ? _ref6 : []).map((parameter, index)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
769
850
|
direction: "row",
|
|
770
851
|
spacing: 1,
|
|
771
852
|
sx: {
|
|
@@ -854,7 +935,7 @@ function emptyDraft() {
|
|
|
854
935
|
})),
|
|
855
936
|
children: 'Add parameter'
|
|
856
937
|
}),
|
|
857
|
-
((
|
|
938
|
+
((_ref7 = draft == null ? void 0 : draft.parameters) != null ? _ref7 : []).length ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
858
939
|
children: [
|
|
859
940
|
/*#__PURE__*/ _jsx(SectionLabel, {
|
|
860
941
|
children: 'What visitors see'
|
|
@@ -862,9 +943,9 @@ function emptyDraft() {
|
|
|
862
943
|
/*#__PURE__*/ _jsx(Typography, {
|
|
863
944
|
variant: "caption",
|
|
864
945
|
color: "text.secondary",
|
|
865
|
-
children: 'Used by a Function Widget on a page. A parameter with ' + 'choices is asked as a list
|
|
946
|
+
children: 'Used by a Function Widget on a page. A parameter with ' + 'choices is asked as a list — Add choice gives it one — a ' + 'number as a number box and a true/false as a switch.'
|
|
866
947
|
}),
|
|
867
|
-
((
|
|
948
|
+
((_ref8 = draft == null ? void 0 : draft.parameters) != null ? _ref8 : []).map((parameter, index)=>/*#__PURE__*/ _jsx(ParameterAudienceFields, {
|
|
868
949
|
parameter: parameter,
|
|
869
950
|
onChange: (next)=>patch((previous)=>_extends({}, previous, {
|
|
870
951
|
parameters: previous.parameters.map((p, index2)=>index2 === index ? next : p)
|
|
@@ -875,7 +956,7 @@ function emptyDraft() {
|
|
|
875
956
|
/*#__PURE__*/ _jsx(SectionLabel, {
|
|
876
957
|
children: 'Variables'
|
|
877
958
|
}),
|
|
878
|
-
((
|
|
959
|
+
((_ref9 = draft == null ? void 0 : draft.variables) != null ? _ref9 : []).map((variable, index)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
879
960
|
direction: "row",
|
|
880
961
|
spacing: 1,
|
|
881
962
|
sx: {
|
|
@@ -948,9 +1029,9 @@ function emptyDraft() {
|
|
|
948
1029
|
/*#__PURE__*/ _jsx(Typography, {
|
|
949
1030
|
variant: "caption",
|
|
950
1031
|
color: "text.secondary",
|
|
951
|
-
children: '
|
|
1032
|
+
children: 'Press ƒx beside a box to build its formula by picking values — ' + 'add, smallest of, format as money and more. A box also takes ' + 'a formula written out: + − × ÷ and parentheses, any site ' + 'variable by its name (a dictionary’s members as ' + 'name.member), and ' + `${FUNCTION_BUILTIN_NAMES.map((name)=>`${name}()`).join(', ')}.`
|
|
952
1033
|
}),
|
|
953
|
-
((
|
|
1034
|
+
((_ref10 = draft == null ? void 0 : draft.operations) != null ? _ref10 : []).map((operation, index)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
954
1035
|
spacing: 1,
|
|
955
1036
|
sx: {
|
|
956
1037
|
border: 1,
|
|
@@ -986,6 +1067,18 @@ function emptyDraft() {
|
|
|
986
1067
|
flex: 1
|
|
987
1068
|
}
|
|
988
1069
|
}),
|
|
1070
|
+
/*#__PURE__*/ _jsx(FormulaBuilderButton, {
|
|
1071
|
+
current: operation.if.left,
|
|
1072
|
+
names: formulaNameOptions,
|
|
1073
|
+
onOpen: loadSiteVariables,
|
|
1074
|
+
onUse: (expression)=>patch((previous)=>_extends({}, previous, {
|
|
1075
|
+
operations: previous.operations.map((op, index2)=>index2 === index ? _extends({}, op, {
|
|
1076
|
+
if: _extends({}, op.if, {
|
|
1077
|
+
left: expression
|
|
1078
|
+
})
|
|
1079
|
+
}) : op)
|
|
1080
|
+
}))
|
|
1081
|
+
}),
|
|
989
1082
|
/*#__PURE__*/ _jsx(TextField, {
|
|
990
1083
|
value: operation.if.comparator,
|
|
991
1084
|
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
@@ -1019,6 +1112,18 @@ function emptyDraft() {
|
|
|
1019
1112
|
sx: {
|
|
1020
1113
|
flex: 1
|
|
1021
1114
|
}
|
|
1115
|
+
}),
|
|
1116
|
+
/*#__PURE__*/ _jsx(FormulaBuilderButton, {
|
|
1117
|
+
current: operation.if.right,
|
|
1118
|
+
names: formulaNameOptions,
|
|
1119
|
+
onOpen: loadSiteVariables,
|
|
1120
|
+
onUse: (expression)=>patch((previous)=>_extends({}, previous, {
|
|
1121
|
+
operations: previous.operations.map((op, index2)=>index2 === index ? _extends({}, op, {
|
|
1122
|
+
if: _extends({}, op.if, {
|
|
1123
|
+
right: expression
|
|
1124
|
+
})
|
|
1125
|
+
}) : op)
|
|
1126
|
+
}))
|
|
1022
1127
|
})
|
|
1023
1128
|
]
|
|
1024
1129
|
}),
|
|
@@ -1064,7 +1169,7 @@ function emptyDraft() {
|
|
|
1064
1169
|
}),
|
|
1065
1170
|
/*#__PURE__*/ _jsx(TextField, {
|
|
1066
1171
|
label: "Return value",
|
|
1067
|
-
value: (
|
|
1172
|
+
value: (_ref11 = draft == null ? void 0 : draft.returnValue) != null ? _ref11 : '',
|
|
1068
1173
|
onChange: (event)=>patch((previous)=>_extends({}, previous, {
|
|
1069
1174
|
returnValue: event.target.value
|
|
1070
1175
|
})),
|
|
@@ -1086,7 +1191,7 @@ function emptyDraft() {
|
|
|
1086
1191
|
flexWrap: 'wrap'
|
|
1087
1192
|
},
|
|
1088
1193
|
children: [
|
|
1089
|
-
((
|
|
1194
|
+
((_ref12 = draft == null ? void 0 : draft.parameters) != null ? _ref12 : []).map((parameter)=>{
|
|
1090
1195
|
var _testArgs_parameter_name;
|
|
1091
1196
|
return /*#__PURE__*/ _jsx(TextField, {
|
|
1092
1197
|
label: parameter.name,
|