@saltcorn/builder 0.9.4-beta.9 → 0.9.5-beta.0

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
+ };
@@ -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);
@@ -450,6 +452,29 @@ const TabsSettings = () => {
450
452
  </tr>
451
453
  </Fragment>
452
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}
453
478
  </Fragment>
454
479
  )}
455
480
  </tbody>
@@ -464,6 +489,7 @@ Tabs.craft = {
464
489
  props: {
465
490
  titles: ["Tab1", "Tab2"],
466
491
  showif: [],
492
+ acc_init_opens: [],
467
493
  ntabs: 2,
468
494
  tabsStyle: "Tabs",
469
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"
@@ -9,6 +9,7 @@ import { useNode } from "@craftjs/core";
9
9
  import optionsCtx from "../context";
10
10
  import previewCtx from "../preview_context";
11
11
  import relationsCtx from "../relations_context";
12
+ import Select from "react-select";
12
13
 
13
14
  import {
14
15
  fetchViewPreview,
@@ -151,7 +152,7 @@ const ViewSettings = () => {
151
152
  if (rest.startsWith(".")) viewname = prefix;
152
153
  else viewname = rest;
153
154
  }
154
- if (viewname.includes(".")) viewname = viewname.split(".")[0];
155
+ if (viewname && viewname.includes(".")) viewname = viewname.split(".")[0];
155
156
 
156
157
  if (
157
158
  finder &&
@@ -176,7 +177,7 @@ const ViewSettings = () => {
176
177
  : [undefined, undefined];
177
178
  let safeRelation = null;
178
179
  const subView = views.find((view) => view.name === viewname);
179
- if (relation) {
180
+ if (relation && subView) {
180
181
  const subTbl = tables.find((tbl) => tbl.id === subView.table_id);
181
182
  safeRelation = new Relation(
182
183
  relation,
@@ -186,7 +187,7 @@ const ViewSettings = () => {
186
187
  }
187
188
  if (
188
189
  options.mode !== "filter" &&
189
- subView.table_id &&
190
+ subView?.table_id &&
190
191
  !safeRelation &&
191
192
  !hasLegacyRelation &&
192
193
  relationsData?.relations.length > 0
@@ -199,8 +200,8 @@ const ViewSettings = () => {
199
200
  const helpContext = { view_name: viewname };
200
201
  if (options.tableName) helpContext.srcTable = options.tableName;
201
202
  const set_view_name = (e) => {
202
- if (e.target) {
203
- const target_value = e.target.value;
203
+ if (e?.target?.value || e?.value) {
204
+ const target_value = e.target?.value || e.value;
204
205
  if (target_value !== viewname) {
205
206
  if (options.mode === "filter") {
206
207
  setProp((prop) => {
@@ -237,25 +238,27 @@ const ViewSettings = () => {
237
238
  }
238
239
  }
239
240
  };
240
-
241
+ const viewOptions = options.views.map(({ name, label }) => ({
242
+ label,
243
+ value: name,
244
+ }));
245
+ const selectedView = viewOptions.find((v) => v.value === viewname);
241
246
  return (
242
247
  <div>
243
248
  {relationsData ? (
244
249
  <Fragment>
245
250
  <div>
246
251
  <label>View to {options.mode === "show" ? "embed" : "show"}</label>
247
- <select
248
- value={viewname}
249
- className="form-control form-select"
250
- onChange={set_view_name}
251
- onBlur={set_view_name}
252
- >
253
- {options.views.map((v, ix) => (
254
- <option key={ix} value={v.name}>
255
- {v.label}
256
- </option>
257
- ))}
258
- </select>
252
+ {options.inJestTestingMode ? null : (
253
+ <Select
254
+ options={viewOptions}
255
+ value={selectedView}
256
+ onChange={set_view_name}
257
+ onBlur={set_view_name}
258
+ menuPortalTarget={document.body}
259
+ styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
260
+ ></Select>
261
+ )}
259
262
  </div>
260
263
  {options.mode !== "filter" && (
261
264
  <div>
@@ -287,18 +290,20 @@ const ViewSettings = () => {
287
290
  ) : (
288
291
  <div>
289
292
  <label>View to {options.mode === "show" ? "embed" : "show"}</label>
290
- <select
291
- value={view}
292
- className="form-control form-select"
293
- onChange={setAProp("view")}
294
- onBlur={setAProp("view")}
295
- >
296
- {options.views.map((f, ix) => (
297
- <option key={ix} value={f.name}>
298
- {f.label || f.name}
299
- </option>
300
- ))}
301
- </select>
293
+ {options.inJestTestingMode ? null : (
294
+ <Select
295
+ options={viewOptions}
296
+ value={selectedView}
297
+ onChange={(e) => {
298
+ const target_value = e?.target?.value || e?.value;
299
+ setProp((prop) => {
300
+ prop.view = target_value;
301
+ });
302
+ }}
303
+ menuPortalTarget={document.body}
304
+ styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
305
+ ></Select>
306
+ )}
302
307
  </div>
303
308
  )}
304
309
  {options.mode !== "edit" && (
@@ -339,7 +344,7 @@ const ViewSettings = () => {
339
344
  )}
340
345
  </Fragment>
341
346
  )}
342
- {(state === "shared" || options.mode === "page") && (
347
+ {
343
348
  <Fragment>
344
349
  <label>
345
350
  Extra state Formula
@@ -357,7 +362,7 @@ const ViewSettings = () => {
357
362
  </small>
358
363
  ) : null}
359
364
  </Fragment>
360
- )}
365
+ }
361
366
  {view ? (
362
367
  <a
363
368
  className="d-block mt-2"
@@ -24,6 +24,7 @@ import {
24
24
 
25
25
  import { RelationBadges } from "./RelationBadges";
26
26
  import { RelationOnDemandPicker } from "./RelationOnDemandPicker";
27
+ import Select from "react-select";
27
28
 
28
29
  import {
29
30
  RelationsFinder,
@@ -201,8 +202,8 @@ const ViewLinkSettings = () => {
201
202
  });
202
203
  }
203
204
  const set_view_name = (e) => {
204
- if (e.target) {
205
- const target_value = e.target.value;
205
+ if (e?.target?.value || e?.value) {
206
+ const target_value = e.target?.value || e.value;
206
207
  if (target_value !== use_view_name) {
207
208
  const newRelations = finder.findRelations(
208
209
  tableName,
@@ -236,6 +237,11 @@ const ViewLinkSettings = () => {
236
237
  };
237
238
  const helpContext = { view_name: use_view_name };
238
239
  if (tableName) helpContext.srcTable = tableName;
240
+ const viewOptions = options.views.map(({ name, label }) => ({
241
+ label,
242
+ value: name,
243
+ }));
244
+ const selectedView = viewOptions.find((v) => v.value === use_view_name);
239
245
  return (
240
246
  <div>
241
247
  <table className="w-100">
@@ -243,18 +249,18 @@ const ViewLinkSettings = () => {
243
249
  <tr>
244
250
  <td colSpan="2">
245
251
  <label>View to link to</label>
246
- <select
247
- value={use_view_name}
248
- className="form-control form-select"
249
- onChange={set_view_name}
250
- onBlur={set_view_name}
251
- >
252
- {options.views.map((f, ix) => (
253
- <option key={ix} value={f.name}>
254
- {f.label}
255
- </option>
256
- ))}
257
- </select>
252
+ {options.inJestTestingMode ? null : (
253
+ <Select
254
+ options={viewOptions}
255
+ value={selectedView}
256
+ onChange={set_view_name}
257
+ onBlur={set_view_name}
258
+ menuPortalTarget={document.body}
259
+ styles={{
260
+ menuPortal: (base) => ({ ...base, zIndex: 19999 }),
261
+ }}
262
+ ></Select>
263
+ )}
258
264
  </td>
259
265
  </tr>
260
266
  <tr>
@@ -323,6 +329,7 @@ const ViewLinkSettings = () => {
323
329
  values={node}
324
330
  linkFirst={true}
325
331
  linkIsBlank={true}
332
+ allowRunOnLoad={false}
326
333
  />
327
334
  </tbody>
328
335
  </table>