@saltcorn/builder 0.9.4-beta.2 → 0.9.4-beta.21

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.
@@ -376,6 +376,7 @@ const JoinFieldSettings = () => {
376
376
  textStyle,
377
377
  configuration,
378
378
  fieldview,
379
+ click_to_edit,
379
380
  node_id,
380
381
  } = useNode((node) => ({
381
382
  name: node.data.props.name,
@@ -383,7 +384,7 @@ const JoinFieldSettings = () => {
383
384
  textStyle: node.data.props.textStyle,
384
385
  fieldview: node.data.props.fieldview,
385
386
  configuration: node.data.props.configuration,
386
-
387
+ click_to_edit: node.data.props.click_to_edit,
387
388
  node_id: node.id,
388
389
  }));
389
390
  const options = useContext(optionsCtx);
@@ -493,6 +494,28 @@ const JoinFieldSettings = () => {
493
494
  </td>
494
495
  </tr>
495
496
  )}
497
+ {options.mode === "show" || options.mode === "list" ? (
498
+ <tr>
499
+ <td></td>
500
+ <td>
501
+ <div className="form-check">
502
+ <input
503
+ className="form-check-input"
504
+ name="inline"
505
+ type="checkbox"
506
+ checked={click_to_edit}
507
+ onChange={(e) => {
508
+ if (e && e.target) {
509
+ const target_value = e.target.checked;
510
+ setProp((prop) => (prop.click_to_edit = target_value));
511
+ }
512
+ }}
513
+ />
514
+ <label className="form-check-label">Click to edit?</label>
515
+ </div>
516
+ </td>
517
+ </tr>
518
+ ) : null}
496
519
  <tr>
497
520
  <td></td>
498
521
  <td>
@@ -527,6 +550,7 @@ JoinField.craft = {
527
550
  { name: "name", segment_name: "join_field", column_name: "join_field" },
528
551
  "fieldview",
529
552
  "textStyle",
553
+ "click_to_edit",
530
554
  "block",
531
555
  { name: "configuration", default: {} },
532
556
  ],
@@ -281,6 +281,7 @@ const LinkSettings = () => {
281
281
  values={node}
282
282
  linkFirst={true}
283
283
  linkIsBlank={true}
284
+ allowRunOnLoad={false}
284
285
  />
285
286
  </tbody>
286
287
  </table>
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/elements/Column
4
+ * @subcategory components / elements
5
+ */
6
+
7
+ import React, {
8
+ useContext,
9
+ Fragment,
10
+ useRef,
11
+ useEffect,
12
+ useState,
13
+ } from "react";
14
+
15
+ import { Element, useNode, useEditor } from "@craftjs/core";
16
+ import { setAPropGen, SettingsFromFields } from "./utils";
17
+ import { Column } from "./Column";
18
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
19
+ import { faArrowUp, faArrowDown } from "@fortawesome/free-solid-svg-icons";
20
+ import optionsCtx from "../context";
21
+
22
+ export /**
23
+ *
24
+ * @param {object} props
25
+ * @param {string} props.children
26
+ * @param {*} props.align
27
+ * @returns {div}
28
+ * @category saltcorn-builder
29
+ * @subcategory components
30
+ * @namespace
31
+ */
32
+ const ListColumn = ({
33
+ alignment,
34
+ colIndex,
35
+ contents,
36
+ header_label,
37
+ showif,
38
+ col_width,
39
+ col_width_units,
40
+ }) => {
41
+ const {
42
+ selected,
43
+ id,
44
+ connectors: { connect, drag },
45
+ } = useNode((node) => ({ selected: node.events.selected }));
46
+ const { actions, query, isActve } = useEditor((state) => ({}));
47
+ const options = useContext(optionsCtx);
48
+
49
+ const {
50
+ data: { parent },
51
+ } = query.node(id).get();
52
+ const siblings = query.node(parent).childNodes();
53
+ const nChildren = siblings.length;
54
+ const childIx = siblings.findIndex((sib) => sib === id);
55
+
56
+ const moveDown = () => {
57
+ const {
58
+ data: { parent },
59
+ } = query.node(id).get();
60
+ actions.move(id, parent, childIx + 2);
61
+ };
62
+ const moveUp = () => {
63
+ const {
64
+ data: { parent },
65
+ } = query.node(id).get();
66
+ actions.move(id, parent, childIx - 1);
67
+ };
68
+ return (
69
+ <div
70
+ className={`${
71
+ selected ? "selected-node" : ""
72
+ } d-flex w-100 list-column-outer`}
73
+ ref={(dom) => connect(drag(dom))}
74
+ >
75
+ <div className={`list-column flex-50 p-2`}>
76
+ <div className="d-flex justify-content-between h-100">
77
+ <div className="">
78
+ Column {childIx}
79
+ {header_label ? `: ${header_label}` : ""}
80
+ <br />
81
+ {showif ? (
82
+ <span className="badge bg-secondary me-2">showif</span>
83
+ ) : (
84
+ ""
85
+ )}
86
+ {alignment && alignment !== "Default" ? (
87
+ <span className="badge bg-secondary me-2">Align {alignment}</span>
88
+ ) : (
89
+ ""
90
+ )}
91
+ {col_width ? (
92
+ <span className="badge bg-secondary me-2">
93
+ {col_width}
94
+ {col_width_units}
95
+ </span>
96
+ ) : (
97
+ ""
98
+ )}
99
+ </div>
100
+ <div className="d-flex flex-column h-100 justify-content-between">
101
+ {childIx !== null && childIx > 0 ? (
102
+ <FontAwesomeIcon icon={faArrowUp} onClick={moveUp} />
103
+ ) : (
104
+ <span></span>
105
+ )}
106
+ {childIx !== null && childIx < nChildren - 1 ? (
107
+ <FontAwesomeIcon icon={faArrowDown} onClick={moveDown} />
108
+ ) : (
109
+ <span></span>
110
+ )}
111
+ </div>
112
+ </div>
113
+ </div>
114
+ <Element
115
+ canvas
116
+ id={`listcol`}
117
+ is={Column}
118
+ singleOccupancy={!options.allowMultipleElementsPerColumn}
119
+ >
120
+ {contents}
121
+ </Element>
122
+ </div>
123
+ );
124
+ };
125
+
126
+ const fields = [
127
+ {
128
+ name: "header_label",
129
+ label: "Header label",
130
+ type: "String",
131
+ },
132
+ {
133
+ name: "showif",
134
+ label: "Show if true",
135
+ sublabel: "Formula. Leave blank to always show",
136
+ class: "validate-expression",
137
+ type: "String",
138
+ required: false,
139
+ },
140
+ {
141
+ name: "col_width",
142
+ label: "Column width",
143
+ type: "Integer",
144
+ attributes: { asideNext: true },
145
+ },
146
+ {
147
+ name: "col_width_units",
148
+ label: "Units",
149
+ type: "String",
150
+ required: true,
151
+ attributes: {
152
+ inline: true,
153
+ options: ["px", "%", "vw", "em", "rem"],
154
+ },
155
+ },
156
+ {
157
+ name: "alignment",
158
+ label: "Alignment",
159
+ input_type: "select",
160
+ options: ["Default", "Left", "Center", "Right"],
161
+ },
162
+ ];
163
+ ListColumn.craft = {
164
+ displayName: "ListColumn",
165
+ props: {},
166
+ rules: {
167
+ canDrag: () => true,
168
+ },
169
+ related: {
170
+ settings: SettingsFromFields(fields, {
171
+ additionalFieldsOptionKey: "additionalColumnFields",
172
+ }),
173
+ segment_type: "list_column",
174
+ hasContents: true,
175
+ colFields: fields,
176
+ },
177
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/elements/ListColumns
4
+ * @subcategory components / elements
5
+ */
6
+
7
+ import React, { useContext, Fragment } from "react";
8
+
9
+ import { Element, useNode } from "@craftjs/core";
10
+ import optionsCtx from "../context";
11
+
12
+ export /**
13
+ *
14
+ * @param {object} props
15
+ * @param {string} props.children
16
+ * @param {*} props.align
17
+ * @returns {div}
18
+ * @category saltcorn-builder
19
+ * @subcategory components
20
+ * @namespace
21
+ */
22
+ const ListColumns = ({ children, align }) => {
23
+ const {
24
+ selected,
25
+ id,
26
+ connectors: { connect, drag },
27
+ } = useNode((node) => ({ selected: node.events.selected }));
28
+ return (
29
+ <div className={selected ? "selected-node" : ""}>
30
+ <div className={` ${id === "ROOT" ? "root-canvas" : ""}`}>{children}</div>
31
+ </div>
32
+ );
33
+ };
34
+
35
+ export /**
36
+ * @returns {div}
37
+ * @category saltcorn-builder
38
+ * @subcategory components
39
+ * @namespace
40
+ */
41
+ const ListColumnsSettings = () => {
42
+ useNode((node) => ({}));
43
+ return <div></div>;
44
+ };
45
+
46
+ /**
47
+ * @type {object}
48
+ */
49
+ ListColumns.craft = {
50
+ displayName: "ListColumns",
51
+ props: {},
52
+ rules: {
53
+ canDrag: () => false,
54
+ canDrop: () => false,
55
+ canMoveIn: (incoming) => {
56
+ return incoming?.data?.displayName === "ListColumn";
57
+ },
58
+ },
59
+ related: {
60
+ settings: ListColumnsSettings,
61
+ },
62
+ };
@@ -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 = (parsed, parentTbl) => {
5
- const result = [];
6
- let currentCfg = null;
7
- for (const { type, table, key } of parsed) {
8
- if (type === "Inbound") {
9
- if (currentCfg) result.push(currentCfg);
10
- currentCfg = { up: key, table };
11
- } else {
12
- if (!currentCfg && key) result.push({ down: key, table: parentTbl });
13
- else if (currentCfg) {
14
- currentCfg.down = key;
15
- result.push(currentCfg);
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
- if (
21
- currentCfg &&
22
- !result.find(
23
- ({ down, table, up }) =>
24
- down === currentCfg.down &&
25
- table === currentCfg.table &&
26
- up === currentCfg.up
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.push(currentCfg);
30
- return result;
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
- {parsed.length > 0
74
- ? buildBadgeCfgs(parsed, parentTbl).map(buildBadge)
75
- : buildBadge({ table: "invalid relation" }, 0)}
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
- const parsed = relationHelpers.parseLegacyRelation(prefix, rest, parentTbl);
82
- if (parsed.length === 0)
83
- return buildBadge({ table: "invalid relation" }, 0);
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: parsed[0].table }, 0)}
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(parsed, parentTbl).map(buildBadge)}
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
- const [showTab, setShowTab] = useState(
40
- tabsStyle === "Accordion" && startClosed ? false : 0
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,
@@ -179,9 +179,11 @@ const TextSettings = () => {
179
179
  } = node;
180
180
  const { mode, fields } = useContext(optionsCtx);
181
181
  const setAProp = setAPropGen(setProp);
182
+ const allowFormula = mode === "show" || mode === "list";
183
+
182
184
  return (
183
185
  <div>
184
- {mode === "show" && (
186
+ {allowFormula && (
185
187
  <div className="form-check">
186
188
  <input
187
189
  type="checkbox"
@@ -197,7 +199,7 @@ const TextSettings = () => {
197
199
  </div>
198
200
  )}
199
201
  <label>Text to display</label>
200
- {mode === "show" && isFormula.text ? (
202
+ {allowFormula && isFormula.text ? (
201
203
  <input
202
204
  type="text"
203
205
  className="text-to-display form-control"