@saltcorn/builder 0.9.4-beta.8 → 0.9.4

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
  }
@@ -185,6 +185,7 @@ const TabsSettings = () => {
185
185
  titles: node.data.props.titles,
186
186
  showif: node.data.props.showif,
187
187
  field: node.data.props.field,
188
+ acc_init_opens: node.data.props.acc_init_opens,
188
189
  }));
189
190
  const {
190
191
  actions: { setProp },
@@ -200,6 +201,7 @@ const TabsSettings = () => {
200
201
  tabId,
201
202
  showif,
202
203
  setting_tab_n,
204
+ acc_init_opens,
203
205
  } = node;
204
206
  const use_setting_tab_n = setting_tab_n || 0;
205
207
  const options = useContext(optionsCtx);
@@ -424,7 +426,9 @@ const TabsSettings = () => {
424
426
  />
425
427
  </td>
426
428
  </tr>
427
- {options.mode === "show" || options.mode === "edit" ? (
429
+ {options.mode === "show" ||
430
+ options.mode === "edit" ||
431
+ options.mode === "filter" ? (
428
432
  <Fragment>
429
433
  <tr>
430
434
  <th colSpan="2">Show if formula</th>
@@ -448,6 +452,29 @@ const TabsSettings = () => {
448
452
  </tr>
449
453
  </Fragment>
450
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}
451
478
  </Fragment>
452
479
  )}
453
480
  </tbody>
@@ -462,6 +489,7 @@ Tabs.craft = {
462
489
  props: {
463
490
  titles: ["Tab1", "Tab2"],
464
491
  showif: [],
492
+ acc_init_opens: [],
465
493
  ntabs: 2,
466
494
  tabsStyle: "Tabs",
467
495
  independent: false,
@@ -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"