@ea-lab/reactive-json 0.0.0

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.
Files changed (66) hide show
  1. package/README.md +83 -0
  2. package/dist/reactive-json.css +5 -0
  3. package/dist/reactive-json.js +56303 -0
  4. package/dist/reactive-json.umd.cjs +382 -0
  5. package/lib/component/action/HashChangeListener.jsx +66 -0
  6. package/lib/component/action/Hide.jsx +14 -0
  7. package/lib/component/action/MessageListener.jsx +62 -0
  8. package/lib/component/action/Popover.jsx +53 -0
  9. package/lib/component/action/ReactOnEvent.jsx +118 -0
  10. package/lib/component/action/Redirect.jsx +26 -0
  11. package/lib/component/action/Tooltip.jsx +27 -0
  12. package/lib/component/action/VisuallyHide.jsx +15 -0
  13. package/lib/component/element/chart/BarChart.jsx +40 -0
  14. package/lib/component/element/chart/DoughnutChart.jsx +32 -0
  15. package/lib/component/element/chart/LineChart.jsx +40 -0
  16. package/lib/component/element/chart/PolarAreaChart.jsx +32 -0
  17. package/lib/component/element/form/CheckBoxField.jsx +215 -0
  18. package/lib/component/element/form/DateField.jsx +42 -0
  19. package/lib/component/element/form/NumberField.jsx +29 -0
  20. package/lib/component/element/form/SelectField.jsx +130 -0
  21. package/lib/component/element/form/TextAreaField.jsx +48 -0
  22. package/lib/component/element/form/TextField.jsx +65 -0
  23. package/lib/component/element/form/formElementsCommon.jsx +54 -0
  24. package/lib/component/element/html/AccordionItem.jsx +42 -0
  25. package/lib/component/element/html/FolderSortableTree.jsx +307 -0
  26. package/lib/component/element/html/FormatNumeral.jsx +118 -0
  27. package/lib/component/element/html/Html.jsx +107 -0
  28. package/lib/component/element/html/LabelFromValue.jsx +89 -0
  29. package/lib/component/element/html/Modal.jsx +77 -0
  30. package/lib/component/element/html/ModalForm.jsx +30 -0
  31. package/lib/component/element/html/Paragraph.jsx +10 -0
  32. package/lib/component/element/html/PreformattedMarkup.jsx +54 -0
  33. package/lib/component/element/html/SortableTreeItemCollapseButton.jsx +20 -0
  34. package/lib/component/element/html/Tabs.jsx +55 -0
  35. package/lib/component/element/special/BootstrapElement.jsx +32 -0
  36. package/lib/component/element/special/Count.jsx +46 -0
  37. package/lib/component/element/special/DataFilter.jsx +156 -0
  38. package/lib/component/element/special/DelayedActions.jsx +119 -0
  39. package/lib/component/element/special/PageControls.jsx +19 -0
  40. package/lib/component/element/special/Phantom.jsx +25 -0
  41. package/lib/component/element/special/Switch.jsx +131 -0
  42. package/lib/component/hook/usePagination.jsx +184 -0
  43. package/lib/component/reaction/addData.jsx +23 -0
  44. package/lib/component/reaction/fetchData.jsx +83 -0
  45. package/lib/component/reaction/moveData.jsx +52 -0
  46. package/lib/component/reaction/postMessage.jsx +43 -0
  47. package/lib/component/reaction/redirectNow.jsx +17 -0
  48. package/lib/component/reaction/removeData.jsx +48 -0
  49. package/lib/component/reaction/setClipboardData.jsx +20 -0
  50. package/lib/component/reaction/setData.jsx +23 -0
  51. package/lib/component/reaction/submitData.jsx +136 -0
  52. package/lib/component/reaction/triggerEvent.jsx +62 -0
  53. package/lib/component/utility/formatString.jsx +59 -0
  54. package/lib/engine/Actions.jsx +392 -0
  55. package/lib/engine/EventDispatcherContext.jsx +16 -0
  56. package/lib/engine/EventDispatcherProvider.jsx +80 -0
  57. package/lib/engine/GlobalDataContext.jsx +13 -0
  58. package/lib/engine/GlobalDataContextProvider.jsx +33 -0
  59. package/lib/engine/PaginationContext.jsx +10 -0
  60. package/lib/engine/PaginationProvider.jsx +61 -0
  61. package/lib/engine/ReactiveJsonRoot.jsx +315 -0
  62. package/lib/engine/TemplateContext.jsx +13 -0
  63. package/lib/engine/TemplateSystem.jsx +302 -0
  64. package/lib/engine/View.jsx +240 -0
  65. package/lib/main.jsx +41 -0
  66. package/package.json +72 -0
@@ -0,0 +1,307 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
3
+ import TemplateContext from "../../../engine/TemplateContext";
4
+ import {evaluateTemplateValue} from "../../../engine/TemplateSystem";
5
+ import View from "../../../engine/View";
6
+ import {propsDataLocationToPathAndValue} from "../form/formElementsCommon";
7
+ // clsx is included in dnd-kit-sortable-tree.
8
+ import clsx from "clsx";
9
+ import {FolderTreeItemWrapper} from "dnd-kit-sortable-tree";
10
+ import {SortableTree} from "dnd-kit-sortable-tree";
11
+ import {cloneDeep} from "lodash";
12
+ import React, {forwardRef, useContext} from "react";
13
+
14
+ const FolderSortableTree = ({props, path, datafield}) => {
15
+ const globalDataContext = useContext(GlobalDataContext);
16
+ const templateContext = useContext(TemplateContext);
17
+
18
+ let {
19
+ formData: treeData,
20
+ formDataPath: treeDataPath,
21
+ } = propsDataLocationToPathAndValue({
22
+ currentPath: path,
23
+ datafield: datafield,
24
+ dataLocation: props.dataLocation,
25
+ defaultValue: props.defaultFieldValue,
26
+ globalDataContext,
27
+ templateContext,
28
+ });
29
+
30
+ if (treeData === undefined) {
31
+ // Empty tree data.
32
+ return null;
33
+ }
34
+
35
+ /**
36
+ * Template (the usual structure given to View props) that will be used for each tree item.
37
+ *
38
+ * @type {*|null}
39
+ */
40
+ const itemTemplate = props.itemTemplate ?? null;
41
+
42
+ /**
43
+ * This value is needed when the user wants to limit the data rendered in this tree.
44
+ *
45
+ * @type {string|undefined|*}
46
+ */
47
+ const treeRootPath = evaluateTemplateValue({
48
+ valueToEvaluate: props.treeRootPath ?? undefined,
49
+ globalDataContext,
50
+ templateContext
51
+ });
52
+
53
+ /**
54
+ * Sets the maximum depth. Optional.
55
+ *
56
+ * "maxDepth" is not a native SortableTree option;
57
+ * that is why it is not under the sortableTreeOptions key.
58
+ * Please note that it will not fix the data until a drag and drop interaction.
59
+ *
60
+ * @type {number}
61
+ */
62
+ const maxDepth = props.maxDepth;
63
+
64
+ /**
65
+ * Tells if the given maxDepth is expressed in absolute or relative base.
66
+ *
67
+ * If false, the maxDepth will be used to limit the depth relative to the tree root item
68
+ * determined by treeRootItem. If true, the depth will be limited starting from the root
69
+ * item given by treeData.
70
+ *
71
+ * @type {boolean}
72
+ */
73
+ const maxDepthIsAbsolute = (typeof treeRootPath !== "string") ? true : (props.maxDepthIsAbsolute ?? true);
74
+
75
+ /**
76
+ * Tells if we want to keep the base item when "treeRootPath" is defined.
77
+ *
78
+ * Set this to true to keep in the tree the item
79
+ * that "treeRootPath" (if set) will use as the tree root.
80
+ * When false, the base tree items will be the
81
+ * children of this base item instead.
82
+ *
83
+ * @type {boolean}
84
+ */
85
+ const keepBaseItem = props.keepBaseItem ?? false;
86
+
87
+ /*
88
+ * Used by the max depth limiter and the trees filtered by treeRootPath.
89
+ */
90
+ let baseDepth = 0;
91
+
92
+ /*
93
+ * Useful when the treeRootPath option is active.
94
+ * This is the item id that will be set back during the "onItemsChanged" event
95
+ * on the base elements.
96
+ */
97
+ let baseParentId = null;
98
+
99
+ let baseItemIndex = undefined;
100
+
101
+ if (typeof treeRootPath === "string" && treeRootPath.length > 0) {
102
+ // A tree root path has been specified.
103
+ const pathSplitted = treeRootPath.split(".");
104
+
105
+ let baseParentId_previous = baseParentId;
106
+ let treeData_previous = treeData;
107
+ let treeDataPath_previous = treeDataPath;
108
+
109
+ while (pathSplitted.length > 0) {
110
+ const index = Number.parseInt(pathSplitted.shift());
111
+
112
+ if (Number.isNaN(index)) {
113
+ // This value is invalid. Do not even try to load the tree.
114
+ return null;
115
+ }
116
+
117
+ // The "previous" values are used for the "keepBaseItem" option.
118
+ baseParentId_previous = treeData?.["id"] ?? null;
119
+ treeData_previous = treeData;
120
+ treeDataPath_previous = treeDataPath;
121
+ baseItemIndex = index;
122
+
123
+ baseParentId = treeData[index]?.["id"];
124
+ treeData = treeData[index]?.["children"] ?? undefined;
125
+ treeDataPath = treeDataPath + "." + index + ".children";
126
+
127
+ if (treeData === undefined) {
128
+ // No tree to render.
129
+ return null;
130
+ }
131
+
132
+ ++baseDepth;
133
+ }
134
+
135
+ if (keepBaseItem) {
136
+ // Use the "previous" values.
137
+ baseParentId = baseParentId_previous;
138
+ treeData = treeData_previous;
139
+ treeDataPath = treeDataPath_previous;
140
+
141
+ // Remove all items but the one identified by "indexToKeep".
142
+ // This item will be indexed at 0 for SortableTree.
143
+ treeData = [treeData[baseItemIndex]];
144
+ }
145
+ }
146
+
147
+ const onItemsChanged = (e) => {
148
+ let finalData = e;
149
+ let finalDataPath = treeDataPath;
150
+
151
+ if (baseDepth > 0) {
152
+ // Fix the depths of all the items by adding baseDepth.
153
+ // Also fix the parentId if we have the treeRootPath option active.
154
+ finalData = cloneDeep(finalData);
155
+
156
+ const recursiveFixer = (it, currentDepth = 0) => {
157
+ if (currentDepth === 0) {
158
+ it.parentId = baseParentId;
159
+ }
160
+
161
+ it.depth += baseDepth;
162
+
163
+ it.children?.forEach((child) => {
164
+ recursiveFixer(child, currentDepth + 1);
165
+ });
166
+ };
167
+
168
+ finalData.forEach((child) => {
169
+ recursiveFixer(child, 0);
170
+ });
171
+ }
172
+
173
+ if ((treeRootPath !== undefined) && keepBaseItem) {
174
+ // We are in a partial tree configuration,
175
+ // and this tree has the base item kept in the hierarchy.
176
+ // Fix the data and paths.
177
+ // We take the first and only item.
178
+ finalData = e[0];
179
+ finalDataPath = treeDataPath + "." + baseItemIndex;
180
+ }
181
+
182
+ globalDataContext.updateData(finalData, finalDataPath);
183
+ }
184
+
185
+ const GenericTreeItemComponent = forwardRef((props, ref) => {
186
+ const finalCurrentData = props.item.value ?? {};
187
+
188
+ // Rebuild the data path by inspecting the parents.
189
+ const parentsIndices = [];
190
+
191
+ let itemToInspect = props.item;
192
+ parentsIndices.push(itemToInspect.index);
193
+
194
+ while (itemToInspect.parent) {
195
+ parentsIndices.push("children");
196
+ itemToInspect = itemToInspect.parent;
197
+ parentsIndices.push(itemToInspect.index);
198
+ }
199
+
200
+ // Reverse the order.
201
+ parentsIndices.reverse();
202
+
203
+ // This is the path leading to the "value" key excluded.
204
+ const finalValuePath = treeDataPath + "." + parentsIndices.join(".");
205
+
206
+ // This is the full path, "value" included.
207
+ const finalDataPath = finalValuePath + ".value";
208
+
209
+ finalCurrentData._treeItemDepth = props.item.depth;
210
+ finalCurrentData._treeItemIndex = props.item.index;
211
+ finalCurrentData._treeItemIndex1 = props.item.index + 1;
212
+
213
+ // Put the collapse button into the template data.
214
+ // This is the stylable button which serves as a collapse switch.
215
+ // The implementation is taken directly from the FolderTreeItemWrapper component.
216
+ // We can then include the collapse button in the item
217
+ // thanks to the SortableTreeItemCollapseButton component.
218
+ const sortableTreeData = {};
219
+
220
+ sortableTreeData._treeAddCollapseButton = () => (
221
+ !!props.onCollapse && !!props.childCount &&
222
+ <button
223
+ onClick={(e) => {
224
+ e.preventDefault();
225
+ props.onCollapse?.();
226
+ }}
227
+ className={clsx(
228
+ 'dnd-sortable-tree_folder_tree-item-collapse_button',
229
+ props.collapsed &&
230
+ 'dnd-sortable-tree_folder_tree-item-collapse_button-collapsed'
231
+ )}/>
232
+ );
233
+
234
+ if (maxDepth) {
235
+ // A maximum depth has been defined for this tree.
236
+ if (maxDepthIsAbsolute) {
237
+ props.item.canHaveChildren = (baseDepth + props.item.depth) < maxDepth;
238
+ } else {
239
+ props.item.canHaveChildren = (props.item.depth) < maxDepth;
240
+ }
241
+ }
242
+
243
+ return (
244
+ <FolderTreeItemWrapper
245
+ {...props}
246
+ data-htmlbuilder-tree-item-children-count={props.childCount || "0"}
247
+ data-htmlbuilder-tree-item-collapsed={props.collapsed}
248
+ data-htmlbuilder-tree-item-depth={props.item.depth}
249
+ data-htmlbuilder-tree-item-depth-list={getDepthAsList(props.item.depth)}
250
+ data-htmlbuilder-tree-item-index={props.item.index}
251
+ data-htmlbuilder-tree-item-index1={props.item.index + 1}
252
+ data-htmlbuilder-tree-item-is-last={props.isLast}
253
+ ref={ref}>
254
+ <TemplateContext.Provider value={{
255
+ templateData: finalCurrentData,
256
+ templatePath: finalDataPath,
257
+ sortableTreeData: sortableTreeData
258
+ }}>
259
+ <View
260
+ props={itemTemplate}
261
+ currentData={finalCurrentData}
262
+ />
263
+ </TemplateContext.Provider>
264
+ </FolderTreeItemWrapper>
265
+ );
266
+ });
267
+
268
+ // Deep copy because SortableTree seems to directly edit the data.
269
+ const clone = cloneDeep(treeData);
270
+
271
+ // Additional properties for SortableTree.
272
+ const sortableTreeOptions = props.sortableTreeOptions ?? {};
273
+
274
+ return (
275
+ <ActionDependant {...props}>
276
+ <SortableTree
277
+ {...sortableTreeOptions}
278
+ items={clone}
279
+ onItemsChanged={onItemsChanged}
280
+ TreeItemComponent={GenericTreeItemComponent}/>
281
+ </ActionDependant>
282
+ );
283
+ };
284
+
285
+ /**
286
+ * Builds a list of depths such as 4 => "0 1 2 3 4".
287
+ *
288
+ * This can be used as an attribute value, for the CSS word selector "~=".
289
+ * E.g.: [data-htmlbuilder-tree-item-depth-list~=3].
290
+ *
291
+ * @param {int} itemDepth The item depth.
292
+ *
293
+ * @returns {string} The string of depths.
294
+ */
295
+ function getDepthAsList(itemDepth) {
296
+ const depthAsList = [];
297
+ let remainingDepth = itemDepth ?? 0;
298
+
299
+ while (remainingDepth >= 0) {
300
+ depthAsList.push(remainingDepth);
301
+ --remainingDepth;
302
+ }
303
+
304
+ return depthAsList.reverse().join(" ");
305
+ }
306
+
307
+ export default FolderSortableTree;
@@ -0,0 +1,118 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
3
+ import TemplateContext from "../../../engine/TemplateContext";
4
+ import {evaluateTemplateValue} from "../../../engine/TemplateSystem";
5
+ import {useContext} from "react";
6
+
7
+ /**
8
+ * Transforms a number into a numeral of a specific language: roman, upper latin, lower latin, ... and custom.
9
+ */
10
+ const FormatNumeral = ({props}) => {
11
+ const globalDataContext = useContext(GlobalDataContext);
12
+ const templateContext = useContext(TemplateContext);
13
+
14
+ let formatted = false;
15
+
16
+ if (props.content !== undefined) {
17
+ const evaluated = evaluateTemplateValue({valueToEvaluate: props.content, globalDataContext, templateContext});
18
+
19
+ switch (props.format) {
20
+ case "roman-upper":
21
+ formatted = convertArabicToRoman(evaluated);
22
+ break;
23
+
24
+ case "roman-lower":
25
+ formatted = convertArabicToRoman(evaluated, true);
26
+ break;
27
+
28
+ case "latin-upper":
29
+ formatted = convertArabicToLatinLetters(evaluated);
30
+ break;
31
+
32
+ case "latin-lower":
33
+ formatted = convertArabicToLatinLetters(evaluated, true);
34
+ break;
35
+
36
+ default:
37
+ break;
38
+ }
39
+ }
40
+
41
+ return (
42
+ <ActionDependant {...props}>
43
+ <>
44
+ {(formatted !== false) && formatted}
45
+ </>
46
+ </ActionDependant>
47
+ );
48
+ };
49
+
50
+ /**
51
+ * Converts an arabic number to latin letters.
52
+ *
53
+ * Implementation inspired by: https://stackoverflow.com/a/11090169.
54
+ *
55
+ * @param number The number to convert.
56
+ * @param asLowerCase Set to true for lowercase.
57
+ *
58
+ * @returns {*|string|number|false} Number in roman counting, or false if invalid.
59
+ */
60
+ function convertArabicToLatinLetters(number, asLowerCase = false) {
61
+ if (number < 1) {
62
+ return false;
63
+ }
64
+
65
+ let mod = number % 26,
66
+ pow = number / 26 | 0,
67
+ out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
68
+
69
+ const letters = pow ? convertArabicToLatinLetters(pow) + out : out;
70
+
71
+ return asLowerCase ? letters.toLowerCase() : letters;
72
+ }
73
+
74
+ /**
75
+ * Converts an arabic number to roman conuting.
76
+ *
77
+ * Implementation inspired by: https://www.30secondsofcode.org/js/s/to-roman-numeral/.
78
+ *
79
+ * Added the lowercase option.
80
+ *
81
+ * @param number The number to convert.
82
+ * @param asLowercase Set to true for lowercase.
83
+ *
84
+ * @returns {number|string|false} Number in roman counting, or false if invalid.
85
+ */
86
+ function convertArabicToRoman(number, asLowercase = false) {
87
+ if (number < 1) {
88
+ return false;
89
+ }
90
+
91
+ const lookup = [
92
+ ['M', 1000],
93
+ ['CM', 900],
94
+ ['D', 500],
95
+ ['CD', 400],
96
+ ['C', 100],
97
+ ['XC', 90],
98
+ ['L', 50],
99
+ ['XL', 40],
100
+ ['X', 10],
101
+ ['IX', 9],
102
+ ['V', 5],
103
+ ['IV', 4],
104
+ ['I', 1],
105
+ ];
106
+
107
+ const uppercase = lookup.reduce(
108
+ (acc, [k, v]) => {
109
+ acc += k.repeat(Math.floor(number / v));
110
+ number = number % v;
111
+ return acc;
112
+ },
113
+ '');
114
+
115
+ return asLowercase ? uppercase.toLowerCase() : uppercase;
116
+ }
117
+
118
+ export default FormatNumeral;
@@ -0,0 +1,107 @@
1
+ import {evaluateAttributes} from "../../../engine/TemplateSystem";
2
+ import View from "../../../engine/View";
3
+ import ActionDependant from "../../../engine/Actions";
4
+ import {useContext} from "react";
5
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
6
+ import TemplateContext from "../../../engine/TemplateContext";
7
+
8
+ export const normalizeAttributesForReactJsx = (maybeAttributesObj) => {
9
+ if (typeof maybeAttributesObj !== "object" || Object.keys(maybeAttributesObj).length === 0) {
10
+ // Not a valid attributes object.
11
+ return {};
12
+ }
13
+
14
+ const mapping = {
15
+ "class": "className",
16
+ // TODO: checked to defaultChecked seems incorrect. Commented out for removal.
17
+ // "checked": "defaultChecked",
18
+ };
19
+
20
+ // Recreate a shallow copy with the normalized attribute keys.
21
+ const attributesObj = {};
22
+
23
+ for (const [attributeName, attributeValue] of Object.entries(maybeAttributesObj)) {
24
+ const finalAttributeName = mapping.hasOwnProperty(attributeName)
25
+ ? mapping[attributeName]
26
+ : attributeName;
27
+ attributesObj[finalAttributeName] = attributeValue;
28
+ }
29
+
30
+ return attributesObj;
31
+ };
32
+
33
+ const Html = ({props, currentData, datafield, path}) => {
34
+ const globalDataContext = useContext(GlobalDataContext);
35
+ const templateContext = useContext(TemplateContext);
36
+
37
+ const Tag = `${props.tag}`;
38
+ const extra = props.extra ?? {};
39
+
40
+ // We need to alter the incoming attributes.
41
+ // Normalize the attributes for JSX.
42
+ const attrs = normalizeAttributesForReactJsx(props.attributes);
43
+ const attrsFromCurrentData = normalizeAttributesForReactJsx(currentData.attributes);
44
+
45
+ // Infer the props (template) attributes with the data attributes.
46
+ for (const attrName of Object.keys(attrsFromCurrentData)) {
47
+ if (attrName.charAt(0) === '+') {
48
+ // Append to the props attribute value.
49
+ const finalAttributeName = attrName.substring(1);
50
+ attrs[finalAttributeName] = (attrs[finalAttributeName] ?? "").length > 0
51
+ // Append using a space.
52
+ ? " " + attrsFromCurrentData[attrName]
53
+ // Set directly.
54
+ : attrsFromCurrentData[attrName];
55
+ continue;
56
+ }
57
+
58
+ // Set the props attribute value directly.
59
+ attrs[attrName] = attrsFromCurrentData[attrName];
60
+ }
61
+
62
+ const evaluatedAttrs = evaluateAttributes({attrs, globalDataContext, templateContext});
63
+
64
+ // console.log({
65
+ // "cpn": "Html",
66
+ // "props": props,
67
+ // "currentData": currentData,
68
+ // "datafield": datafield,
69
+ // "path": path,
70
+ // "finalAttrs": attrs,
71
+ // });
72
+
73
+ const isVoidTag = (tag) => {
74
+ const voidTagList = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
75
+ return tag && (voidTagList.indexOf(tag) !== -1);
76
+ };
77
+
78
+ return <ActionDependant {...props}>
79
+ {isVoidTag(props.tag)
80
+ ? <>
81
+ <Tag {...evaluatedAttrs}/>
82
+ {Object.keys(extra).length ? (
83
+ <View
84
+ props={extra}
85
+ />
86
+ ) : ("")}
87
+ </>
88
+ : <>
89
+ <Tag {...evaluatedAttrs}>
90
+ {props.content &&
91
+ (<View
92
+ currentData={currentData.content ?? undefined}
93
+ datafield={"content"}
94
+ path={path + ".content"}
95
+ props={props.content}
96
+ />)}
97
+ </Tag>
98
+ {Object.keys(extra).length ? (
99
+ <View
100
+ props={extra}
101
+ />
102
+ ) : ("")}
103
+ </>}
104
+ </ActionDependant>;
105
+ }
106
+
107
+ export default Html;
@@ -0,0 +1,89 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
3
+ import TemplateContext from "../../../engine/TemplateContext";
4
+ import {evaluateTemplateValue} from "../../../engine/TemplateSystem";
5
+ import View from "../../../engine/View";
6
+ import {useContext} from "react";
7
+
8
+ /**
9
+ * Shows the label associated to a value.
10
+ *
11
+ * Uses an option-like structure as data source.
12
+ * Thus, it's compatible with SelectField, CheckBoxField...
13
+ *
14
+ * E.g.: [{"label": "Public name", "value": "option value"}].
15
+ *
16
+ * @param currentData
17
+ * @param datafield
18
+ * @param path
19
+ * @param props
20
+ * @returns {JSX.Element}
21
+ * @constructor
22
+ */
23
+ const LabelFromValue = ({currentData, datafield, path, props}) => {
24
+ const globalDataContext = useContext(GlobalDataContext);
25
+ const templateContext = useContext(TemplateContext);
26
+
27
+ const dynamicOptions = props.dynamicOptions ?? undefined;
28
+
29
+ let options;
30
+
31
+ if (dynamicOptions) {
32
+ // Build the options through the given data.
33
+ options = evaluateTemplateValue({valueToEvaluate: dynamicOptions, globalDataContext, templateContext}) ?? [];
34
+ } else {
35
+ options = props.options ?? [];
36
+ }
37
+
38
+ // This is the data that contains the current value of this component.
39
+ let formData;
40
+
41
+ // This is the field value when the data is not supplied on initialization.
42
+ const defaultFieldValue = props.defaultFieldValue ?? undefined;
43
+
44
+ const dataLocation = props.dataLocation ?? undefined;
45
+
46
+ if (dataLocation) {
47
+ // A custom data location has been specified.
48
+ formData = evaluateTemplateValue({
49
+ globalDataContext: globalDataContext,
50
+ templateContext: templateContext,
51
+ valueToEvaluate: dataLocation,
52
+ }) ?? defaultFieldValue;
53
+ } else {
54
+ // Use the template data.
55
+ if ((templateContext.templateData[datafield] ?? undefined) === undefined) {
56
+ // Initialize the data for this component.
57
+ templateContext.templateData = (typeof templateContext.templateData === "object") ? templateContext.templateData : {};
58
+ templateContext.templateData[datafield] = defaultFieldValue;
59
+ }
60
+
61
+ // The "form" data is located in the template context data,
62
+ // under the datafield key. (Dev note: this is maybe not the best way to handle this.)
63
+ formData = templateContext.templateData[datafield];
64
+ }
65
+
66
+ let finalValue = options.find((option) => option.value === formData);
67
+
68
+ if (!finalValue || !finalValue.label) {
69
+ if (!formData) {
70
+ // Nothing to show.
71
+ return null;
72
+ }
73
+
74
+ // Show the raw data.
75
+ finalValue = formData;
76
+ }
77
+
78
+ return (
79
+ <ActionDependant {...props}>
80
+ <View
81
+ currentData={currentData}
82
+ datafield={datafield}
83
+ path={path}
84
+ props={finalValue.label}/>
85
+ </ActionDependant>
86
+ );
87
+ };
88
+
89
+ export default LabelFromValue;
@@ -0,0 +1,77 @@
1
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
2
+ import TemplateContext from "../../../engine/TemplateContext";
3
+ import {dataLocationToPath, evaluateTemplateValue, useEvaluatedAttributes} from "../../../engine/TemplateSystem";
4
+ import ActionDependant from "../../../engine/Actions";
5
+ import View from "../../../engine/View";
6
+ import {useContext, useState} from "react";
7
+ import {default as BsModal} from 'react-bootstrap/Modal';
8
+
9
+ const Modal = ({currentData, path, props}) => {
10
+ const globalDataContext = useContext(GlobalDataContext);
11
+ const templateContext = useContext(TemplateContext);
12
+
13
+ // The modal will be opened by default when in state mode.
14
+ const [show, setShow] = useState(true);
15
+
16
+ const evaluatedAttrs = useEvaluatedAttributes(props.attributes);
17
+
18
+ const {showBoolPath} = props;
19
+
20
+ // State mode is when the given bool path is not a path.
21
+ const isInStateMode = typeof showBoolPath !== "string";
22
+
23
+ // This is the value which control the modal opening state.
24
+ evaluatedAttrs.show = isInStateMode
25
+ ? show
26
+ : evaluateTemplateValue({
27
+ valueToEvaluate: props?.showBoolPath ?? false, globalDataContext, templateContext
28
+ });
29
+
30
+ const handleClose = () => {
31
+ if (isInStateMode) {
32
+ setShow(false);
33
+ return;
34
+ }
35
+
36
+ // A bool path is given, this will be used to control the modal visibility.
37
+ const fullPath = dataLocationToPath({
38
+ dataLocation: showBoolPath,
39
+ currentPath: templateContext.templatePath,
40
+ globalDataContext,
41
+ templateContext,
42
+ });
43
+
44
+ globalDataContext.updateData(undefined, fullPath);
45
+ };
46
+
47
+ // Add the reactive-json class to identify this modal as managed by reactive-json.
48
+ const base = evaluatedAttrs.className ? evaluatedAttrs.className.split(" ") : [];
49
+ base.push("reactive-json");
50
+ evaluatedAttrs.className = base.join(" ");
51
+
52
+ return (
53
+ <ActionDependant {...props}>
54
+ <BsModal {...evaluatedAttrs} onHide={handleClose}>
55
+ {(props.headerTitle || props.closeButton) &&
56
+ <BsModal.Header closeButton={props.closeButton}>
57
+ <BsModal.Title>
58
+ <View
59
+ currentData={currentData?.headerTitle ?? undefined}
60
+ datafield={"headerTitle"}
61
+ path={(path ?? "") + ".headerTitle"}
62
+ props={props?.headerTitle}/>
63
+ </BsModal.Title>
64
+ </BsModal.Header>}
65
+ {props.body && <BsModal.Body>
66
+ <View
67
+ currentData={currentData?.body ?? undefined}
68
+ datafield={"body"}
69
+ path={(path ?? "") + ".body"}
70
+ props={props?.body}/>
71
+ </BsModal.Body>}
72
+ </BsModal>
73
+ </ActionDependant>
74
+ );
75
+ };
76
+
77
+ export default Modal;