@saltcorn/builder 0.9.4-beta.1 → 0.9.4-beta.11
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 +34 -19
- package/src/components/Toolbox.js +112 -163
- package/src/components/elements/BoxModelEditor.js +8 -8
- package/src/components/elements/Columns.js +4 -4
- package/src/components/elements/Container.js +26 -2
- package/src/components/elements/DropMenu.js +31 -2
- package/src/components/elements/Link.js +1 -0
- package/src/components/elements/RelationBadges.js +53 -44
- package/src/components/elements/Tabs.js +118 -40
- package/src/components/elements/View.js +86 -40
- package/src/components/elements/ViewLink.js +80 -34
- package/src/components/elements/utils.js +139 -76
- package/src/components/storage.js +6 -0
- package/tests/relations_finder.test.js +57 -92
- package/tests/test_data.js +0 -163
|
@@ -222,6 +222,7 @@ const ContainerSettings = () => {
|
|
|
222
222
|
display: node.data.props.display,
|
|
223
223
|
style: node.data.props.style,
|
|
224
224
|
imgResponsiveWidths: node.data.props.imgResponsiveWidths,
|
|
225
|
+
click_action: node.data.props.click_action,
|
|
225
226
|
}));
|
|
226
227
|
const {
|
|
227
228
|
actions: { setProp },
|
|
@@ -251,6 +252,7 @@ const ContainerSettings = () => {
|
|
|
251
252
|
overflow,
|
|
252
253
|
htmlElement,
|
|
253
254
|
imgResponsiveWidths,
|
|
255
|
+
click_action,
|
|
254
256
|
} = node;
|
|
255
257
|
const options = useContext(optionsCtx);
|
|
256
258
|
const { uploadedFiles } = useContext(previewCtx);
|
|
@@ -738,7 +740,7 @@ const ContainerSettings = () => {
|
|
|
738
740
|
)}
|
|
739
741
|
{["show", "edit", "filter"].includes(options.mode) && (
|
|
740
742
|
<tr>
|
|
741
|
-
<td>
|
|
743
|
+
<td colSpan={2}>
|
|
742
744
|
<input
|
|
743
745
|
type="text"
|
|
744
746
|
className="form-control text-to-display"
|
|
@@ -844,7 +846,29 @@ const ContainerSettings = () => {
|
|
|
844
846
|
onChange={setAProp("url")}
|
|
845
847
|
/>
|
|
846
848
|
</OrFormula>
|
|
847
|
-
|
|
849
|
+
{options.triggerActions ? (
|
|
850
|
+
<Fragment>
|
|
851
|
+
<label>Click action</label>
|
|
852
|
+
<select
|
|
853
|
+
value={click_action}
|
|
854
|
+
className="form-control form-select"
|
|
855
|
+
onChange={(e) => {
|
|
856
|
+
if (!e.target) return;
|
|
857
|
+
const value = e.target.value;
|
|
858
|
+
setProp((prop) => {
|
|
859
|
+
prop.click_action = value;
|
|
860
|
+
});
|
|
861
|
+
}}
|
|
862
|
+
>
|
|
863
|
+
<option value="">None</option>
|
|
864
|
+
{options.triggerActions.map((f, ix) => (
|
|
865
|
+
<option key={ix} value={f}>
|
|
866
|
+
{f}
|
|
867
|
+
</option>
|
|
868
|
+
))}
|
|
869
|
+
</select>
|
|
870
|
+
</Fragment>
|
|
871
|
+
) : null}
|
|
848
872
|
<label>Hover color</label>
|
|
849
873
|
<select
|
|
850
874
|
value={hoverColor}
|
|
@@ -8,7 +8,11 @@ import React, { Fragment, useState } from "react";
|
|
|
8
8
|
import { Element, useNode } from "@craftjs/core";
|
|
9
9
|
import { Column } from "./Column";
|
|
10
10
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
faCaretDown,
|
|
13
|
+
faCaretSquareLeft,
|
|
14
|
+
faCaretSquareRight,
|
|
15
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
12
16
|
import {
|
|
13
17
|
SettingsRow,
|
|
14
18
|
BlockSetting,
|
|
@@ -36,6 +40,7 @@ const DropMenu = ({
|
|
|
36
40
|
action_textcol,
|
|
37
41
|
block,
|
|
38
42
|
label,
|
|
43
|
+
menu_direction,
|
|
39
44
|
}) => {
|
|
40
45
|
const {
|
|
41
46
|
selected,
|
|
@@ -72,7 +77,7 @@ const DropMenu = ({
|
|
|
72
77
|
<div
|
|
73
78
|
className={`dropdown-menu dropmenu-dropdown ${
|
|
74
79
|
showDropdown ? "show" : ""
|
|
75
|
-
}`}
|
|
80
|
+
} ${menu_direction === "end" ? "dropdown-menu-end" : ""}`}
|
|
76
81
|
>
|
|
77
82
|
<div className="canvas d-flex flex-column">{children}</div>
|
|
78
83
|
</div>
|
|
@@ -96,6 +101,7 @@ const DropMenuSettings = () => {
|
|
|
96
101
|
action_bgcol: node.data.props.action_bgcol,
|
|
97
102
|
action_bordercol: node.data.props.action_bordercol,
|
|
98
103
|
action_textcol: node.data.props.action_textcol,
|
|
104
|
+
menu_direction: node.data.props.menu_direction,
|
|
99
105
|
}));
|
|
100
106
|
const {
|
|
101
107
|
actions: { setProp },
|
|
@@ -118,12 +124,34 @@ const DropMenuSettings = () => {
|
|
|
118
124
|
setProp={setProp}
|
|
119
125
|
keyPrefix="action_"
|
|
120
126
|
values={node}
|
|
127
|
+
allowRunOnLoad={false}
|
|
121
128
|
/>
|
|
122
129
|
<tr>
|
|
123
130
|
<td colSpan="2">
|
|
124
131
|
<BlockSetting block={block} setProp={setProp} />
|
|
125
132
|
</td>
|
|
126
133
|
</tr>
|
|
134
|
+
<SettingsRow
|
|
135
|
+
field={{
|
|
136
|
+
label: "Drop direction",
|
|
137
|
+
name: "menu_direction",
|
|
138
|
+
type: "btn_select",
|
|
139
|
+
options: [
|
|
140
|
+
{
|
|
141
|
+
value: "end",
|
|
142
|
+
title: "End",
|
|
143
|
+
label: <FontAwesomeIcon icon={faCaretSquareLeft} />,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
value: "start",
|
|
147
|
+
title: "Start",
|
|
148
|
+
label: <FontAwesomeIcon icon={faCaretSquareRight} />,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
}}
|
|
152
|
+
node={node}
|
|
153
|
+
setProp={setProp}
|
|
154
|
+
/>
|
|
127
155
|
</tbody>
|
|
128
156
|
</table>
|
|
129
157
|
);
|
|
@@ -151,6 +179,7 @@ DropMenu.craft = {
|
|
|
151
179
|
"action_bgcol",
|
|
152
180
|
"action_bordercol",
|
|
153
181
|
"action_textcol",
|
|
182
|
+
"menu_direction",
|
|
154
183
|
],
|
|
155
184
|
},
|
|
156
185
|
};
|
|
@@ -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,9 +180,12 @@ 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,
|
|
188
|
+
acc_init_opens: node.data.props.acc_init_opens,
|
|
184
189
|
}));
|
|
185
190
|
const {
|
|
186
191
|
actions: { setProp },
|
|
@@ -194,7 +199,11 @@ const TabsSettings = () => {
|
|
|
194
199
|
field,
|
|
195
200
|
serverRendered,
|
|
196
201
|
tabId,
|
|
202
|
+
showif,
|
|
203
|
+
setting_tab_n,
|
|
204
|
+
acc_init_opens,
|
|
197
205
|
} = node;
|
|
206
|
+
const use_setting_tab_n = setting_tab_n || 0;
|
|
198
207
|
const options = useContext(optionsCtx);
|
|
199
208
|
useEffect(() => {
|
|
200
209
|
if (field)
|
|
@@ -263,42 +272,6 @@ const TabsSettings = () => {
|
|
|
263
272
|
</Fragment>
|
|
264
273
|
) : (
|
|
265
274
|
<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
275
|
{tabsStyle === "Accordion" ? (
|
|
303
276
|
<tr>
|
|
304
277
|
<td colSpan="2">
|
|
@@ -401,6 +374,107 @@ const TabsSettings = () => {
|
|
|
401
374
|
</td>
|
|
402
375
|
</tr>
|
|
403
376
|
) : null}
|
|
377
|
+
<tr>
|
|
378
|
+
<th>
|
|
379
|
+
<label>Number of sections</label>
|
|
380
|
+
</th>
|
|
381
|
+
<td>
|
|
382
|
+
<input
|
|
383
|
+
type="number"
|
|
384
|
+
className="form-control"
|
|
385
|
+
value={ntabs}
|
|
386
|
+
step="1"
|
|
387
|
+
min="1"
|
|
388
|
+
max="20"
|
|
389
|
+
onChange={setAProp("ntabs")}
|
|
390
|
+
/>
|
|
391
|
+
</td>
|
|
392
|
+
</tr>
|
|
393
|
+
<tr>
|
|
394
|
+
<td colSpan={2}>
|
|
395
|
+
<ConfigField
|
|
396
|
+
field={{
|
|
397
|
+
name: "setting_tab_n",
|
|
398
|
+
label: "Tab number",
|
|
399
|
+
type: "btn_select",
|
|
400
|
+
options: ntimes(ntabs, (i) => ({
|
|
401
|
+
value: i,
|
|
402
|
+
title: `${i + 1}`,
|
|
403
|
+
label: `${i + 1}`,
|
|
404
|
+
})),
|
|
405
|
+
}}
|
|
406
|
+
node={node}
|
|
407
|
+
setProp={setProp}
|
|
408
|
+
props={node}
|
|
409
|
+
></ConfigField>
|
|
410
|
+
</td>
|
|
411
|
+
</tr>
|
|
412
|
+
<tr>
|
|
413
|
+
<th colSpan="2">Title</th>
|
|
414
|
+
</tr>
|
|
415
|
+
<tr>
|
|
416
|
+
<td colSpan={2}>
|
|
417
|
+
<input
|
|
418
|
+
type="text"
|
|
419
|
+
className="form-control text-to-display"
|
|
420
|
+
value={titles[use_setting_tab_n] || ""}
|
|
421
|
+
onChange={(e) => {
|
|
422
|
+
if (!e.target) return;
|
|
423
|
+
const value = e.target.value;
|
|
424
|
+
setProp((prop) => (prop.titles[use_setting_tab_n] = value));
|
|
425
|
+
}}
|
|
426
|
+
/>
|
|
427
|
+
</td>
|
|
428
|
+
</tr>
|
|
429
|
+
{options.mode === "show" ||
|
|
430
|
+
options.mode === "edit" ||
|
|
431
|
+
options.mode === "filter" ? (
|
|
432
|
+
<Fragment>
|
|
433
|
+
<tr>
|
|
434
|
+
<th colSpan="2">Show if formula</th>
|
|
435
|
+
</tr>
|
|
436
|
+
<tr>
|
|
437
|
+
<td colSpan={2}>
|
|
438
|
+
<input
|
|
439
|
+
type="text"
|
|
440
|
+
className="form-control text-to-display"
|
|
441
|
+
value={showif?.[use_setting_tab_n] || ""}
|
|
442
|
+
onChange={(e) => {
|
|
443
|
+
if (!e.target) return;
|
|
444
|
+
const value = e.target.value;
|
|
445
|
+
setProp((prop) => {
|
|
446
|
+
if (!prop.showif) prop.showif = [];
|
|
447
|
+
prop.showif[use_setting_tab_n] = value;
|
|
448
|
+
});
|
|
449
|
+
}}
|
|
450
|
+
/>
|
|
451
|
+
</td>
|
|
452
|
+
</tr>
|
|
453
|
+
</Fragment>
|
|
454
|
+
) : null}
|
|
455
|
+
{tabsStyle === "Accordion" ? (
|
|
456
|
+
<tr>
|
|
457
|
+
<td colSpan="2">
|
|
458
|
+
<div className="form-check">
|
|
459
|
+
<input
|
|
460
|
+
className="form-check-input"
|
|
461
|
+
name="block"
|
|
462
|
+
type="checkbox"
|
|
463
|
+
checked={acc_init_opens?.[use_setting_tab_n] || false}
|
|
464
|
+
onChange={(e) => {
|
|
465
|
+
if (!e.target) return;
|
|
466
|
+
const value = e.target.checked;
|
|
467
|
+
setProp((prop) => {
|
|
468
|
+
if (!prop.acc_init_opens) prop.acc_init_opens = [];
|
|
469
|
+
prop.acc_init_opens[use_setting_tab_n] = value;
|
|
470
|
+
});
|
|
471
|
+
}}
|
|
472
|
+
/>
|
|
473
|
+
<label className="form-check-label">Initially open</label>
|
|
474
|
+
</div>
|
|
475
|
+
</td>
|
|
476
|
+
</tr>
|
|
477
|
+
) : null}
|
|
404
478
|
</Fragment>
|
|
405
479
|
)}
|
|
406
480
|
</tbody>
|
|
@@ -414,6 +488,8 @@ const TabsSettings = () => {
|
|
|
414
488
|
Tabs.craft = {
|
|
415
489
|
props: {
|
|
416
490
|
titles: ["Tab1", "Tab2"],
|
|
491
|
+
showif: [],
|
|
492
|
+
acc_init_opens: [],
|
|
417
493
|
ntabs: 2,
|
|
418
494
|
tabsStyle: "Tabs",
|
|
419
495
|
independent: false,
|
|
@@ -421,8 +497,10 @@ Tabs.craft = {
|
|
|
421
497
|
deeplink: true,
|
|
422
498
|
disable_inactive: false,
|
|
423
499
|
serverRendered: false,
|
|
500
|
+
setting_tab_n: 0,
|
|
424
501
|
tabId: "",
|
|
425
502
|
},
|
|
503
|
+
defaultProps: { setting_tab_n: 0, ntabs: 2 },
|
|
426
504
|
displayName: "Tabs",
|
|
427
505
|
related: {
|
|
428
506
|
settings: TabsSettings,
|