@saltcorn/builder 0.9.1-beta.8 → 0.9.1

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.1-beta.8",
3
+ "version": "0.9.1",
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",
@@ -18,7 +18,9 @@ import {
18
18
  DynamicFontAwesomeIcon,
19
19
  setAPropGen,
20
20
  buildOptions,
21
+ ConfigField,
21
22
  } from "./utils";
23
+ import { ntimes } from "./Columns";
22
24
 
23
25
  export /**
24
26
  *
@@ -104,6 +106,10 @@ const ActionSettings = () => {
104
106
  action_bgcol: node.data.props.action_bgcol,
105
107
  action_bordercol: node.data.props.action_bordercol,
106
108
  action_textcol: node.data.props.action_textcol,
109
+ nsteps: node.data.props.nsteps,
110
+ step_only_ifs: node.data.props.step_only_ifs,
111
+ step_action_names: node.data.props.step_action_names,
112
+ setting_action_n: node.data.props.setting_action_n,
107
113
  }));
108
114
  const {
109
115
  actions: { setProp },
@@ -117,11 +123,22 @@ const ActionSettings = () => {
117
123
  configuration,
118
124
  action_label,
119
125
  action_style,
126
+ nsteps,
127
+ setting_action_n,
128
+ step_only_ifs,
129
+ step_action_names,
120
130
  } = node;
121
131
  const options = useContext(optionsCtx);
122
132
  const getCfgFields = (fv) => (options.actionConfigForms || {})[fv];
123
133
  const cfgFields = getCfgFields(name);
124
134
  const setAProp = setAPropGen(setProp);
135
+ const use_setting_action_n =
136
+ setting_action_n || setting_action_n === 0 ? setting_action_n : 0;
137
+ const stepCfgFields =
138
+ name === "Multi-step action"
139
+ ? getCfgFields(step_action_names?.[use_setting_action_n])
140
+ : null;
141
+
125
142
  return (
126
143
  <div>
127
144
  <table className="w-100">
@@ -154,6 +171,12 @@ const ActionSettings = () => {
154
171
  prop.action_row_variable = "state";
155
172
  }
156
173
  }
174
+ if (value === "Multi-step action" && !nsteps)
175
+ prop.nsteps = 1;
176
+ if (value === "Multi-step action" && !setting_action_n)
177
+ prop.setting_action_n = 0;
178
+ if (value === "Multi-step action" && !configuration.steps)
179
+ prop.configuration = { steps: [] };
157
180
  });
158
181
  setInitialConfig(setProp, value, getCfgFields(value));
159
182
  }}
@@ -163,6 +186,9 @@ const ActionSettings = () => {
163
186
  {f}
164
187
  </option>
165
188
  ))}
189
+ {options.allowMultiStepAction ? (
190
+ <option value={"Multi-step action"}>Multi-step action</option>
191
+ ) : null}
166
192
  </select>
167
193
  </td>
168
194
  </tr>
@@ -255,7 +281,104 @@ const ActionSettings = () => {
255
281
  {action_style !== "on_page_load" ? (
256
282
  <BlockSetting block={block} setProp={setProp} />
257
283
  ) : null}
258
- {cfgFields ? (
284
+ {name === "Multi-step action" ? (
285
+ <Fragment>
286
+ <table className="mb-2">
287
+ <tbody>
288
+ <tr>
289
+ <td className="w-50">
290
+ <label>#Steps</label>
291
+ </td>
292
+ <td>
293
+ <input
294
+ type="number"
295
+ value={nsteps}
296
+ className="form-control d-inline"
297
+ step="1"
298
+ min="1"
299
+ onChange={(e) => {
300
+ if (!e.target) return;
301
+ const value = e.target.value;
302
+ setProp((prop) => {
303
+ prop.nsteps = value;
304
+ });
305
+ }}
306
+ />
307
+ </td>
308
+ </tr>
309
+ </tbody>
310
+ </table>
311
+ <ConfigField
312
+ field={{
313
+ name: "setting_action_n",
314
+ label: "Column number",
315
+ type: "btn_select",
316
+ options: ntimes(nsteps, (i) => ({
317
+ value: i,
318
+ title: `${i + 1}`,
319
+ label: `${i + 1}`,
320
+ })),
321
+ }}
322
+ node={node}
323
+ setProp={setProp}
324
+ props={node}
325
+ ></ConfigField>
326
+ <label>Action</label>
327
+ <select
328
+ value={step_action_names?.[use_setting_action_n] || ""}
329
+ className="form-control form-select"
330
+ onChange={(e) => {
331
+ if (!e.target) return;
332
+ const value = e.target.value;
333
+ setProp((prop) => {
334
+ if (!prop.step_action_names) prop.step_action_names = [];
335
+ prop.step_action_names[use_setting_action_n] = value;
336
+ });
337
+ }}
338
+ >
339
+ {options.actions
340
+ .filter((f) => !(options.builtInActions || []).includes(f))
341
+ .map((f, ix) => (
342
+ <option key={ix} value={f}>
343
+ {f}
344
+ </option>
345
+ ))}
346
+ </select>
347
+ <label>Only if... (formula)</label>
348
+ <input
349
+ type="text"
350
+ className="form-control text-to-display"
351
+ value={step_only_ifs?.[use_setting_action_n] || ""}
352
+ onChange={(e) => {
353
+ if (!e.target) return;
354
+ const value = e.target.value;
355
+ setProp((prop) => {
356
+ if (!prop.step_only_ifs) prop.step_only_ifs = [];
357
+ prop.step_only_ifs[use_setting_action_n] = value;
358
+ });
359
+ }}
360
+ />
361
+ {stepCfgFields ? (
362
+ <Fragment>
363
+ Step configuration:
364
+ <ConfigForm
365
+ fields={stepCfgFields}
366
+ configuration={
367
+ configuration?.steps?.[use_setting_action_n] || {}
368
+ }
369
+ setProp={setProp}
370
+ setter={(prop, fldname, v) => {
371
+ if (!prop.configuration.steps) prop.configuration.steps = [];
372
+ if (!prop.configuration.steps[use_setting_action_n])
373
+ prop.configuration.steps[use_setting_action_n] = {};
374
+ prop.configuration.steps[use_setting_action_n][fldname] = v;
375
+ }}
376
+ node={node}
377
+ />
378
+ </Fragment>
379
+ ) : null}
380
+ </Fragment>
381
+ ) : cfgFields ? (
259
382
  <ConfigForm
260
383
  fields={cfgFields}
261
384
  configuration={configuration}
@@ -272,6 +395,7 @@ const ActionSettings = () => {
272
395
  */
273
396
  Action.craft = {
274
397
  displayName: "Action",
398
+ defaultProps: { setting_action_n: 0, nsteps: 1 },
275
399
  related: {
276
400
  settings: ActionSettings,
277
401
  },
@@ -272,7 +272,12 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
272
272
  {selectedCategory === "size" && (
273
273
  <Fragment>
274
274
  <SettingsRow
275
- field={{ name: "width", label: "width", type: "DimUnits" }}
275
+ field={{
276
+ name: "width",
277
+ label: "width",
278
+ type: "DimUnits",
279
+ horiz: true,
280
+ }}
276
281
  node={node}
277
282
  setProp={setProp}
278
283
  isStyle={!!sizeWithStyle}
@@ -282,6 +287,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
282
287
  name: "min-width",
283
288
  label: "min width",
284
289
  type: "DimUnits",
290
+ horiz: true,
285
291
  }}
286
292
  node={node}
287
293
  setProp={setProp}
@@ -292,6 +298,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
292
298
  name: "max-width",
293
299
  label: "max width",
294
300
  type: "DimUnits",
301
+ horiz: true,
295
302
  }}
296
303
  node={node}
297
304
  setProp={setProp}
@@ -99,6 +99,16 @@ const CardSettings = () => {
99
99
  node={node}
100
100
  setProp={setProp}
101
101
  />
102
+ <SettingsRow
103
+ field={{
104
+ label: "Class",
105
+ name: "class",
106
+ type: "String",
107
+ canBeFormula: true,
108
+ }}
109
+ node={node}
110
+ setProp={setProp}
111
+ />
102
112
  <SettingsRow
103
113
  field={{ label: "Shadow", name: "shadow", type: "Bool" }}
104
114
  node={node}
@@ -135,6 +145,7 @@ const fields = [
135
145
  canBeFormula: true,
136
146
  },
137
147
  { label: "URL", name: "url", type: "String", canBeFormula: true },
148
+ { label: "Class", name: "class", type: "String", canBeFormula: true },
138
149
  { label: "Shadow", name: "shadow", type: "Bool" },
139
150
  { label: "Save indicator", name: "titleAjaxIndicator", type: "Bool" },
140
151
  { label: "No padding", name: "noPadding", type: "Bool" },
@@ -148,6 +159,7 @@ Card.craft = {
148
159
  props: {
149
160
  title: "",
150
161
  url: "",
162
+ class: "",
151
163
  shadow: true,
152
164
  isFormula: {},
153
165
  style: {},
@@ -307,6 +307,30 @@ const LinkSettings = () => {
307
307
 
308
308
  <BlockSetting block={block} setProp={setProp} />
309
309
  <TextStyleSetting textStyle={textStyle} setProp={setProp} />
310
+ <table>
311
+ <tbody>
312
+ {(link_src === "Page" && url && url.startsWith("/page/")) ||
313
+ (link_src === "View" && url && url.startsWith("/view/")) ? (
314
+ <tr>
315
+ <td colSpan="2">
316
+ <a
317
+ className="d-block mt-2"
318
+ target="_blank"
319
+ href={
320
+ link_src === "Page"
321
+ ? url.replace("/page/", `/pageedit/edit/`)
322
+ : link_src === "View"
323
+ ? url.replace("/view/", `/viewedit/config/`)
324
+ : ""
325
+ }
326
+ >
327
+ Configure this {link_src}
328
+ </a>
329
+ </td>
330
+ </tr>
331
+ ) : null}
332
+ </tbody>
333
+ </table>
310
334
  </div>
311
335
  );
312
336
  };
@@ -241,24 +241,26 @@ const TabsSettings = () => {
241
241
  </td>
242
242
  </tr>
243
243
  {tabsStyle === "Value switch" ? (
244
- <tr>
245
- <td>
246
- <label>Field</label>
247
- </td>
248
- <td>
249
- <select
250
- value={field}
251
- className="form-control form-select"
252
- onChange={setAProp("field")}
253
- >
254
- {options.fields.map((f, ix) => (
255
- <option key={ix} value={f.name}>
256
- {f.label}
257
- </option>
258
- ))}
259
- </select>
260
- </td>
261
- </tr>
244
+ <Fragment>
245
+ <tr>
246
+ <td>
247
+ <label>Field</label>
248
+ </td>
249
+ <td>
250
+ <select
251
+ value={field}
252
+ className="form-control form-select"
253
+ onChange={setAProp("field")}
254
+ >
255
+ {options.fields.map((f, ix) => (
256
+ <option key={ix} value={f.name}>
257
+ {f.label}
258
+ </option>
259
+ ))}
260
+ </select>
261
+ </td>
262
+ </tr>
263
+ </Fragment>
262
264
  ) : (
263
265
  <Fragment>
264
266
  <tr>
@@ -297,6 +297,16 @@ const TextSettings = () => {
297
297
  setProp={setProp}
298
298
  isStyle={true}
299
299
  />
300
+ <SettingsRow
301
+ field={{
302
+ name: "color",
303
+ label: "Color",
304
+ type: "Color",
305
+ }}
306
+ node={node}
307
+ setProp={setProp}
308
+ isStyle={true}
309
+ />
300
310
  </tbody>
301
311
  </table>
302
312
  <BlockOrInlineSetting
@@ -268,6 +268,19 @@ const ViewLinkSettings = () => {
268
268
  <table>
269
269
  <tbody>
270
270
  <MinRoleSettingRow minRole={minRole} setProp={setProp} />
271
+ {use_view_name ? (
272
+ <tr>
273
+ <td colSpan="2">
274
+ <a
275
+ className="d-block mt-2"
276
+ target="_blank"
277
+ href={`/viewedit/config/${use_view_name}`}
278
+ >
279
+ Configure this view
280
+ </a>
281
+ </td>
282
+ </tr>
283
+ ) : null}
271
284
  </tbody>
272
285
  </table>
273
286
  </div>
@@ -652,6 +652,7 @@ const ConfigForm = ({
652
652
  fields,
653
653
  configuration,
654
654
  setProp,
655
+ setter,
655
656
  node,
656
657
  onChange,
657
658
  tableName,
@@ -663,7 +664,7 @@ const ConfigForm = ({
663
664
  let noshow = false;
664
665
  Object.entries(f.showIf).forEach(([nm, value]) => {
665
666
  if (Array.isArray(value))
666
- noshow = noshow || value.includes(configuration[nm]);
667
+ noshow = noshow || !value.includes(configuration[nm]);
667
668
  else noshow = noshow || value !== configuration[nm];
668
669
  });
669
670
  if (noshow) return null;
@@ -684,6 +685,7 @@ const ConfigForm = ({
684
685
  ) : null}
685
686
  <ConfigField
686
687
  field={f}
688
+ setter={setter}
687
689
  configuration={configuration}
688
690
  setProp={setProp}
689
691
  onChange={onChange}
@@ -734,6 +736,7 @@ const ConfigField = ({
734
736
  setProp,
735
737
  onChange,
736
738
  props,
739
+ setter,
737
740
  isStyle,
738
741
  }) => {
739
742
  /**
@@ -744,7 +747,8 @@ const ConfigField = ({
744
747
 
745
748
  const myOnChange = (v) => {
746
749
  setProp((prop) => {
747
- if (configuration) {
750
+ if (setter) setter(prop, field.name, v);
751
+ else if (configuration) {
748
752
  if (!prop.configuration) prop.configuration = {};
749
753
  prop.configuration[field.name] = v;
750
754
  } else if (isStyle) {
@@ -813,11 +817,13 @@ const ConfigField = ({
813
817
  onBlur={(e) => e.target && myOnChange(e.target.value)}
814
818
  >
815
819
  <option value={""}></option>
816
- {Object.entries(options.fonts || {}).map(([nm, ff], ix) => (
817
- <option key={ix} value={ff}>
818
- {nm}
819
- </option>
820
- ))}
820
+ {Object.entries(options.fonts || {})
821
+ .sort()
822
+ .map(([nm, ff], ix) => (
823
+ <option key={ix} value={ff}>
824
+ {nm}
825
+ </option>
826
+ ))}
821
827
  </select>
822
828
  ),
823
829
  Integer: () => (
@@ -943,7 +949,7 @@ const ConfigField = ({
943
949
  className={`w-${
944
950
  styleDim === "auto" ? 100 : 50
945
951
  } form-control-sm d-inline dimunit`}
946
- vert={true}
952
+ vert={!field.horiz}
947
953
  onChange={(e) => {
948
954
  if (!e.target) return;
949
955
  const target_value = e.target.value;
@@ -1002,7 +1008,7 @@ const SettingsFromFields =
1002
1008
  let noshow = false;
1003
1009
  Object.entries(f.showIf).forEach(([nm, value]) => {
1004
1010
  if (Array.isArray(value))
1005
- noshow = noshow || value.includes(node[nm]);
1011
+ noshow = noshow || !value.includes(node[nm]);
1006
1012
  else noshow = noshow || value !== node[nm];
1007
1013
  });
1008
1014
  if (noshow) return null;
@@ -1332,16 +1338,24 @@ export const bstyleopt = (style) => ({
1332
1338
  ></div>
1333
1339
  ),
1334
1340
  });
1341
+
1342
+ export const rand_ident = () =>
1343
+ Math.floor(Math.random() * 16777215).toString(16);
1344
+
1335
1345
  export const recursivelyCloneToElems = (query) => (nodeId, ix) => {
1336
1346
  const { data } = query.node(nodeId).get();
1337
1347
  const { type, props, nodes } = data;
1348
+ const newProps = { ...props };
1349
+ if (newProps.rndid) {
1350
+ newProps.rndid = rand_ident();
1351
+ }
1338
1352
  const children = (nodes || []).map(recursivelyCloneToElems(query));
1339
1353
  if (data.displayName === "Columns") {
1340
1354
  const cols = ntimes(data.props.ncols, (ix) =>
1341
1355
  recursivelyCloneToElems(query)(data.linkedNodes["Col" + ix])
1342
1356
  );
1343
1357
  return React.createElement(Columns, {
1344
- ...props,
1358
+ ...newProps,
1345
1359
  ...(typeof ix !== "undefined" ? { key: ix } : {}),
1346
1360
  contents: cols,
1347
1361
  });
@@ -1350,7 +1364,7 @@ export const recursivelyCloneToElems = (query) => (nodeId, ix) => {
1350
1364
  return React.createElement(
1351
1365
  Element,
1352
1366
  {
1353
- ...props,
1367
+ ...newProps,
1354
1368
  canvas: true,
1355
1369
  is: type,
1356
1370
  ...(typeof ix !== "undefined" ? { key: ix } : {}),
@@ -1360,7 +1374,7 @@ export const recursivelyCloneToElems = (query) => (nodeId, ix) => {
1360
1374
  return React.createElement(
1361
1375
  type,
1362
1376
  {
1363
- ...props,
1377
+ ...newProps,
1364
1378
  ...(typeof ix !== "undefined" ? { key: ix } : {}),
1365
1379
  },
1366
1380
  children
@@ -28,6 +28,7 @@ import { Container } from "./elements/Container";
28
28
  import { DropDownFilter } from "./elements/DropDownFilter";
29
29
  import { ToggleFilter } from "./elements/ToggleFilter";
30
30
  import { DropMenu } from "./elements/DropMenu";
31
+ import { rand_ident } from "./elements/utils";
31
32
 
32
33
  /**
33
34
  * @param {object} segment
@@ -167,6 +168,9 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT") => {
167
168
  action_bgcol={segment.action_bgcol || ""}
168
169
  action_bordercol={segment.action_bordercol || ""}
169
170
  action_textcol={segment.action_textcol || ""}
171
+ nsteps={segment.nsteps || ""}
172
+ step_only_ifs={segment.step_only_ifs || ""}
173
+ step_action_names={segment.step_action_names || ""}
170
174
  confirm={segment.confirm}
171
175
  configuration={segment.configuration || {}}
172
176
  block={segment.block || false}
@@ -344,7 +348,6 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT") => {
344
348
  /**
345
349
  * @returns {number}
346
350
  */
347
- const rand_ident = () => Math.floor(Math.random() * 16777215).toString(16);
348
351
 
349
352
  export /**
350
353
  * @param {object[]} nodes
@@ -555,6 +558,9 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT") => {
555
558
  action_textcol: node.props.action_textcol,
556
559
  minRole: node.props.minRole,
557
560
  confirm: node.props.confirm,
561
+ nsteps: node.props.nsteps,
562
+ step_only_ifs: node.props.step_only_ifs,
563
+ step_action_names: node.props.step_action_names,
558
564
  configuration: node.props.configuration,
559
565
  isFormula: node.props.isFormula,
560
566
  rndid: node.props.rndid === "not_assigned" ? newid : node.props.rndid,
@@ -578,6 +584,9 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT") => {
578
584
  action_bgcol: node.props.action_bgcol,
579
585
  action_bordercol: node.props.action_bordercol,
580
586
  action_textcol: node.props.action_textcol,
587
+ nsteps: node.props.nsteps,
588
+ step_only_ifs: node.props.step_only_ifs,
589
+ step_action_names: node.props.step_action_names,
581
590
  minRole: node.props.minRole,
582
591
  isFormula: node.props.isFormula,
583
592
  rndid: node.props.rndid === "not_assigned" ? newid : node.props.rndid,