@saltcorn/builder 0.9.4-beta.8 → 0.9.4-beta.9
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 +12 -12
- package/package.json +2 -1
- package/src/components/Builder.js +17 -4
- package/src/components/Library.js +25 -5
- package/src/components/elements/RelationBadges.js +53 -44
- package/src/components/elements/Tabs.js +3 -1
- package/src/components/elements/View.js +84 -38
- package/src/components/elements/ViewLink.js +79 -34
- package/src/components/elements/utils.js +124 -77
- package/tests/relations_finder.test.js +57 -92
- package/tests/test_data.js +0 -163
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/builder",
|
|
3
|
-
"version": "0.9.4-beta.
|
|
3
|
+
"version": "0.9.4-beta.9",
|
|
4
4
|
"description": "Drag and drop view builder for Saltcorn, open-source no-code platform",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"homepage": "https://saltcorn.com",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"@babel/preset-react": "7.9.4",
|
|
21
21
|
"@craftjs/core": "0.1.0-beta.20",
|
|
22
22
|
"@craftjs/utils": "0.1.0-beta.20",
|
|
23
|
+
"@saltcorn/common-code": "0.9.4-beta.8",
|
|
23
24
|
"saltcorn-craft-layers-noeye": "0.1.0-beta.22",
|
|
24
25
|
"@fonticonpicker/react-fonticonpicker": "1.2.0",
|
|
25
26
|
"@fortawesome/fontawesome-svg-core": "1.2.34",
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
faRedo,
|
|
53
53
|
faTrashAlt,
|
|
54
54
|
faSave,
|
|
55
|
+
faExclamationTriangle,
|
|
55
56
|
} from "@fortawesome/free-solid-svg-icons";
|
|
56
57
|
import {
|
|
57
58
|
faCaretSquareLeft,
|
|
@@ -394,7 +395,7 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
394
395
|
const [previews, setPreviews] = useState({});
|
|
395
396
|
const [uploadedFiles, setUploadedFiles] = useState([]);
|
|
396
397
|
const nodekeys = useRef([]);
|
|
397
|
-
const [
|
|
398
|
+
const [savingState, setSavingState] = useState({ isSaving: false });
|
|
398
399
|
const [isEnlarged, setIsEnlarged] = useState(false);
|
|
399
400
|
const [isLeftEnlarged, setIsLeftEnlarged] = useState(false);
|
|
400
401
|
const [relationsCache, setRelationsCache] = useState({});
|
|
@@ -422,7 +423,8 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
422
423
|
<div className="componets-and-library-accordion toolbox-card">
|
|
423
424
|
<InitNewElement
|
|
424
425
|
nodekeys={nodekeys}
|
|
425
|
-
|
|
426
|
+
setSavingState={setSavingState}
|
|
427
|
+
savingState={savingState}
|
|
426
428
|
/>
|
|
427
429
|
<Accordion>
|
|
428
430
|
<div className="card mt-1" accordiontitle="Components">
|
|
@@ -503,7 +505,12 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
503
505
|
<HistoryPanel />
|
|
504
506
|
<FontAwesomeIcon
|
|
505
507
|
icon={faSave}
|
|
506
|
-
className={isSaving ? "d-inline" : "d-none"}
|
|
508
|
+
className={savingState.isSaving ? "d-inline" : "d-none"}
|
|
509
|
+
/>
|
|
510
|
+
<FontAwesomeIcon
|
|
511
|
+
icon={faExclamationTriangle}
|
|
512
|
+
color="#ff0033"
|
|
513
|
+
className={savingState.error ? "d-inline" : "d-none"}
|
|
507
514
|
/>
|
|
508
515
|
<FontAwesomeIcon
|
|
509
516
|
icon={isEnlarged ? faCaretSquareRight : faCaretSquareLeft}
|
|
@@ -511,7 +518,13 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
511
518
|
onClick={() => setIsEnlarged(!isEnlarged)}
|
|
512
519
|
title={isEnlarged ? "Shrink" : "Enlarge"}
|
|
513
520
|
/>
|
|
514
|
-
|
|
521
|
+
<div
|
|
522
|
+
className={` ${
|
|
523
|
+
savingState.error ? "d-block" : "d-none"
|
|
524
|
+
} my-2 fw-bold`}
|
|
525
|
+
>
|
|
526
|
+
your work is not being saved
|
|
527
|
+
</div>
|
|
515
528
|
<SettingsPanel />
|
|
516
529
|
</div>
|
|
517
530
|
</div>
|
|
@@ -83,7 +83,7 @@ export /**
|
|
|
83
83
|
* @subcategory components
|
|
84
84
|
* @namespace
|
|
85
85
|
*/
|
|
86
|
-
const InitNewElement = ({ nodekeys,
|
|
86
|
+
const InitNewElement = ({ nodekeys, savingState, setSavingState }) => {
|
|
87
87
|
const [saveTimeout, setSaveTimeout] = useState(false);
|
|
88
88
|
const savedData = useRef(false);
|
|
89
89
|
const { actions, query, connectors } = useEditor((state, query) => {
|
|
@@ -103,7 +103,7 @@ const InitNewElement = ({ nodekeys, setIsSaving }) => {
|
|
|
103
103
|
}
|
|
104
104
|
if (isEqual(savedData.current, JSON.stringify(data.layout))) return;
|
|
105
105
|
savedData.current = JSON.stringify(data.layout);
|
|
106
|
-
|
|
106
|
+
setSavingState({ isSaving: true });
|
|
107
107
|
|
|
108
108
|
fetch(`/${urlroot}/savebuilder/${options.page_id || options.view_id}`, {
|
|
109
109
|
method: "POST", // or 'PUT'
|
|
@@ -112,9 +112,29 @@ const InitNewElement = ({ nodekeys, setIsSaving }) => {
|
|
|
112
112
|
"CSRF-Token": options.csrfToken,
|
|
113
113
|
},
|
|
114
114
|
body: JSON.stringify(data),
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
})
|
|
116
|
+
.then((response) => {
|
|
117
|
+
response.json().then((data) => {
|
|
118
|
+
if (typeof data?.error === "string") {
|
|
119
|
+
// don't log duplicates
|
|
120
|
+
if (!savingState.error)
|
|
121
|
+
window.notifyAlert({ type: "danger", text: data.error });
|
|
122
|
+
setSavingState({ isSaving: false, error: data.error });
|
|
123
|
+
} else setSavingState({ isSaving: false });
|
|
124
|
+
});
|
|
125
|
+
})
|
|
126
|
+
.catch((e) => {
|
|
127
|
+
const text = e || "Unable to save";
|
|
128
|
+
// don't log duplicates
|
|
129
|
+
if (savingState.error) setSavingState({ isSaving: false, error: text });
|
|
130
|
+
else {
|
|
131
|
+
window.notifyAlert({ type: "danger", text: text });
|
|
132
|
+
setSavingState({
|
|
133
|
+
isSaving: false,
|
|
134
|
+
error: text,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
118
138
|
};
|
|
119
139
|
const throttledSave = useThrottle(() => {
|
|
120
140
|
doSave(query);
|
|
@@ -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
|
}
|
|
@@ -424,7 +424,9 @@ const TabsSettings = () => {
|
|
|
424
424
|
/>
|
|
425
425
|
</td>
|
|
426
426
|
</tr>
|
|
427
|
-
{options.mode === "show" ||
|
|
427
|
+
{options.mode === "show" ||
|
|
428
|
+
options.mode === "edit" ||
|
|
429
|
+
options.mode === "filter" ? (
|
|
428
430
|
<Fragment>
|
|
429
431
|
<tr>
|
|
430
432
|
<th colSpan="2">Show if formula</th>
|
|
@@ -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>
|