@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.
@@ -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,123 +59,216 @@ 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 === "Percent true" || stat === "Percent false"
83
+ ? "Float"
84
+ : stat === "Count" || stat === "CountUnique"
85
+ ? "Integer"
86
+ : stat === "Array_Agg"
87
+ ? "Array"
88
+ : targetFieldType;
89
+ const fvs = options.agg_fieldview_options[outcomeType];
90
+
91
+ const [fetchedCfgFields, setFetchedCfgFields] = useState([]);
92
+ const cfgFields = fetchedCfgFields;
93
+ useEffect(() => {
94
+ fetch(
95
+ `/field/fieldviewcfgform/${
96
+ targetField?.table_name || options.tableName
97
+ }?accept=json`,
98
+ {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ "CSRF-Token": options.csrfToken,
103
+ "X-Requested-With": "XMLHttpRequest",
104
+ },
105
+ body: JSON.stringify({
106
+ agg_outcome_type: outcomeType,
107
+ agg_fieldview,
108
+ agg_field: targetField?.name,
109
+ }),
110
+ }
111
+ )
112
+ .then(function (response) {
113
+ if (response.status < 399) return response.json();
114
+ else return [];
115
+ })
116
+ .then(setFetchedCfgFields);
117
+ }, [outcomeType, agg_fieldview]);
72
118
  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) => (
145
- <option value={`Latest ${f.name}`}>Latest {f.name}</option>
146
- ))}
147
- {options.fields
148
- .filter((f) => f.type === "Date" || f.type.name === "Date")
149
- .map((f) => (
150
- <option value={`Earliest ${f.name}`}>
151
- Earliest {f.name}
119
+ <Fragment>
120
+ <table>
121
+ <tbody>
122
+ {options.mode === "filter" ? null : (
123
+ <tr>
124
+ <td>
125
+ <label>Relation</label>
126
+ </td>
127
+ <td>
128
+ <select
129
+ className="form-control form-select"
130
+ value={agg_relation}
131
+ onChange={(e) => {
132
+ if (!e.target) return;
133
+ const value = e.target.value;
134
+ setProp((prop) => {
135
+ prop.agg_relation = value;
136
+ const fs = options.agg_field_opts[value];
137
+ if (fs && fs.length > 0) prop.agg_field = fs[0]?.name;
138
+ });
139
+ }}
140
+ >
141
+ {options.child_field_list.map((f, ix) => (
142
+ <option key={ix} value={f}>
143
+ {f}
144
+ </option>
145
+ ))}
146
+ </select>
147
+ </td>
148
+ </tr>
149
+ )}
150
+ <tr>
151
+ <td>
152
+ <label>
153
+ {options.mode === "filter" ? "Field" : "Child table field"}
154
+ </label>
155
+ </td>
156
+ <td>
157
+ <select
158
+ className="form-control form-select"
159
+ value={agg_field}
160
+ onChange={setAProp("agg_field")}
161
+ >
162
+ {(options.agg_field_opts[agg_relation] || []).map((f, ix) => (
163
+ <option key={ix} value={f.name}>
164
+ {f.label}
152
165
  </option>
153
166
  ))}
154
- </select>
155
- </td>
156
- </tr>
157
- <tr>
158
- <td>
159
- <label>Where</label>
160
- </td>
161
- <td>
162
- <input
163
- type="text"
164
- className="form-control"
165
- value={aggwhere}
166
- onChange={setAProp("aggwhere")}
167
- />
168
- </td>
169
- </tr>
170
- <TextStyleRow textStyle={textStyle} setProp={setProp} />
171
- <tr>
172
- <td colSpan="2">
173
- <BlockSetting block={block} setProp={setProp} />
174
- </td>
175
- </tr>
176
- </tbody>
177
- </table>
167
+ </select>
168
+ </td>
169
+ </tr>
170
+ <tr>
171
+ <td>
172
+ <label>Statistic</label>
173
+ </td>
174
+ <td>
175
+ <select
176
+ value={stat}
177
+ className="form-control form-select"
178
+ onChange={setAProp("stat")}
179
+ onBlur={setAProp("stat")}
180
+ >
181
+ {buildOptions(
182
+ [
183
+ "Count",
184
+ "CountUnique",
185
+ "Avg",
186
+ "Sum",
187
+ "Max",
188
+ "Min",
189
+ "Array_Agg",
190
+ ],
191
+ { valAttr: true }
192
+ )}
193
+ {targetFieldType === "Bool" ? (
194
+ <option value={`Percent true`}>Percent true</option>
195
+ ) : null}
196
+ {targetFieldType === "Bool" ? (
197
+ <option value={`Percent false`}>Percent false</option>
198
+ ) : null}
199
+ {(options.agg_field_opts[agg_relation] || [])
200
+ .filter((f) => f.ftype === "Date")
201
+ .map((f, ix) => (
202
+ <option key={ix} value={`Latest ${f.name}`}>
203
+ Latest {f.name}
204
+ </option>
205
+ ))}
206
+ {(options.agg_field_opts[agg_relation] || [])
207
+ .filter((f) => f.ftype === "Date")
208
+ .map((f, ix) => (
209
+ <option key={ix} value={`Earliest ${f.name}`}>
210
+ Earliest {f.name}
211
+ </option>
212
+ ))}
213
+ </select>
214
+ </td>
215
+ </tr>
216
+ <tr>
217
+ <td>
218
+ <label>Where</label>
219
+ </td>
220
+ <td>
221
+ <input
222
+ type="text"
223
+ className="form-control"
224
+ value={aggwhere}
225
+ onChange={setAProp("aggwhere")}
226
+ />
227
+ </td>
228
+ </tr>
229
+ {fvs && (
230
+ <tr>
231
+ <td>
232
+ <label>Field view</label>
233
+ </td>
234
+
235
+ <td>
236
+ <select
237
+ value={agg_fieldview}
238
+ className="form-control form-select"
239
+ onChange={(e) => {
240
+ if (!e.target) return;
241
+ const value = e.target.value;
242
+ setProp((prop) => (prop.agg_fieldview = value));
243
+ //refetchPreview({ fieldview: value });
244
+ }}
245
+ >
246
+ {(fvs || []).map((fvnm, ix) => (
247
+ <option key={ix} value={fvnm}>
248
+ {fvnm}
249
+ </option>
250
+ ))}
251
+ </select>
252
+ </td>
253
+ </tr>
254
+ )}
255
+
256
+ <TextStyleRow textStyle={textStyle} setProp={setProp} />
257
+ <tr>
258
+ <td colSpan="2">
259
+ <BlockSetting block={block} setProp={setProp} />
260
+ </td>
261
+ </tr>
262
+ </tbody>
263
+ </table>{" "}
264
+ {cfgFields ? (
265
+ <ConfigForm
266
+ fields={cfgFields}
267
+ configuration={configuration || {}}
268
+ setProp={setProp}
269
+ />
270
+ ) : null}
271
+ </Fragment>
178
272
  );
179
273
  };
180
274
 
@@ -194,6 +288,8 @@ Aggregation.craft = {
194
288
  "agg_field",
195
289
  "aggwhere",
196
290
  "stat",
291
+ "agg_fieldview",
292
+ { name: "configuration", default: {} },
197
293
  ],
198
294
  },
199
295
  };
@@ -61,7 +61,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
61
61
  onClick={() => setCatAndDir("margin", "top")}
62
62
  >
63
63
  <input
64
- disabled
64
+ readOnly={true}
65
65
  type="text"
66
66
  autoComplete="off"
67
67
  name="boxmodel-ex-1_top_margin"
@@ -92,7 +92,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
92
92
  onClick={() => setCatAndDir("border", "top")}
93
93
  >
94
94
  <input
95
- disabled
95
+ readOnly={true}
96
96
  type="text"
97
97
  autoComplete="off"
98
98
  name="boxmodel-ex-1_top_border"
@@ -125,7 +125,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
125
125
  onClick={() => setCatAndDir("padding", "top")}
126
126
  >
127
127
  <input
128
- disabled
128
+ readOnly={true}
129
129
  type="text"
130
130
  autoComplete="off"
131
131
  name="boxmodel-ex-1_top_padding"
@@ -140,7 +140,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
140
140
  onClick={() => setSelectedCategory("size")}
141
141
  >
142
142
  <input
143
- disabled
143
+ readOnly={true}
144
144
  type="text"
145
145
  autoComplete="off"
146
146
  name="boxmodel-ex-1_width"
@@ -155,7 +155,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
155
155
  />
156
156
  x
157
157
  <input
158
- disabled
158
+ readOnly={true}
159
159
  type="text"
160
160
  autoComplete="off"
161
161
  name="boxmodel-ex-1_height"
@@ -174,7 +174,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
174
174
  onClick={() => setCatAndDir("padding", "bottom")}
175
175
  >
176
176
  <input
177
- disabled
177
+ readOnly={true}
178
178
  type="text"
179
179
  autoComplete="off"
180
180
  name="boxmodel-ex-1_bottom_padding"
@@ -199,7 +199,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
199
199
  onClick={() => setCatAndDir("border", "bottom")}
200
200
  >
201
201
  <input
202
- disabled
202
+ readOnly={true}
203
203
  type="text"
204
204
  autoComplete="off"
205
205
  name="boxmodel-ex-1_bottom_border"
@@ -228,7 +228,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
228
228
  onClick={() => setCatAndDir("margin", "bottom")}
229
229
  >
230
230
  <input
231
- disabled
231
+ readOnly={true}
232
232
  type="text"
233
233
  autoComplete="off"
234
234
  name="boxmodel-ex-1_bottom_margin"
@@ -25,12 +25,20 @@ const Column = ({ children, align }) => {
25
25
  id,
26
26
  connectors: { connect, drag },
27
27
  } = useNode((node) => ({ selected: node.events.selected }));
28
+ const options = useContext(optionsCtx);
29
+
28
30
  return (
29
31
  <div
30
- className={selected ? "selected-node" : ""}
32
+ className={`${selected ? "selected-node" : ""} ${
33
+ options.mode === "list" ? "flex-50 list-col-contents" : ""
34
+ }`}
31
35
  ref={(dom) => connect(drag(dom))}
32
36
  >
33
- <div className={`canvas ${id === "ROOT" ? "root-canvas" : ""}`}>
37
+ <div
38
+ className={`canvas ${id === "ROOT" ? "root-canvas" : ""} ${
39
+ options.mode === "list" ? "list-empty-msg list-col-canvas" : ""
40
+ }`}
41
+ >
34
42
  {children}
35
43
  </div>
36
44
  </div>
@@ -56,6 +64,12 @@ Column.craft = {
56
64
  props: {},
57
65
  rules: {
58
66
  canDrag: () => true,
67
+ canMoveIn: (incomming, current) => {
68
+ if (current?.data?.props?.singleOccupancy && current.data.nodes?.length)
69
+ return false;
70
+
71
+ return true;
72
+ },
59
73
  },
60
74
  related: {
61
75
  settings: ColumnSettings,
@@ -259,9 +259,9 @@ const ColumnsSettings = () => {
259
259
  label: "Vertical",
260
260
  type: "btn_select",
261
261
  options: [
262
- { value: "start", title: "All", label: <AlignTop /> },
263
- { value: "center", title: "All", label: <AlignMiddle /> },
264
- { value: "end", title: "All", label: <AlignBottom /> },
262
+ { value: "start", title: "Start", label: <AlignTop /> },
263
+ { value: "center", title: "Center", label: <AlignMiddle /> },
264
+ { value: "end", title: "End", label: <AlignBottom /> },
265
265
  ],
266
266
  }}
267
267
  node={colSetsNode}
@@ -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);
@@ -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 { faCaretDown } from "@fortawesome/free-solid-svg-icons";
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
  };
@@ -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>
@@ -46,7 +46,7 @@ const fields = (mode) => {
46
46
  type: "textarea",
47
47
  segment_name: "contents",
48
48
  sublabel:
49
- mode === "show"
49
+ mode === "show" || mode === "list"
50
50
  ? "Access fields with <code>{{ var_name }}</code>"
51
51
  : undefined,
52
52
  },