@saltcorn/builder 0.9.3-beta.8 → 0.9.3-rc.1
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/dist/builder_bundle.js +10 -10
- package/package.json +1 -1
- package/src/components/Builder.js +82 -73
- package/src/components/Library.js +28 -10
- package/src/components/elements/Aggregation.js +2 -2
- package/src/components/elements/Field.js +22 -5
- package/src/components/elements/Image.js +229 -227
- package/src/components/elements/JoinField.js +25 -4
- package/src/components/elements/ToggleFilter.js +6 -8
- package/src/components/elements/View.js +42 -19
- package/src/components/elements/ViewLink.js +25 -11
- package/src/components/elements/utils.js +71 -5
- package/src/components/relations_context.js +3 -0
- package/tests/relations_finder.test.js +8 -0
|
@@ -8,6 +8,7 @@ import React, { Fragment, useEffect, useMemo } from "react";
|
|
|
8
8
|
import { useNode } from "@craftjs/core";
|
|
9
9
|
import optionsCtx from "../context";
|
|
10
10
|
import previewCtx from "../preview_context";
|
|
11
|
+
import relationsCtx from "../relations_context";
|
|
11
12
|
|
|
12
13
|
import {
|
|
13
14
|
fetchViewPreview,
|
|
@@ -15,7 +16,9 @@ import {
|
|
|
15
16
|
setAPropGen,
|
|
16
17
|
buildOptions,
|
|
17
18
|
HelpTopicLink,
|
|
19
|
+
initialRelation,
|
|
18
20
|
prepCacheAndFinder,
|
|
21
|
+
updateRelationsCache,
|
|
19
22
|
} from "./utils";
|
|
20
23
|
|
|
21
24
|
import { RelationBadges } from "./RelationBadges";
|
|
@@ -113,6 +116,7 @@ const ViewSettings = () => {
|
|
|
113
116
|
const fixed_state_fields =
|
|
114
117
|
options.fixed_state_fields && options.fixed_state_fields[view];
|
|
115
118
|
const { setPreviews } = React.useContext(previewCtx);
|
|
119
|
+
const { relationsCache, setRelationsCache } = React.useContext(relationsCtx);
|
|
116
120
|
|
|
117
121
|
const setAProp = setAPropGen(setProp);
|
|
118
122
|
let errorString = false;
|
|
@@ -131,18 +135,25 @@ const ViewSettings = () => {
|
|
|
131
135
|
else viewname = rest;
|
|
132
136
|
}
|
|
133
137
|
if (viewname.includes(".")) viewname = viewname.split(".")[0];
|
|
138
|
+
if (finder)
|
|
139
|
+
updateRelationsCache(
|
|
140
|
+
relationsCache,
|
|
141
|
+
setRelationsCache,
|
|
142
|
+
options,
|
|
143
|
+
finder,
|
|
144
|
+
viewname
|
|
145
|
+
);
|
|
134
146
|
const [relations, setRelations] = finder
|
|
135
|
-
? React.useState(
|
|
136
|
-
finder.findRelations(
|
|
137
|
-
options.tableName,
|
|
138
|
-
viewname,
|
|
139
|
-
options.excluded_subview_templates
|
|
140
|
-
)
|
|
141
|
-
)
|
|
147
|
+
? React.useState(relationsCache[options.tableName][viewname])
|
|
142
148
|
: [undefined, undefined];
|
|
143
149
|
let safeRelation = relation;
|
|
144
|
-
if (
|
|
145
|
-
|
|
150
|
+
if (
|
|
151
|
+
options.mode !== "filter" &&
|
|
152
|
+
!safeRelation &&
|
|
153
|
+
!hasLegacyRelation &&
|
|
154
|
+
relations?.paths.length > 0
|
|
155
|
+
) {
|
|
156
|
+
safeRelation = initialRelation(relations.paths, options.tableName);
|
|
146
157
|
setProp((prop) => {
|
|
147
158
|
prop.relation = safeRelation;
|
|
148
159
|
});
|
|
@@ -153,17 +164,29 @@ const ViewSettings = () => {
|
|
|
153
164
|
if (e.target) {
|
|
154
165
|
const target_value = e.target.value;
|
|
155
166
|
if (target_value !== viewname) {
|
|
156
|
-
|
|
157
|
-
options.tableName,
|
|
158
|
-
target_value,
|
|
159
|
-
options.excluded_subview_templates
|
|
160
|
-
);
|
|
161
|
-
if (newRelations.paths.length > 0) {
|
|
167
|
+
if (options.mode === "filter") {
|
|
162
168
|
setProp((prop) => {
|
|
163
169
|
prop.view = target_value;
|
|
164
|
-
prop.relation = newRelations.paths[0];
|
|
165
170
|
});
|
|
166
|
-
|
|
171
|
+
} else {
|
|
172
|
+
updateRelationsCache(
|
|
173
|
+
relationsCache,
|
|
174
|
+
setRelationsCache,
|
|
175
|
+
options,
|
|
176
|
+
finder,
|
|
177
|
+
target_value
|
|
178
|
+
);
|
|
179
|
+
const newRelations = relationsCache[options.tableName][target_value];
|
|
180
|
+
if (newRelations.paths.length > 0) {
|
|
181
|
+
setProp((prop) => {
|
|
182
|
+
prop.view = target_value;
|
|
183
|
+
prop.relation = initialRelation(
|
|
184
|
+
newRelations.paths,
|
|
185
|
+
options.tableName
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
setRelations(newRelations);
|
|
189
|
+
}
|
|
167
190
|
}
|
|
168
191
|
}
|
|
169
192
|
}
|
|
@@ -188,7 +211,7 @@ const ViewSettings = () => {
|
|
|
188
211
|
))}
|
|
189
212
|
</select>
|
|
190
213
|
</div>
|
|
191
|
-
{
|
|
214
|
+
{options.mode !== "filter" && (
|
|
192
215
|
<div>
|
|
193
216
|
<RelationOnDemandPicker
|
|
194
217
|
relations={relations.layers}
|
|
@@ -213,7 +236,7 @@ const ViewSettings = () => {
|
|
|
213
236
|
tableNameCache={caches.tableNameCache}
|
|
214
237
|
/>
|
|
215
238
|
</div>
|
|
216
|
-
}
|
|
239
|
+
)}
|
|
217
240
|
</Fragment>
|
|
218
241
|
) : (
|
|
219
242
|
<div>
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import React, { useMemo } from "react";
|
|
8
8
|
import { useNode } from "@craftjs/core";
|
|
9
9
|
import optionsCtx from "../context";
|
|
10
|
+
import relationsCtx from "../relations_context";
|
|
11
|
+
|
|
10
12
|
import {
|
|
11
13
|
BlockSetting,
|
|
12
14
|
MinRoleSettingRow,
|
|
@@ -17,6 +19,8 @@ import {
|
|
|
17
19
|
FormulaTooltip,
|
|
18
20
|
HelpTopicLink,
|
|
19
21
|
prepCacheAndFinder,
|
|
22
|
+
initialRelation,
|
|
23
|
+
updateRelationsCache,
|
|
20
24
|
} from "./utils";
|
|
21
25
|
|
|
22
26
|
import { RelationBadges } from "./RelationBadges";
|
|
@@ -127,6 +131,7 @@ const ViewLinkSettings = () => {
|
|
|
127
131
|
() => prepCacheAndFinder(options),
|
|
128
132
|
[undefined]
|
|
129
133
|
);
|
|
134
|
+
const { relationsCache, setRelationsCache } = React.useContext(relationsCtx);
|
|
130
135
|
let errorString = false;
|
|
131
136
|
try {
|
|
132
137
|
Function("return " + extra_state_fml);
|
|
@@ -142,16 +147,19 @@ const ViewLinkSettings = () => {
|
|
|
142
147
|
const safeViewName = use_view_name?.includes(".")
|
|
143
148
|
? use_view_name.split(".")[0]
|
|
144
149
|
: use_view_name;
|
|
150
|
+
updateRelationsCache(
|
|
151
|
+
relationsCache,
|
|
152
|
+
setRelationsCache,
|
|
153
|
+
options,
|
|
154
|
+
finder,
|
|
155
|
+
safeViewName
|
|
156
|
+
);
|
|
145
157
|
const [relations, setRelations] = React.useState(
|
|
146
|
-
|
|
147
|
-
options.tableName,
|
|
148
|
-
safeViewName,
|
|
149
|
-
options.excluded_subview_templates
|
|
150
|
-
)
|
|
158
|
+
relationsCache[options.tableName][safeViewName]
|
|
151
159
|
);
|
|
152
160
|
let safeRelation = relation;
|
|
153
161
|
if (!safeRelation && !hasLegacyRelation && relations?.paths.length > 0) {
|
|
154
|
-
safeRelation = relations.paths
|
|
162
|
+
safeRelation = initialRelation(relations.paths, options.tableName);
|
|
155
163
|
setProp((prop) => {
|
|
156
164
|
prop.relation = safeRelation;
|
|
157
165
|
});
|
|
@@ -160,15 +168,21 @@ const ViewLinkSettings = () => {
|
|
|
160
168
|
if (e.target) {
|
|
161
169
|
const target_value = e.target.value;
|
|
162
170
|
if (target_value !== use_view_name) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
options
|
|
171
|
+
updateRelationsCache(
|
|
172
|
+
relationsCache,
|
|
173
|
+
setRelationsCache,
|
|
174
|
+
options,
|
|
175
|
+
finder,
|
|
176
|
+
target_value
|
|
167
177
|
);
|
|
178
|
+
const newRelations = relationsCache[options.tableName][target_value];
|
|
168
179
|
if (newRelations.paths.length > 0) {
|
|
169
180
|
setProp((prop) => {
|
|
170
181
|
prop.name = target_value;
|
|
171
|
-
prop.relation =
|
|
182
|
+
prop.relation = initialRelation(
|
|
183
|
+
newRelations.paths,
|
|
184
|
+
options.tableName
|
|
185
|
+
);
|
|
172
186
|
});
|
|
173
187
|
setRelations(newRelations);
|
|
174
188
|
}
|
|
@@ -767,12 +767,16 @@ const ConfigField = ({
|
|
|
767
767
|
field.default
|
|
768
768
|
);
|
|
769
769
|
if (field.input_type === "fromtype") field.input_type = null;
|
|
770
|
-
if (
|
|
770
|
+
if (
|
|
771
|
+
field.type &&
|
|
772
|
+
(field.type.name === "String" || field.type === "String") &&
|
|
773
|
+
field.attributes?.options
|
|
774
|
+
) {
|
|
771
775
|
field.input_type = "select";
|
|
772
776
|
field.options =
|
|
773
|
-
typeof field.attributes
|
|
774
|
-
? field.attributes
|
|
775
|
-
: field.attributes
|
|
777
|
+
typeof field.attributes?.options === "string"
|
|
778
|
+
? field.attributes?.options.split(",").map((s) => s.trim())
|
|
779
|
+
: field.attributes?.options;
|
|
776
780
|
if (!field.required && field.options) field.options.unshift("");
|
|
777
781
|
}
|
|
778
782
|
const field_type = field.input_type || field.type.name || field.type;
|
|
@@ -781,7 +785,7 @@ const ConfigField = ({
|
|
|
781
785
|
field_type === "select";
|
|
782
786
|
const getOptions = () =>
|
|
783
787
|
typeof field?.attributes?.options === "string"
|
|
784
|
-
? field.attributes
|
|
788
|
+
? field.attributes?.options.split(",").map((s) => s.trim())
|
|
785
789
|
: field?.attributes?.options || field.options;
|
|
786
790
|
|
|
787
791
|
if (hasSelect && typeof value === "undefined") {
|
|
@@ -890,6 +894,7 @@ const ConfigField = ({
|
|
|
890
894
|
className="form-control"
|
|
891
895
|
value={value}
|
|
892
896
|
onChange={(e) => e.target && myOnChange(e.target.value)}
|
|
897
|
+
spellCheck={false}
|
|
893
898
|
/>
|
|
894
899
|
),
|
|
895
900
|
select: () => (
|
|
@@ -1501,3 +1506,64 @@ export const prepCacheAndFinder = ({
|
|
|
1501
1506
|
return { caches, finder };
|
|
1502
1507
|
} else return { caches: null, finder: null };
|
|
1503
1508
|
};
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* @param {string[]} paths
|
|
1512
|
+
* @param {string} sourceTbl name of the topview table
|
|
1513
|
+
* @returns either a same table relation, a parent relation, a child relation, or the first relation
|
|
1514
|
+
*/
|
|
1515
|
+
export const initialRelation = (paths, sourceTbl) => {
|
|
1516
|
+
let sameTblRel = null;
|
|
1517
|
+
let parentRel = null;
|
|
1518
|
+
let childRel = null;
|
|
1519
|
+
for (const path of paths) {
|
|
1520
|
+
if (!sameTblRel && path === `.${sourceTbl}`) sameTblRel = path;
|
|
1521
|
+
else {
|
|
1522
|
+
const tokens = path.split(".");
|
|
1523
|
+
if (
|
|
1524
|
+
!parentRel &&
|
|
1525
|
+
tokens.length === 3 &&
|
|
1526
|
+
tokens[1] === sourceTbl &&
|
|
1527
|
+
tokens[2].indexOf("$") === -1
|
|
1528
|
+
)
|
|
1529
|
+
parentRel = path;
|
|
1530
|
+
else {
|
|
1531
|
+
const lastToken = tokens[tokens.length - 1];
|
|
1532
|
+
if (
|
|
1533
|
+
lastToken.indexOf("$") > 0 &&
|
|
1534
|
+
(!childRel || childRel.split(".").length > tokens.length)
|
|
1535
|
+
)
|
|
1536
|
+
childRel = path;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return sameTblRel || parentRel || childRel || paths[0];
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* update the builder wide relations cache relations cache
|
|
1545
|
+
* if there is no entry for the given tableName and viewname
|
|
1546
|
+
* @param {any} relationsCache cache from the context
|
|
1547
|
+
* @param {Function} setRelationsCache set cache in context
|
|
1548
|
+
* @param {any} options builder options
|
|
1549
|
+
* @param {RelationsFinder} finder
|
|
1550
|
+
* @param {string} viewname subview name
|
|
1551
|
+
*/
|
|
1552
|
+
export const updateRelationsCache = (
|
|
1553
|
+
relationsCache,
|
|
1554
|
+
setRelationsCache,
|
|
1555
|
+
options,
|
|
1556
|
+
finder,
|
|
1557
|
+
viewname
|
|
1558
|
+
) => {
|
|
1559
|
+
if (!relationsCache[options.tableName])
|
|
1560
|
+
relationsCache[options.tableName] = {};
|
|
1561
|
+
if (!relationsCache[options.tableName][viewname]) {
|
|
1562
|
+
relationsCache[options.tableName][viewname] = finder.findRelations(
|
|
1563
|
+
options.tableName,
|
|
1564
|
+
viewname,
|
|
1565
|
+
options.excluded_subview_templates
|
|
1566
|
+
);
|
|
1567
|
+
setRelationsCache({ ...relationsCache });
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
@@ -38,14 +38,22 @@ const doTest = (
|
|
|
38
38
|
name: view || viewName,
|
|
39
39
|
});
|
|
40
40
|
require("@craftjs/core").useNode.mockImplementation(useNodeMock);
|
|
41
|
+
let relationsCache = {};
|
|
42
|
+
const setRelationsCache = (newVal) => {
|
|
43
|
+
relationsCache = newVal;
|
|
44
|
+
};
|
|
41
45
|
// mock react context
|
|
42
46
|
const useContextMock = (React.useContext = jest.fn());
|
|
43
47
|
useContextMock.mockReturnValue({
|
|
48
|
+
// optionsCtx part
|
|
44
49
|
tables: tables,
|
|
45
50
|
views: views,
|
|
46
51
|
tableName: tableName,
|
|
47
52
|
roles: [],
|
|
48
53
|
excluded_subview_templates: excludedTemplates,
|
|
54
|
+
// relationsCtx part
|
|
55
|
+
relationsCache: relationsCache,
|
|
56
|
+
setRelationsCache: setRelationsCache,
|
|
49
57
|
});
|
|
50
58
|
// spy on useState and extract the relations (first call)
|
|
51
59
|
const spy = jest.spyOn(React, "useState");
|