@saltcorn/builder 0.9.4-beta.19 → 0.9.4-beta.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/builder",
3
- "version": "0.9.4-beta.19",
3
+ "version": "0.9.4-beta.20",
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,7 +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.19",
23
+ "@saltcorn/common-code": "0.9.4-beta.20",
24
24
  "saltcorn-craft-layers-noeye": "0.1.0-beta.22",
25
25
  "@fonticonpicker/react-fonticonpicker": "1.2.0",
26
26
  "@fortawesome/fontawesome-svg-core": "1.2.34",
@@ -490,7 +490,7 @@ const AggregationElem = ({ connectors, child_field_list, agg_field_opts }) => (
490
490
  >
491
491
  <Aggregation
492
492
  agg_relation={child_field_list[0]}
493
- agg_field={headOr(agg_field_opts[child_field_list[0]], "")}
493
+ agg_field={headOr(agg_field_opts[child_field_list[0]], "")?.name}
494
494
  stat={"Count"}
495
495
  textStyle={""}
496
496
  aggwhere={""}
@@ -628,7 +628,13 @@ export /**
628
628
  const ToolboxFilter = ({ expanded }) => {
629
629
  const { connectors, query } = useEditor();
630
630
  const options = useContext(optionsCtx);
631
- const { fields, views, field_view_options } = options;
631
+ const {
632
+ fields,
633
+ views,
634
+ field_view_options,
635
+ child_field_list,
636
+ agg_field_opts,
637
+ } = options;
632
638
  return chunkToolBox(
633
639
  [
634
640
  <TextElem connectors={connectors} />,
@@ -643,6 +649,11 @@ const ToolboxFilter = ({ expanded }) => {
643
649
  <ToggleFilterElem connectors={connectors} fields={fields} />,
644
650
  <SearchElem connectors={connectors} />,
645
651
  <ActionElem connectors={connectors} options={options} />,
652
+ <AggregationElem
653
+ connectors={connectors}
654
+ child_field_list={child_field_list}
655
+ agg_field_opts={agg_field_opts}
656
+ />,
646
657
  <ContainerElem connectors={connectors} />,
647
658
  <CardElem connectors={connectors} />,
648
659
  <TabsElem connectors={connectors} />,
@@ -110,6 +110,7 @@ const ActionSettings = () => {
110
110
  step_only_ifs: node.data.props.step_only_ifs,
111
111
  step_action_names: node.data.props.step_action_names,
112
112
  setting_action_n: node.data.props.setting_action_n,
113
+ spinner: node.data.props.spinner,
113
114
  }));
114
115
  const {
115
116
  actions: { setProp },
@@ -127,6 +128,7 @@ const ActionSettings = () => {
127
128
  setting_action_n,
128
129
  step_only_ifs,
129
130
  step_action_names,
131
+ spinner,
130
132
  } = node;
131
133
  const options = useContext(optionsCtx);
132
134
  const getCfgFields = (fv) => (options.actionConfigForms || {})[fv];
@@ -278,6 +280,16 @@ const ActionSettings = () => {
278
280
  />
279
281
  <label className="form-check-label">User confirmation?</label>
280
282
  </div>
283
+ <div className="form-check">
284
+ <input
285
+ className="form-check-input"
286
+ name="block"
287
+ type="checkbox"
288
+ checked={spinner}
289
+ onChange={setAProp("spinner", { checked: true })}
290
+ />
291
+ <label className="form-check-label">Spinner on click</label>
292
+ </div>
281
293
  {action_style !== "on_page_load" ? (
282
294
  <BlockSetting block={block} setProp={setProp} />
283
295
  ) : null}
@@ -4,7 +4,7 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { useContext } from "react";
7
+ import React, { useContext, useState, useEffect, Fragment } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
9
  import optionsCtx from "../context";
10
10
  import {
@@ -13,6 +13,7 @@ import {
13
13
  TextStyleRow,
14
14
  setAPropGen,
15
15
  buildOptions,
16
+ ConfigForm,
16
17
  } from "./utils";
17
18
 
18
19
  export /**
@@ -58,125 +59,209 @@ const AggregationSettings = () => {
58
59
  aggwhere,
59
60
  block,
60
61
  textStyle,
62
+ agg_fieldview,
63
+ configuration,
61
64
  } = useNode((node) => ({
62
65
  agg_relation: node.data.props.agg_relation,
63
66
  agg_field: node.data.props.agg_field,
64
67
  aggwhere: node.data.props.aggwhere,
65
68
  stat: node.data.props.stat,
66
69
  block: node.data.props.block,
70
+ agg_fieldview: node.data.props.agg_fieldview,
71
+ configuration: node.data.props.configuration,
67
72
  textStyle: node.data.props.textStyle,
68
73
  }));
69
74
  const options = useContext(optionsCtx);
70
75
  const setAProp = setAPropGen(setProp);
71
76
 
77
+ const targetField = options.agg_field_opts[agg_relation]?.find?.(
78
+ (f) => f.name === agg_field
79
+ );
80
+ const targetFieldType = targetField?.ftype;
81
+ const outcomeType =
82
+ stat === "Count" || stat === "CountUnique"
83
+ ? "Integer"
84
+ : stat === "Array_Agg"
85
+ ? "Array"
86
+ : targetFieldType;
87
+ const fvs = options.agg_fieldview_options[outcomeType];
88
+
89
+ const [fetchedCfgFields, setFetchedCfgFields] = useState([]);
90
+ const cfgFields = fetchedCfgFields;
91
+ useEffect(() => {
92
+ fetch(
93
+ `/field/fieldviewcfgform/${
94
+ targetField?.table_name || options.tableName
95
+ }?accept=json`,
96
+ {
97
+ method: "POST",
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ "CSRF-Token": options.csrfToken,
101
+ "X-Requested-With": "XMLHttpRequest",
102
+ },
103
+ body: JSON.stringify({
104
+ agg_outcome_type: outcomeType,
105
+ agg_fieldview,
106
+ agg_field: targetField?.name,
107
+ }),
108
+ }
109
+ )
110
+ .then(function (response) {
111
+ if (response.status < 399) return response.json();
112
+ else return [];
113
+ })
114
+ .then(setFetchedCfgFields);
115
+ }, [outcomeType, agg_fieldview]);
116
+
72
117
  return (
73
- <table>
74
- <tbody>
75
- <tr>
76
- <td>
77
- <label>Relation</label>
78
- </td>
79
- <td>
80
- <select
81
- className="form-control form-select"
82
- value={agg_relation}
83
- onChange={(e) => {
84
- if (!e.target) return;
85
- const value = e.target.value;
86
- setProp((prop) => {
87
- prop.agg_relation = value;
88
- const fs = options.agg_field_opts[value];
89
- if (fs && fs.length > 0) prop.agg_field = fs[0];
90
- });
91
- }}
92
- >
93
- {options.child_field_list.map((f, ix) => (
94
- <option key={ix} value={f}>
95
- {f}
96
- </option>
97
- ))}
98
- </select>
99
- </td>
100
- </tr>
101
- <tr>
102
- <td>
103
- <label>Child table field</label>
104
- </td>
105
- <td>
106
- <select
107
- className="form-control form-select"
108
- value={agg_field}
109
- onChange={setAProp("agg_field")}
110
- >
111
- {(options.agg_field_opts[agg_relation] || []).map((f, ix) => (
112
- <option key={ix} value={f}>
113
- {f}
114
- </option>
115
- ))}
116
- </select>
117
- </td>
118
- </tr>
119
- <tr>
120
- <td>
121
- <label>Statistic</label>
122
- </td>
123
- <td>
124
- <select
125
- value={stat}
126
- className="form-control form-select"
127
- onChange={setAProp("stat")}
128
- onBlur={setAProp("stat")}
129
- >
130
- {buildOptions(
131
- [
132
- "Count",
133
- "CountUnique",
134
- "Avg",
135
- "Sum",
136
- "Max",
137
- "Min",
138
- "Array_Agg",
139
- ],
140
- { valAttr: true }
141
- )}
142
- {options.fields
143
- .filter((f) => f.type === "Date" || f.type.name === "Date")
144
- .map((f, ix) => (
145
- <option key={ix} value={`Latest ${f.name}`}>
146
- Latest {f.name}
118
+ <Fragment>
119
+ <table>
120
+ <tbody>
121
+ {options.mode === "filter" ? null : (
122
+ <tr>
123
+ <td>
124
+ <label>Relation</label>
125
+ </td>
126
+ <td>
127
+ <select
128
+ className="form-control form-select"
129
+ value={agg_relation}
130
+ onChange={(e) => {
131
+ if (!e.target) return;
132
+ const value = e.target.value;
133
+ setProp((prop) => {
134
+ prop.agg_relation = value;
135
+ const fs = options.agg_field_opts[value];
136
+ if (fs && fs.length > 0) prop.agg_field = fs[0]?.name;
137
+ });
138
+ }}
139
+ >
140
+ {options.child_field_list.map((f, ix) => (
141
+ <option key={ix} value={f}>
142
+ {f}
143
+ </option>
144
+ ))}
145
+ </select>
146
+ </td>
147
+ </tr>
148
+ )}
149
+ <tr>
150
+ <td>
151
+ <label>
152
+ {options.mode === "filter" ? "Field" : "Child table field"}
153
+ </label>
154
+ </td>
155
+ <td>
156
+ <select
157
+ className="form-control form-select"
158
+ value={agg_field}
159
+ onChange={setAProp("agg_field")}
160
+ >
161
+ {(options.agg_field_opts[agg_relation] || []).map((f, ix) => (
162
+ <option key={ix} value={f.name}>
163
+ {f.label}
147
164
  </option>
148
165
  ))}
149
- {options.fields
150
- .filter((f) => f.type === "Date" || f.type.name === "Date")
151
- .map((f, ix) => (
152
- <option key={ix} value={`Earliest ${f.name}`}>
153
- Earliest {f.name}
154
- </option>
155
- ))}
156
- </select>
157
- </td>
158
- </tr>
159
- <tr>
160
- <td>
161
- <label>Where</label>
162
- </td>
163
- <td>
164
- <input
165
- type="text"
166
- className="form-control"
167
- value={aggwhere}
168
- onChange={setAProp("aggwhere")}
169
- />
170
- </td>
171
- </tr>
172
- <TextStyleRow textStyle={textStyle} setProp={setProp} />
173
- <tr>
174
- <td colSpan="2">
175
- <BlockSetting block={block} setProp={setProp} />
176
- </td>
177
- </tr>
178
- </tbody>
179
- </table>
166
+ </select>
167
+ </td>
168
+ </tr>
169
+ <tr>
170
+ <td>
171
+ <label>Statistic</label>
172
+ </td>
173
+ <td>
174
+ <select
175
+ value={stat}
176
+ className="form-control form-select"
177
+ onChange={setAProp("stat")}
178
+ onBlur={setAProp("stat")}
179
+ >
180
+ {buildOptions(
181
+ [
182
+ "Count",
183
+ "CountUnique",
184
+ "Avg",
185
+ "Sum",
186
+ "Max",
187
+ "Min",
188
+ "Array_Agg",
189
+ ],
190
+ { valAttr: true }
191
+ )}
192
+ {options.fields
193
+ .filter((f) => f.type === "Date" || f.type.name === "Date")
194
+ .map((f, ix) => (
195
+ <option key={ix} value={`Latest ${f.name}`}>
196
+ Latest {f.name}
197
+ </option>
198
+ ))}
199
+ {options.fields
200
+ .filter((f) => f.type === "Date" || f.type.name === "Date")
201
+ .map((f, ix) => (
202
+ <option key={ix} value={`Earliest ${f.name}`}>
203
+ Earliest {f.name}
204
+ </option>
205
+ ))}
206
+ </select>
207
+ </td>
208
+ </tr>
209
+ <tr>
210
+ <td>
211
+ <label>Where</label>
212
+ </td>
213
+ <td>
214
+ <input
215
+ type="text"
216
+ className="form-control"
217
+ value={aggwhere}
218
+ onChange={setAProp("aggwhere")}
219
+ />
220
+ </td>
221
+ </tr>
222
+ {fvs && (
223
+ <tr>
224
+ <td>
225
+ <label>Field view</label>
226
+ </td>
227
+
228
+ <td>
229
+ <select
230
+ value={agg_fieldview}
231
+ className="form-control form-select"
232
+ onChange={(e) => {
233
+ if (!e.target) return;
234
+ const value = e.target.value;
235
+ setProp((prop) => (prop.agg_fieldview = value));
236
+ //refetchPreview({ fieldview: value });
237
+ }}
238
+ >
239
+ {(fvs || []).map((fvnm, ix) => (
240
+ <option key={ix} value={fvnm}>
241
+ {fvnm}
242
+ </option>
243
+ ))}
244
+ </select>
245
+ </td>
246
+ </tr>
247
+ )}
248
+
249
+ <TextStyleRow textStyle={textStyle} setProp={setProp} />
250
+ <tr>
251
+ <td colSpan="2">
252
+ <BlockSetting block={block} setProp={setProp} />
253
+ </td>
254
+ </tr>
255
+ </tbody>
256
+ </table>{" "}
257
+ {cfgFields ? (
258
+ <ConfigForm
259
+ fields={cfgFields}
260
+ configuration={configuration || {}}
261
+ setProp={setProp}
262
+ />
263
+ ) : null}
264
+ </Fragment>
180
265
  );
181
266
  };
182
267
 
@@ -196,6 +281,8 @@ Aggregation.craft = {
196
281
  "agg_field",
197
282
  "aggwhere",
198
283
  "stat",
284
+ "agg_fieldview",
285
+ { name: "configuration", default: {} },
199
286
  ],
200
287
  },
201
288
  };
@@ -219,26 +219,28 @@ const FieldSettings = () => {
219
219
  </td>
220
220
  </tr>
221
221
  )}
222
- <tr>
223
- <td></td>
224
- <td>
225
- <div className="form-check">
226
- <input
227
- className="form-check-input"
228
- name="inline"
229
- type="checkbox"
230
- checked={click_to_edit}
231
- onChange={(e) => {
232
- if (e && e.target) {
233
- const target_value = e.target.checked;
234
- setProp((prop) => (prop.click_to_edit = target_value));
235
- }
236
- }}
237
- />
238
- <label className="form-check-label">Click to edit?</label>
239
- </div>
240
- </td>
241
- </tr>
222
+ {options.mode === "show" || options.mode === "list" ? (
223
+ <tr>
224
+ <td></td>
225
+ <td>
226
+ <div className="form-check">
227
+ <input
228
+ className="form-check-input"
229
+ name="inline"
230
+ type="checkbox"
231
+ checked={click_to_edit}
232
+ onChange={(e) => {
233
+ if (e && e.target) {
234
+ const target_value = e.target.checked;
235
+ setProp((prop) => (prop.click_to_edit = target_value));
236
+ }
237
+ }}
238
+ />
239
+ <label className="form-check-label">Click to edit?</label>
240
+ </div>
241
+ </td>
242
+ </tr>
243
+ ) : null}
242
244
 
243
245
  {!(blockDisplay && blockDisplay.includes(fieldview)) && (
244
246
  <tr>
@@ -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
  ],
@@ -152,7 +152,7 @@ const ViewSettings = () => {
152
152
  if (rest.startsWith(".")) viewname = prefix;
153
153
  else viewname = rest;
154
154
  }
155
- if (viewname.includes(".")) viewname = viewname.split(".")[0];
155
+ if (viewname && viewname.includes(".")) viewname = viewname.split(".")[0];
156
156
 
157
157
  if (
158
158
  finder &&
@@ -177,7 +177,7 @@ const ViewSettings = () => {
177
177
  : [undefined, undefined];
178
178
  let safeRelation = null;
179
179
  const subView = views.find((view) => view.name === viewname);
180
- if (relation) {
180
+ if (relation && subView) {
181
181
  const subTbl = tables.find((tbl) => tbl.id === subView.table_id);
182
182
  safeRelation = new Relation(
183
183
  relation,
@@ -187,7 +187,7 @@ const ViewSettings = () => {
187
187
  }
188
188
  if (
189
189
  options.mode !== "filter" &&
190
- subView.table_id &&
190
+ subView?.table_id &&
191
191
  !safeRelation &&
192
192
  !hasLegacyRelation &&
193
193
  relationsData?.relations.length > 0
@@ -290,18 +290,26 @@ const ViewSettings = () => {
290
290
  ) : (
291
291
  <div>
292
292
  <label>View to {options.mode === "show" ? "embed" : "show"}</label>
293
- <select
294
- value={view}
295
- className="form-control form-select"
296
- onChange={setAProp("view")}
297
- onBlur={setAProp("view")}
298
- >
299
- {options.views.map((f, ix) => (
300
- <option key={ix} value={f.name}>
301
- {f.label || f.name}
302
- </option>
303
- ))}
304
- </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
+ onBlur={(e) => {
304
+ const target_value = e?.target?.value || e?.value;
305
+ setProp((prop) => {
306
+ prop.view = target_value;
307
+ });
308
+ }}
309
+ menuPortalTarget={document.body}
310
+ styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
311
+ ></Select>
312
+ )}
305
313
  </div>
306
314
  )}
307
315
  {options.mode !== "edit" && (
@@ -493,6 +493,7 @@ export const fetchViewPreview =
493
493
  };
494
494
  let viewname,
495
495
  body = configuration ? { ...configuration } : {};
496
+ if (!view) return "";
496
497
  if (view.includes(":")) {
497
498
  const [prefix, rest] = view.split(":");
498
499
  const tokens = rest.split(".");
@@ -180,6 +180,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
180
180
  step_only_ifs={segment.step_only_ifs || ""}
181
181
  step_action_names={segment.step_action_names || ""}
182
182
  confirm={segment.confirm}
183
+ spinner={segment.spinner}
183
184
  configuration={segment.configuration || {}}
184
185
  block={segment.block || false}
185
186
  minRole={segment.minRole || 10}
@@ -632,6 +633,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
632
633
  action_textcol: node.props.action_textcol,
633
634
  minRole: node.props.minRole,
634
635
  confirm: node.props.confirm,
636
+ spinner: node.props.spinner,
635
637
  nsteps: node.props.nsteps,
636
638
  step_only_ifs: node.props.step_only_ifs,
637
639
  step_action_names: node.props.step_action_names,
@@ -644,6 +646,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
644
646
  block: node.props.block,
645
647
  configuration: node.props.configuration,
646
648
  confirm: node.props.confirm,
649
+ spinner: node.props.spinner,
647
650
  action_name: node.props.name,
648
651
  ...(node.props.name !== "Clear" && node.props.action_row_variable
649
652
  ? {