@saltcorn/builder 0.9.4-beta.0 → 0.9.4-beta.10
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 +15 -11
- package/package.json +2 -1
- package/src/components/Builder.js +59 -13
- package/src/components/Library.js +31 -19
- package/src/components/Toolbox.js +112 -163
- package/src/components/elements/Columns.js +1 -1
- package/src/components/elements/Container.js +1 -1
- package/src/components/elements/RelationBadges.js +53 -44
- package/src/components/elements/Tabs.js +92 -40
- package/src/components/elements/View.js +84 -38
- package/src/components/elements/ViewLink.js +79 -34
- package/src/components/elements/utils.js +138 -75
- package/src/components/storage.js +2 -0
- package/tests/relations_finder.test.js +57 -92
- package/tests/test_data.js +0 -163
|
@@ -1,33 +1,49 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { removeWhitespaces } from "./utils";
|
|
3
|
+
import { parseLegacyRelation, RelationType } from "@saltcorn/common-code";
|
|
3
4
|
|
|
4
|
-
const buildBadgeCfgs = (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
const buildBadgeCfgs = (sourceTblName, type, path, caches) => {
|
|
6
|
+
if (type === RelationType.OWN)
|
|
7
|
+
return [{ table: `${sourceTblName} (same table)` }];
|
|
8
|
+
else if (type === RelationType.INDEPENDENT)
|
|
9
|
+
return [{ table: "None (no relation)" }];
|
|
10
|
+
else if (path.length === 0) return [{ table: "invalid relation" }];
|
|
11
|
+
else {
|
|
12
|
+
const result = [];
|
|
13
|
+
let currentCfg = null;
|
|
14
|
+
let currentTbl = sourceTblName;
|
|
15
|
+
for (const pathElement of path) {
|
|
16
|
+
if (pathElement.inboundKey) {
|
|
17
|
+
if (currentCfg) result.push(currentCfg);
|
|
18
|
+
currentTbl = pathElement.table;
|
|
19
|
+
currentCfg = { up: pathElement.inboundKey, table: currentTbl };
|
|
20
|
+
} else if (pathElement.fkey) {
|
|
21
|
+
if (!currentCfg)
|
|
22
|
+
result.push({ down: pathElement.fkey, table: currentTbl });
|
|
23
|
+
else {
|
|
24
|
+
currentCfg.down = pathElement.fkey;
|
|
25
|
+
result.push(currentCfg);
|
|
26
|
+
}
|
|
27
|
+
const tblObj = caches.tableNameCache[currentTbl];
|
|
28
|
+
const fkey = tblObj.foreign_keys.find(
|
|
29
|
+
(key) => key.name === pathElement.fkey
|
|
30
|
+
);
|
|
31
|
+
currentTbl = fkey.reftable_name;
|
|
32
|
+
currentCfg = { table: currentTbl };
|
|
16
33
|
}
|
|
17
|
-
currentCfg = { table };
|
|
18
34
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
if (
|
|
36
|
+
currentCfg &&
|
|
37
|
+
!result.find(
|
|
38
|
+
({ down, table, up }) =>
|
|
39
|
+
down === currentCfg.down &&
|
|
40
|
+
table === currentCfg.table &&
|
|
41
|
+
up === currentCfg.up
|
|
42
|
+
)
|
|
27
43
|
)
|
|
28
|
-
|
|
29
|
-
result
|
|
30
|
-
|
|
44
|
+
result.push(currentCfg);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
31
47
|
};
|
|
32
48
|
|
|
33
49
|
const buildBadge = ({ up, table, down }, index) => {
|
|
@@ -59,41 +75,34 @@ const buildBadge = ({ up, table, down }, index) => {
|
|
|
59
75
|
);
|
|
60
76
|
};
|
|
61
77
|
|
|
62
|
-
export const RelationBadges = ({
|
|
63
|
-
view,
|
|
64
|
-
relation,
|
|
65
|
-
parentTbl,
|
|
66
|
-
tableNameCache,
|
|
67
|
-
}) => {
|
|
78
|
+
export const RelationBadges = ({ view, relation, parentTbl, caches }) => {
|
|
68
79
|
if (relation) {
|
|
69
|
-
const parsed = relationHelpers.parseRelationPath(relation, tableNameCache);
|
|
70
|
-
|
|
71
80
|
return (
|
|
72
81
|
<div className="overflow-scroll">
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
{buildBadgeCfgs(
|
|
83
|
+
relation.sourceTblName,
|
|
84
|
+
relation.type,
|
|
85
|
+
relation.path,
|
|
86
|
+
caches
|
|
87
|
+
).map(buildBadge)}
|
|
76
88
|
</div>
|
|
77
89
|
);
|
|
78
90
|
} else {
|
|
79
91
|
if (!view) return buildBadge({ table: "invalid relation" }, 0);
|
|
80
92
|
const [prefix, rest] = view.split(":");
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
else if (
|
|
85
|
-
parsed.length === 1 &&
|
|
86
|
-
(parsed[0].type === "Independent" || parsed[0].type === "Own")
|
|
87
|
-
)
|
|
93
|
+
if (!rest) return buildBadge({ table: "invalid relation" }, 0);
|
|
94
|
+
const { type, path } = parseLegacyRelation(prefix, rest, parentTbl);
|
|
95
|
+
if (path.length === 0) return buildBadge({ table: "invalid relation" }, 0);
|
|
96
|
+
else if (path.length === 1 && (type === "Independent" || type === "Own"))
|
|
88
97
|
return (
|
|
89
98
|
<div className="overflow-scroll">
|
|
90
|
-
{buildBadge({ table:
|
|
99
|
+
{buildBadge({ table: path[0].table }, 0)}
|
|
91
100
|
</div>
|
|
92
101
|
);
|
|
93
102
|
else
|
|
94
103
|
return (
|
|
95
104
|
<div className="overflow-scroll">
|
|
96
|
-
{buildBadgeCfgs(
|
|
105
|
+
{buildBadgeCfgs(parentTbl, type, path, caches).map(buildBadge)}
|
|
97
106
|
</div>
|
|
98
107
|
);
|
|
99
108
|
}
|
|
@@ -8,7 +8,7 @@ import React, { Fragment, useState, useContext, useEffect } from "react";
|
|
|
8
8
|
import { ntimes } from "./Columns";
|
|
9
9
|
import { Column } from "./Column";
|
|
10
10
|
import optionsCtx from "../context";
|
|
11
|
-
import { setAPropGen, buildOptions } from "./utils";
|
|
11
|
+
import { setAPropGen, buildOptions, ConfigField } from "./utils";
|
|
12
12
|
|
|
13
13
|
import { Element, useNode } from "@craftjs/core";
|
|
14
14
|
|
|
@@ -31,14 +31,16 @@ const Tabs = ({
|
|
|
31
31
|
independent,
|
|
32
32
|
startClosed,
|
|
33
33
|
field,
|
|
34
|
+
setting_tab_n,
|
|
34
35
|
}) => {
|
|
35
36
|
const {
|
|
36
37
|
selected,
|
|
37
38
|
connectors: { connect, drag },
|
|
39
|
+
actions: { setProp },
|
|
38
40
|
} = useNode((node) => ({ selected: node.events.selected }));
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
41
|
+
|
|
42
|
+
const showTab = setting_tab_n;
|
|
43
|
+
const setShowTab = (n) => setProp((prop) => (prop.setting_tab_n = n));
|
|
42
44
|
const [showTabs, setShowTabs] = useState(
|
|
43
45
|
tabsStyle === "Accordion" && startClosed ? [] : [true]
|
|
44
46
|
);
|
|
@@ -178,8 +180,10 @@ const TabsSettings = () => {
|
|
|
178
180
|
deeplink: node.data.props.deeplink,
|
|
179
181
|
disable_inactive: node.data.props.disable_inactive,
|
|
180
182
|
serverRendered: node.data.props.serverRendered,
|
|
183
|
+
setting_tab_n: node.data.props.setting_tab_n,
|
|
181
184
|
tabId: node.data.props.tabId,
|
|
182
185
|
titles: node.data.props.titles,
|
|
186
|
+
showif: node.data.props.showif,
|
|
183
187
|
field: node.data.props.field,
|
|
184
188
|
}));
|
|
185
189
|
const {
|
|
@@ -194,7 +198,10 @@ const TabsSettings = () => {
|
|
|
194
198
|
field,
|
|
195
199
|
serverRendered,
|
|
196
200
|
tabId,
|
|
201
|
+
showif,
|
|
202
|
+
setting_tab_n,
|
|
197
203
|
} = node;
|
|
204
|
+
const use_setting_tab_n = setting_tab_n || 0;
|
|
198
205
|
const options = useContext(optionsCtx);
|
|
199
206
|
useEffect(() => {
|
|
200
207
|
if (field)
|
|
@@ -263,42 +270,6 @@ const TabsSettings = () => {
|
|
|
263
270
|
</Fragment>
|
|
264
271
|
) : (
|
|
265
272
|
<Fragment>
|
|
266
|
-
<tr>
|
|
267
|
-
<th>
|
|
268
|
-
<label>Number of sections</label>
|
|
269
|
-
</th>
|
|
270
|
-
<td>
|
|
271
|
-
<input
|
|
272
|
-
type="number"
|
|
273
|
-
className="form-control"
|
|
274
|
-
value={ntabs}
|
|
275
|
-
step="1"
|
|
276
|
-
min="0"
|
|
277
|
-
max="20"
|
|
278
|
-
onChange={setAProp("ntabs")}
|
|
279
|
-
/>
|
|
280
|
-
</td>
|
|
281
|
-
</tr>
|
|
282
|
-
<tr>
|
|
283
|
-
<th colSpan="2">Titles</th>
|
|
284
|
-
</tr>
|
|
285
|
-
{ntimes(ntabs, (ix) => (
|
|
286
|
-
<tr key={ix}>
|
|
287
|
-
<th>{ix + 1}</th>
|
|
288
|
-
<td>
|
|
289
|
-
<input
|
|
290
|
-
type="text"
|
|
291
|
-
className="form-control text-to-display"
|
|
292
|
-
value={titles[ix]}
|
|
293
|
-
onChange={(e) => {
|
|
294
|
-
if (!e.target) return;
|
|
295
|
-
const value = e.target.value;
|
|
296
|
-
setProp((prop) => (prop.titles[ix] = value));
|
|
297
|
-
}}
|
|
298
|
-
/>
|
|
299
|
-
</td>
|
|
300
|
-
</tr>
|
|
301
|
-
))}
|
|
302
273
|
{tabsStyle === "Accordion" ? (
|
|
303
274
|
<tr>
|
|
304
275
|
<td colSpan="2">
|
|
@@ -401,6 +372,84 @@ const TabsSettings = () => {
|
|
|
401
372
|
</td>
|
|
402
373
|
</tr>
|
|
403
374
|
) : null}
|
|
375
|
+
<tr>
|
|
376
|
+
<th>
|
|
377
|
+
<label>Number of sections</label>
|
|
378
|
+
</th>
|
|
379
|
+
<td>
|
|
380
|
+
<input
|
|
381
|
+
type="number"
|
|
382
|
+
className="form-control"
|
|
383
|
+
value={ntabs}
|
|
384
|
+
step="1"
|
|
385
|
+
min="1"
|
|
386
|
+
max="20"
|
|
387
|
+
onChange={setAProp("ntabs")}
|
|
388
|
+
/>
|
|
389
|
+
</td>
|
|
390
|
+
</tr>
|
|
391
|
+
<tr>
|
|
392
|
+
<td colSpan={2}>
|
|
393
|
+
<ConfigField
|
|
394
|
+
field={{
|
|
395
|
+
name: "setting_tab_n",
|
|
396
|
+
label: "Tab number",
|
|
397
|
+
type: "btn_select",
|
|
398
|
+
options: ntimes(ntabs, (i) => ({
|
|
399
|
+
value: i,
|
|
400
|
+
title: `${i + 1}`,
|
|
401
|
+
label: `${i + 1}`,
|
|
402
|
+
})),
|
|
403
|
+
}}
|
|
404
|
+
node={node}
|
|
405
|
+
setProp={setProp}
|
|
406
|
+
props={node}
|
|
407
|
+
></ConfigField>
|
|
408
|
+
</td>
|
|
409
|
+
</tr>
|
|
410
|
+
<tr>
|
|
411
|
+
<th colSpan="2">Title</th>
|
|
412
|
+
</tr>
|
|
413
|
+
<tr>
|
|
414
|
+
<td colSpan={2}>
|
|
415
|
+
<input
|
|
416
|
+
type="text"
|
|
417
|
+
className="form-control text-to-display"
|
|
418
|
+
value={titles[use_setting_tab_n] || ""}
|
|
419
|
+
onChange={(e) => {
|
|
420
|
+
if (!e.target) return;
|
|
421
|
+
const value = e.target.value;
|
|
422
|
+
setProp((prop) => (prop.titles[use_setting_tab_n] = value));
|
|
423
|
+
}}
|
|
424
|
+
/>
|
|
425
|
+
</td>
|
|
426
|
+
</tr>
|
|
427
|
+
{options.mode === "show" ||
|
|
428
|
+
options.mode === "edit" ||
|
|
429
|
+
options.mode === "filter" ? (
|
|
430
|
+
<Fragment>
|
|
431
|
+
<tr>
|
|
432
|
+
<th colSpan="2">Show if formula</th>
|
|
433
|
+
</tr>
|
|
434
|
+
<tr>
|
|
435
|
+
<td colSpan={2}>
|
|
436
|
+
<input
|
|
437
|
+
type="text"
|
|
438
|
+
className="form-control text-to-display"
|
|
439
|
+
value={showif?.[use_setting_tab_n] || ""}
|
|
440
|
+
onChange={(e) => {
|
|
441
|
+
if (!e.target) return;
|
|
442
|
+
const value = e.target.value;
|
|
443
|
+
setProp((prop) => {
|
|
444
|
+
if (!prop.showif) prop.showif = [];
|
|
445
|
+
prop.showif[use_setting_tab_n] = value;
|
|
446
|
+
});
|
|
447
|
+
}}
|
|
448
|
+
/>
|
|
449
|
+
</td>
|
|
450
|
+
</tr>
|
|
451
|
+
</Fragment>
|
|
452
|
+
) : null}
|
|
404
453
|
</Fragment>
|
|
405
454
|
)}
|
|
406
455
|
</tbody>
|
|
@@ -414,6 +463,7 @@ const TabsSettings = () => {
|
|
|
414
463
|
Tabs.craft = {
|
|
415
464
|
props: {
|
|
416
465
|
titles: ["Tab1", "Tab2"],
|
|
466
|
+
showif: [],
|
|
417
467
|
ntabs: 2,
|
|
418
468
|
tabsStyle: "Tabs",
|
|
419
469
|
independent: false,
|
|
@@ -421,8 +471,10 @@ Tabs.craft = {
|
|
|
421
471
|
deeplink: true,
|
|
422
472
|
disable_inactive: false,
|
|
423
473
|
serverRendered: false,
|
|
474
|
+
setting_tab_n: 0,
|
|
424
475
|
tabId: "",
|
|
425
476
|
},
|
|
477
|
+
defaultProps: { setting_tab_n: 0, ntabs: 2 },
|
|
426
478
|
displayName: "Tabs",
|
|
427
479
|
related: {
|
|
428
480
|
settings: TabsSettings,
|
|
@@ -17,13 +17,18 @@ import {
|
|
|
17
17
|
buildOptions,
|
|
18
18
|
HelpTopicLink,
|
|
19
19
|
initialRelation,
|
|
20
|
-
|
|
21
|
-
updateRelationsCache,
|
|
20
|
+
buildLayers,
|
|
22
21
|
} from "./utils";
|
|
23
22
|
|
|
24
23
|
import { RelationBadges } from "./RelationBadges";
|
|
25
24
|
import { RelationOnDemandPicker } from "./RelationOnDemandPicker";
|
|
26
25
|
|
|
26
|
+
import {
|
|
27
|
+
RelationsFinder,
|
|
28
|
+
Relation,
|
|
29
|
+
buildTableCaches,
|
|
30
|
+
} from "@saltcorn/common-code";
|
|
31
|
+
|
|
27
32
|
export /**
|
|
28
33
|
* @param {object} props
|
|
29
34
|
* @param {*} props.name
|
|
@@ -48,7 +53,6 @@ const View = ({ name, view, configuration, state }) => {
|
|
|
48
53
|
if (rest.startsWith(".")) viewname = prefix;
|
|
49
54
|
else viewname = rest;
|
|
50
55
|
}
|
|
51
|
-
|
|
52
56
|
const theview = options.views.find((v) => v.name === viewname);
|
|
53
57
|
const label = theview ? theview.label : view;
|
|
54
58
|
const { previews, setPreviews } = React.useContext(previewCtx);
|
|
@@ -109,10 +113,23 @@ const ViewSettings = () => {
|
|
|
109
113
|
extra_state_fml,
|
|
110
114
|
} = node;
|
|
111
115
|
const options = React.useContext(optionsCtx);
|
|
112
|
-
const {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const {
|
|
117
|
+
tables,
|
|
118
|
+
views,
|
|
119
|
+
max_relations_layer_depth,
|
|
120
|
+
tableName,
|
|
121
|
+
excluded_subview_templates,
|
|
122
|
+
} = options;
|
|
123
|
+
// not needed in page editor
|
|
124
|
+
let finder = null;
|
|
125
|
+
let tableCaches = null;
|
|
126
|
+
if (tables && views) {
|
|
127
|
+
finder = useMemo(
|
|
128
|
+
() => new RelationsFinder(tables, views, max_relations_layer_depth),
|
|
129
|
+
[undefined]
|
|
130
|
+
);
|
|
131
|
+
tableCaches = useMemo(() => buildTableCaches(tables), [undefined]);
|
|
132
|
+
}
|
|
116
133
|
const fixed_state_fields =
|
|
117
134
|
options.fixed_state_fields && options.fixed_state_fields[view];
|
|
118
135
|
const { setPreviews } = React.useContext(previewCtx);
|
|
@@ -135,27 +152,48 @@ const ViewSettings = () => {
|
|
|
135
152
|
else viewname = rest;
|
|
136
153
|
}
|
|
137
154
|
if (viewname.includes(".")) viewname = viewname.split(".")[0];
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
finder &&
|
|
158
|
+
!(relationsCache[tableName] && relationsCache[tableName][viewname])
|
|
159
|
+
) {
|
|
160
|
+
const relations = finder.findRelations(
|
|
161
|
+
tableName,
|
|
162
|
+
viewname,
|
|
163
|
+
excluded_subview_templates
|
|
145
164
|
);
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
const layers = buildLayers(
|
|
166
|
+
relations,
|
|
167
|
+
tableName,
|
|
168
|
+
tableCaches.tableNameCache
|
|
169
|
+
);
|
|
170
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
171
|
+
relationsCache[tableName][viewname] = { relations, layers };
|
|
172
|
+
setRelationsCache({ ...relationsCache });
|
|
173
|
+
}
|
|
174
|
+
const [relationsData, setRelationsData] = finder
|
|
175
|
+
? React.useState(relationsCache[tableName][viewname])
|
|
148
176
|
: [undefined, undefined];
|
|
149
|
-
let safeRelation =
|
|
177
|
+
let safeRelation = null;
|
|
178
|
+
const subView = views.find((view) => view.name === viewname);
|
|
179
|
+
if (relation) {
|
|
180
|
+
const subTbl = tables.find((tbl) => tbl.id === subView.table_id);
|
|
181
|
+
safeRelation = new Relation(
|
|
182
|
+
relation,
|
|
183
|
+
subTbl ? subTbl.name : "",
|
|
184
|
+
subView.display_type
|
|
185
|
+
);
|
|
186
|
+
}
|
|
150
187
|
if (
|
|
151
188
|
options.mode !== "filter" &&
|
|
189
|
+
subView.table_id &&
|
|
152
190
|
!safeRelation &&
|
|
153
191
|
!hasLegacyRelation &&
|
|
154
|
-
relations
|
|
192
|
+
relationsData?.relations.length > 0
|
|
155
193
|
) {
|
|
156
|
-
safeRelation = initialRelation(relations
|
|
194
|
+
safeRelation = initialRelation(relationsData.relations);
|
|
157
195
|
setProp((prop) => {
|
|
158
|
-
prop.relation = safeRelation;
|
|
196
|
+
prop.relation = safeRelation.relationString;
|
|
159
197
|
});
|
|
160
198
|
}
|
|
161
199
|
const helpContext = { view_name: viewname };
|
|
@@ -169,24 +207,32 @@ const ViewSettings = () => {
|
|
|
169
207
|
prop.view = target_value;
|
|
170
208
|
});
|
|
171
209
|
} else {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
finder,
|
|
177
|
-
target_value
|
|
210
|
+
const newRelations = finder.findRelations(
|
|
211
|
+
tableName,
|
|
212
|
+
target_value,
|
|
213
|
+
excluded_subview_templates
|
|
178
214
|
);
|
|
179
|
-
const
|
|
180
|
-
|
|
215
|
+
const layers = buildLayers(
|
|
216
|
+
newRelations,
|
|
217
|
+
tableName,
|
|
218
|
+
tableCaches.tableNameCache
|
|
219
|
+
);
|
|
220
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
221
|
+
relationsCache[tableName][target_value] = {
|
|
222
|
+
relations: newRelations,
|
|
223
|
+
layers,
|
|
224
|
+
};
|
|
225
|
+
if (newRelations.length > 0) {
|
|
181
226
|
setProp((prop) => {
|
|
182
227
|
prop.view = target_value;
|
|
183
|
-
prop.relation = initialRelation(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
228
|
+
prop.relation = initialRelation(newRelations).relationString;
|
|
229
|
+
});
|
|
230
|
+
setRelationsData({ relations: newRelations, layers });
|
|
231
|
+
} else
|
|
232
|
+
window.notifyAlert({
|
|
233
|
+
type: "warning",
|
|
234
|
+
text: `${target_value} has no relations`,
|
|
187
235
|
});
|
|
188
|
-
setRelations(newRelations);
|
|
189
|
-
}
|
|
190
236
|
}
|
|
191
237
|
}
|
|
192
238
|
}
|
|
@@ -194,7 +240,7 @@ const ViewSettings = () => {
|
|
|
194
240
|
|
|
195
241
|
return (
|
|
196
242
|
<div>
|
|
197
|
-
{
|
|
243
|
+
{relationsData ? (
|
|
198
244
|
<Fragment>
|
|
199
245
|
<div>
|
|
200
246
|
<label>View to {options.mode === "show" ? "embed" : "show"}</label>
|
|
@@ -214,7 +260,7 @@ const ViewSettings = () => {
|
|
|
214
260
|
{options.mode !== "filter" && (
|
|
215
261
|
<div>
|
|
216
262
|
<RelationOnDemandPicker
|
|
217
|
-
relations={
|
|
263
|
+
relations={relationsData.layers}
|
|
218
264
|
update={(relPath) => {
|
|
219
265
|
if (relPath.startsWith(".")) {
|
|
220
266
|
setProp((prop) => {
|
|
@@ -232,8 +278,8 @@ const ViewSettings = () => {
|
|
|
232
278
|
<RelationBadges
|
|
233
279
|
view={view}
|
|
234
280
|
relation={safeRelation}
|
|
235
|
-
parentTbl={
|
|
236
|
-
|
|
281
|
+
parentTbl={tableName}
|
|
282
|
+
caches={tableCaches}
|
|
237
283
|
/>
|
|
238
284
|
</div>
|
|
239
285
|
)}
|
|
@@ -18,14 +18,19 @@ import {
|
|
|
18
18
|
setAPropGen,
|
|
19
19
|
FormulaTooltip,
|
|
20
20
|
HelpTopicLink,
|
|
21
|
-
prepCacheAndFinder,
|
|
22
21
|
initialRelation,
|
|
23
|
-
|
|
22
|
+
buildLayers,
|
|
24
23
|
} from "./utils";
|
|
25
24
|
|
|
26
25
|
import { RelationBadges } from "./RelationBadges";
|
|
27
26
|
import { RelationOnDemandPicker } from "./RelationOnDemandPicker";
|
|
28
27
|
|
|
28
|
+
import {
|
|
29
|
+
RelationsFinder,
|
|
30
|
+
Relation,
|
|
31
|
+
buildTableCaches,
|
|
32
|
+
} from "@saltcorn/common-code";
|
|
33
|
+
|
|
29
34
|
export /**
|
|
30
35
|
* @param {object} props
|
|
31
36
|
* @param {string} props.name
|
|
@@ -127,10 +132,18 @@ const ViewLinkSettings = () => {
|
|
|
127
132
|
link_target_blank,
|
|
128
133
|
} = node;
|
|
129
134
|
const options = React.useContext(optionsCtx);
|
|
130
|
-
const {
|
|
131
|
-
|
|
135
|
+
const {
|
|
136
|
+
tables,
|
|
137
|
+
views,
|
|
138
|
+
tableName,
|
|
139
|
+
excluded_subview_templates,
|
|
140
|
+
max_relations_layer_depth,
|
|
141
|
+
} = options;
|
|
142
|
+
const finder = useMemo(
|
|
143
|
+
() => new RelationsFinder(tables, views, max_relations_layer_depth),
|
|
132
144
|
[undefined]
|
|
133
145
|
);
|
|
146
|
+
const tableCaches = useMemo(() => buildTableCaches(tables), [undefined]);
|
|
134
147
|
const { relationsCache, setRelationsCache } = React.useContext(relationsCtx);
|
|
135
148
|
let errorString = false;
|
|
136
149
|
try {
|
|
@@ -147,50 +160,82 @@ const ViewLinkSettings = () => {
|
|
|
147
160
|
const safeViewName = use_view_name?.includes(".")
|
|
148
161
|
? use_view_name.split(".")[0]
|
|
149
162
|
: use_view_name;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
const subView = views.find((view) => view.name === safeViewName);
|
|
164
|
+
const hasTableId = subView?.table_id !== undefined;
|
|
165
|
+
if (!(relationsCache[tableName] && relationsCache[tableName][safeViewName])) {
|
|
166
|
+
const relations = finder.findRelations(
|
|
167
|
+
tableName,
|
|
168
|
+
safeViewName,
|
|
169
|
+
excluded_subview_templates
|
|
170
|
+
);
|
|
171
|
+
const layers = buildLayers(
|
|
172
|
+
relations,
|
|
173
|
+
tableName,
|
|
174
|
+
tableCaches.tableNameCache
|
|
175
|
+
);
|
|
176
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
177
|
+
relationsCache[tableName][safeViewName] = { relations, layers };
|
|
178
|
+
setRelationsCache({ ...relationsCache });
|
|
179
|
+
}
|
|
180
|
+
const [relationsData, setRelationsData] = React.useState(
|
|
158
181
|
relationsCache[options.tableName][safeViewName]
|
|
159
182
|
);
|
|
160
|
-
let safeRelation =
|
|
161
|
-
if (
|
|
162
|
-
|
|
183
|
+
let safeRelation = null;
|
|
184
|
+
if (relation) {
|
|
185
|
+
const subView = views.find((view) => view.name === safeViewName);
|
|
186
|
+
const subTbl = tables.find((tbl) => tbl.id === subView.table_id);
|
|
187
|
+
safeRelation = new Relation(
|
|
188
|
+
relation,
|
|
189
|
+
subTbl ? subTbl.name : "",
|
|
190
|
+
subView.display_type
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (
|
|
194
|
+
!safeRelation &&
|
|
195
|
+
!hasLegacyRelation &&
|
|
196
|
+
relationsData?.relations.length > 0
|
|
197
|
+
) {
|
|
198
|
+
safeRelation = initialRelation(relationsData.relations);
|
|
163
199
|
setProp((prop) => {
|
|
164
|
-
prop.relation = safeRelation;
|
|
200
|
+
prop.relation = safeRelation.relationString;
|
|
165
201
|
});
|
|
166
202
|
}
|
|
167
203
|
const set_view_name = (e) => {
|
|
168
204
|
if (e.target) {
|
|
169
205
|
const target_value = e.target.value;
|
|
170
206
|
if (target_value !== use_view_name) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
207
|
+
const newRelations = finder.findRelations(
|
|
208
|
+
tableName,
|
|
209
|
+
target_value,
|
|
210
|
+
excluded_subview_templates
|
|
211
|
+
);
|
|
212
|
+
const layers = buildLayers(
|
|
213
|
+
newRelations,
|
|
214
|
+
tableName,
|
|
215
|
+
tableCaches.tableNameCache
|
|
177
216
|
);
|
|
178
|
-
|
|
179
|
-
|
|
217
|
+
|
|
218
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
219
|
+
relationsCache[tableName][target_value] = {
|
|
220
|
+
relations: newRelations,
|
|
221
|
+
layers,
|
|
222
|
+
};
|
|
223
|
+
if (newRelations.length > 0) {
|
|
180
224
|
setProp((prop) => {
|
|
181
225
|
prop.name = target_value;
|
|
182
|
-
prop.relation = initialRelation(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
226
|
+
prop.relation = initialRelation(newRelations).relationString;
|
|
227
|
+
});
|
|
228
|
+
setRelationsData({ relations: newRelations, layers });
|
|
229
|
+
} else
|
|
230
|
+
window.notifyAlert({
|
|
231
|
+
type: "warning",
|
|
232
|
+
text: `${target_value} has no relations`,
|
|
186
233
|
});
|
|
187
|
-
setRelations(newRelations);
|
|
188
|
-
}
|
|
189
234
|
}
|
|
190
235
|
}
|
|
191
236
|
};
|
|
192
237
|
const helpContext = { view_name: use_view_name };
|
|
193
|
-
if (
|
|
238
|
+
if (tableName) helpContext.srcTable = tableName;
|
|
194
239
|
return (
|
|
195
240
|
<div>
|
|
196
241
|
<table className="w-100">
|
|
@@ -215,7 +260,7 @@ const ViewLinkSettings = () => {
|
|
|
215
260
|
<tr>
|
|
216
261
|
<td colSpan="2">
|
|
217
262
|
<RelationOnDemandPicker
|
|
218
|
-
relations={
|
|
263
|
+
relations={relationsData.layers}
|
|
219
264
|
update={(relPath) => {
|
|
220
265
|
if (relPath.startsWith(".")) {
|
|
221
266
|
setProp((prop) => {
|
|
@@ -233,8 +278,8 @@ const ViewLinkSettings = () => {
|
|
|
233
278
|
<RelationBadges
|
|
234
279
|
view={name}
|
|
235
280
|
relation={safeRelation}
|
|
236
|
-
parentTbl={
|
|
237
|
-
|
|
281
|
+
parentTbl={tableName}
|
|
282
|
+
caches={tableCaches}
|
|
238
283
|
/>
|
|
239
284
|
</td>
|
|
240
285
|
</tr>
|