@saltcorn/builder 1.1.2-beta.7 → 1.1.2-beta.9

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": "1.1.2-beta.7",
3
+ "version": "1.1.2-beta.9",
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.24.7",
21
21
  "@craftjs/core": "0.1.0-beta.20",
22
22
  "@craftjs/utils": "0.1.0-beta.20",
23
- "@saltcorn/common-code": "1.1.2-beta.7",
23
+ "@saltcorn/common-code": "1.1.2-beta.9",
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",
@@ -110,6 +110,7 @@ const InitNewElement = ({ nodekeys, savingState, setSavingState }) => {
110
110
 
111
111
  fetch(`/${urlroot}/savebuilder/${options.page_id || options.view_id}`, {
112
112
  method: "POST", // or 'PUT'
113
+ keepalive: true,
113
114
  headers: {
114
115
  "Content-Type": "application/json",
115
116
  "CSRF-Token": options.csrfToken,
@@ -85,8 +85,7 @@ const Action = ({
85
85
  };
86
86
 
87
87
  export /**
88
- * @category saltcorn-builder
89
- * @subcategory components
88
+ * @category saltcorn-builder * @subcategory components
90
89
  * @namespace
91
90
  * @returns {div}
92
91
  */
@@ -108,11 +107,13 @@ const ActionSettings = () => {
108
107
  action_bgcol: node.data.props.action_bgcol,
109
108
  action_bordercol: node.data.props.action_bordercol,
110
109
  action_textcol: node.data.props.action_textcol,
110
+ action_class: node.data.props.action_class,
111
111
  nsteps: node.data.props.nsteps,
112
112
  step_only_ifs: node.data.props.step_only_ifs,
113
113
  step_action_names: node.data.props.step_action_names,
114
114
  setting_action_n: node.data.props.setting_action_n,
115
115
  spinner: node.data.props.spinner,
116
+ is_submit_action: node.data.props.is_submit_action,
116
117
  }));
117
118
  const {
118
119
  actions: { setProp },
@@ -132,6 +133,8 @@ const ActionSettings = () => {
132
133
  step_only_ifs,
133
134
  step_action_names,
134
135
  spinner,
136
+ is_submit_action,
137
+
135
138
  } = node;
136
139
  const options = useContext(optionsCtx);
137
140
  const getCfgFields = (fv) => (options.actionConfigForms || {})[fv];
@@ -313,6 +316,18 @@ const ActionSettings = () => {
313
316
  {action_style !== "on_page_load" ? (
314
317
  <BlockSetting block={block} setProp={setProp} />
315
318
  ) : null}
319
+ {options.mode === "edit" && name !== "Save" ? (
320
+ <div className="form-check">
321
+ <input
322
+ className="form-check-input"
323
+ name="block"
324
+ type="checkbox"
325
+ checked={is_submit_action}
326
+ onChange={setAProp("is_submit_action", { checked: true })}
327
+ />
328
+ <label className="form-check-label">This is the submit action</label>
329
+ </div>
330
+ ) : null}
316
331
  {name === "Multi-step action" ? (
317
332
  <Fragment>
318
333
  <label>Steps</label>
@@ -204,7 +204,7 @@ export const ArrayManager = ({
204
204
  title="Move left"
205
205
  type="button"
206
206
  style={{ width: "25%" }}
207
- className="btn btn-sm"
207
+ className="btn btn-outline-secondary btn-sm"
208
208
  onClick={() => move(-1)}
209
209
  disabled={node[currentProp] === 0}
210
210
  >
@@ -214,7 +214,7 @@ export const ArrayManager = ({
214
214
  title="Add"
215
215
  type="button"
216
216
  style={{ width: "25%" }}
217
- className="btn btn-sm"
217
+ className="btn btn-outline-secondary btn-sm"
218
218
  onClick={() => add()}
219
219
  >
220
220
  <FontAwesomeIcon icon={faPlus} />
@@ -223,7 +223,7 @@ export const ArrayManager = ({
223
223
  title="Delete"
224
224
  type="button"
225
225
  style={{ width: "25%" }}
226
- className="btn btn-sm"
226
+ className="btn btn-outline-secondary btn-sm"
227
227
  onClick={() => deleteElem()}
228
228
  >
229
229
  <FontAwesomeIcon icon={faTrashAlt} />
@@ -233,7 +233,7 @@ export const ArrayManager = ({
233
233
  type="button"
234
234
  disabled={node[currentProp] === node[countProp] - 1}
235
235
  style={{ width: "25%" }}
236
- className="btn btn-sm"
236
+ className="btn btn-outline-secondary btn-sm"
237
237
  onClick={() => move(1)}
238
238
  >
239
239
  <FontAwesomeIcon icon={faAngleDoubleRight} />
@@ -36,6 +36,7 @@ import {
36
36
  AlignBottom,
37
37
  SlashCircle,
38
38
  Image,
39
+ Images,
39
40
  Rainbow,
40
41
  Palette,
41
42
  EyeFill,
@@ -194,6 +195,7 @@ const ContainerSettings = () => {
194
195
  bgColor: node.data.props.bgColor,
195
196
  isFormula: node.data.props.isFormula,
196
197
  bgFileId: node.data.props.bgFileId,
198
+ bgField: node.data.props.bgField,
197
199
  imageSize: node.data.props.imageSize,
198
200
  htmlElement: node.data.props.htmlElement,
199
201
  vAlign: node.data.props.vAlign,
@@ -260,6 +262,7 @@ const ContainerSettings = () => {
260
262
  click_action,
261
263
  style,
262
264
  transform,
265
+ bgField,
263
266
  } = node;
264
267
  const options = useContext(optionsCtx);
265
268
  const { uploadedFiles } = useContext(previewCtx);
@@ -340,6 +343,17 @@ const ContainerSettings = () => {
340
343
  node={node}
341
344
  setProp={setProp}
342
345
  />
346
+ <SettingsRow
347
+ field={{
348
+ name: "opacity",
349
+ label: "Opacity",
350
+ type: "Float",
351
+ attributes: { min: 0, max: 1 },
352
+ }}
353
+ node={node}
354
+ setProp={setProp}
355
+ isStyle={true}
356
+ />
343
357
  <SettingsRow
344
358
  field={{
345
359
  name: "position",
@@ -462,6 +476,9 @@ const ContainerSettings = () => {
462
476
  options: [
463
477
  { value: "None", label: <SlashCircle /> },
464
478
  { value: "Image", label: <Image /> },
479
+ ...(options.mode === "show"
480
+ ? [{ value: "Image Field", label: <Images /> }]
481
+ : []),
465
482
  { value: "Color", label: <Palette /> },
466
483
  { value: "Gradient", label: <Rainbow /> },
467
484
  ],
@@ -532,30 +549,59 @@ const ContainerSettings = () => {
532
549
  </Fragment>
533
550
  )}
534
551
  {bgType === "Image" && (
535
- <Fragment>
536
- <tr>
537
- <td>
538
- <label>File</label>
539
- </td>
540
- <td>
541
- <select
542
- value={bgFileId}
543
- className="form-control-sm w-100 form-select"
544
- onChange={setAProp("bgFileId")}
545
- >
546
- {options.images.map((f, ix) => (
547
- <option key={ix} value={f.id}>
548
- {f.filename}
552
+ <tr>
553
+ <td>
554
+ <label>File</label>
555
+ </td>
556
+ <td>
557
+ <select
558
+ value={bgFileId}
559
+ className="form-control-sm w-100 form-select"
560
+ onChange={setAProp("bgFileId")}
561
+ >
562
+ {options.images.map((f, ix) => (
563
+ <option key={ix} value={f.id}>
564
+ {f.filename}
565
+ </option>
566
+ ))}
567
+ {(uploadedFiles || []).map((uf, ix) => (
568
+ <option key={ix} value={uf.id}>
569
+ {uf.filename}
570
+ </option>
571
+ ))}{" "}
572
+ </select>
573
+ </td>
574
+ </tr>
575
+ )}
576
+ {bgType === "Image Field" && (
577
+ <tr>
578
+ <td>
579
+ <label>File field</label>
580
+ </td>
581
+ <td>
582
+ <select
583
+ value={bgField}
584
+ className="form-control-sm w-100 form-select"
585
+ onChange={setAProp("bgField")}
586
+ >
587
+ {options.fields
588
+ .filter(
589
+ (f) =>
590
+ f.type === "String" ||
591
+ (f.type && f.type.name === "String") ||
592
+ (f.type && f.type === "File")
593
+ )
594
+ .map((f, ix) => (
595
+ <option key={ix} value={f.name}>
596
+ {f.label}
549
597
  </option>
550
598
  ))}
551
- {(uploadedFiles || []).map((uf, ix) => (
552
- <option key={ix} value={uf.id}>
553
- {uf.filename}
554
- </option>
555
- ))}{" "}
556
- </select>
557
- </td>
558
- </tr>
599
+ </select>
600
+ </td>
601
+ </tr>
602
+ )}
603
+ {(bgType === "Image" || bgType === "Image Field") && (
604
+ <Fragment>
559
605
  <tr>
560
606
  <td>
561
607
  <label>Size</label>
@@ -863,7 +909,7 @@ const ContainerSettings = () => {
863
909
  name: "animateName",
864
910
  label: "Animation",
865
911
  type: "select",
866
- options: ["None", ...options.keyframes || []],
912
+ options: ["None", ...(options.keyframes || [])],
867
913
  }}
868
914
  node={node}
869
915
  setProp={setProp}
@@ -882,8 +928,12 @@ const ContainerSettings = () => {
882
928
  node={node}
883
929
  setProp={setProp}
884
930
  />
885
- <SettingsRow
886
- field={{ name: "animateInitialHide", label: "Initially hidden", type: "Bool" }}
931
+ <SettingsRow
932
+ field={{
933
+ name: "animateInitialHide",
934
+ label: "Initially hidden",
935
+ type: "Bool",
936
+ }}
887
937
  node={node}
888
938
  setProp={setProp}
889
939
  />
@@ -1086,6 +1136,7 @@ Container.craft = {
1086
1136
  vAlign: "top",
1087
1137
  hAlign: "left",
1088
1138
  bgFileId: 0,
1139
+ bgField: "",
1089
1140
  rotate: 0,
1090
1141
  isFormula: {},
1091
1142
  bgType: "None",
@@ -99,6 +99,7 @@ const DropMenuSettings = () => {
99
99
  action_style: node.data.props.action_style,
100
100
  action_size: node.data.props.action_size,
101
101
  action_title: node.data.props.action_title,
102
+ action_class: node.data.props.action_class,
102
103
  action_icon: node.data.props.action_icon,
103
104
  action_bgcol: node.data.props.action_bgcol,
104
105
  action_bordercol: node.data.props.action_bordercol,
@@ -182,6 +183,7 @@ DropMenu.craft = {
182
183
  "action_icon",
183
184
  "action_bgcol",
184
185
  "action_title",
186
+ "action_class",
185
187
  "action_bordercol",
186
188
  "action_textcol",
187
189
  "menu_direction",
@@ -18,6 +18,7 @@ import {
18
18
  OrFormula,
19
19
  setAPropGen,
20
20
  buildOptions,
21
+ SettingsRow,
21
22
  } from "./utils";
22
23
 
23
24
  export /**
@@ -38,21 +39,19 @@ const Image = ({ fileid, block, srctype, url, alt, style }) => {
38
39
  connectors: { connect, drag },
39
40
  } = useNode((node) => ({ selected: node.events.selected }));
40
41
  const theurl = srctype === "File" ? `/files/serve/${fileid}` : url;
41
- return (
42
- <span {...blockProps(block)} ref={(dom) => connect(drag(dom))}>
43
- {fileid === 0 ? (
44
- "No images Available"
45
- ) : (
46
- <img
47
- className={`${style && style.width ? "" : "w-100"} image-widget ${
48
- selected ? "selected-node" : ""
49
- }`}
50
- style={reactifyStyles(style || {})}
51
- src={theurl}
52
- alt={alt}
53
- ></img>
54
- )}
55
- </span>
42
+ return fileid === 0 ? (
43
+ <span>No images Available</span>
44
+ ) : (
45
+ <img
46
+ {...blockProps(block)}
47
+ ref={(dom) => connect(drag(dom))}
48
+ className={`${style && style.width ? "" : "w-100"} image-widget ${
49
+ selected ? "selected-node" : ""
50
+ }`}
51
+ style={reactifyStyles(style || {})}
52
+ src={theurl}
53
+ alt={alt}
54
+ ></img>
56
55
  );
57
56
  };
58
57
 
@@ -274,6 +273,17 @@ const ImageSettings = () => {
274
273
  </td>
275
274
  </tr>
276
275
  )}
276
+ <SettingsRow
277
+ field={{
278
+ name: "object-fit",
279
+ label: "Object fit",
280
+ type: "select",
281
+ options: ["none", "fill", "contain", "cover", "scale-down"],
282
+ }}
283
+ node={node}
284
+ setProp={setProp}
285
+ isStyle={true}
286
+ />
277
287
  {srctype !== "Upload" && (
278
288
  <tr>
279
289
  <td colSpan="2">
@@ -108,6 +108,7 @@ const LinkSettings = () => {
108
108
  link_style: node.data.props.link_style,
109
109
  link_size: node.data.props.link_size,
110
110
  link_title: node.data.props.link_title,
111
+ link_class: node.data.props.link_class,
111
112
  link_icon: node.data.props.link_icon,
112
113
  link_bgcol: node.data.props.link_bgcol,
113
114
  link_bordercol: node.data.props.link_bordercol,
@@ -392,6 +393,7 @@ Link.craft = {
392
393
  "link_icon",
393
394
  "link_style",
394
395
  "link_title",
396
+ "link_class",
395
397
  "link_bgcol",
396
398
  "link_bordercol",
397
399
  "link_textcol",
@@ -74,7 +74,7 @@ const ViewLink = ({
74
74
  const displabel = label || (names.length > 1 ? names[1] : names[0]);
75
75
  return (
76
76
  <span
77
- className={`${textStyle} ${inModal ? "btn btn-secondary btn-sm" : ""} ${
77
+ className={`${textStyle} ${
78
78
  selected ? "selected-node" : "is-builder-link"
79
79
  } ${link_style} ${link_size || ""} ${block ? "d-block" : ""}`}
80
80
  ref={(dom) => connect(drag(dom))}
@@ -114,6 +114,7 @@ const ViewLinkSettings = () => {
114
114
  link_size: node.data.props.link_size,
115
115
  link_icon: node.data.props.link_icon,
116
116
  link_title: node.data.props.link_title,
117
+ link_class: node.data.props.link_class,
117
118
  textStyle: node.data.props.textStyle,
118
119
  link_bgcol: node.data.props.link_bgcol,
119
120
  link_bordercol: node.data.props.link_bordercol,
@@ -403,6 +404,7 @@ ViewLink.craft = {
403
404
  "link_icon",
404
405
  "link_size",
405
406
  "link_title",
407
+ "link_class",
406
408
  "link_target_blank",
407
409
  "link_bgcol",
408
410
  "link_bordercol",
@@ -879,16 +879,15 @@ const ConfigField = ({
879
879
  });
880
880
  onChange && onChange(field.name, v, setProp);
881
881
  };
882
- let value = or_if_undef(
883
- configuration
884
- ? configuration[field.name]
885
- : isStyle
886
- ? props.style[field.name]
887
- : subProp
888
- ? props[subProp]?.[field.name]
889
- : props[field.name],
890
- field.default
891
- );
882
+ let stored_value = configuration
883
+ ? configuration[field.name]
884
+ : isStyle
885
+ ? props.style[field.name]
886
+ : subProp
887
+ ? props[subProp]?.[field.name]
888
+ : props[field.name];
889
+
890
+ let value = or_if_undef(stored_value, field.default);
892
891
  if (valuePostfix)
893
892
  value = `${value}`.replaceAll(valuePostfix || "__nosuchstring", "");
894
893
  if (field.input_type === "fromtype") field.input_type = null;
@@ -912,8 +911,14 @@ const ConfigField = ({
912
911
  typeof field?.attributes?.options === "string"
913
912
  ? field.attributes?.options.split(",").map((s) => s.trim())
914
913
  : field?.attributes?.options || field.options;
915
-
916
- if (hasSelect && typeof value === "undefined") {
914
+ if (
915
+ typeof field.default !== "undefined" &&
916
+ typeof stored_value === "undefined"
917
+ ) {
918
+ useEffect(() => {
919
+ myOnChange(field.default);
920
+ }, []);
921
+ } else if (hasSelect && typeof value === "undefined") {
917
922
  //pick first value to mimic html form behaviour
918
923
  const options = getOptions();
919
924
  let o;
@@ -988,6 +993,8 @@ const ConfigField = ({
988
993
  className={`field-${field?.name} form-control`}
989
994
  value={value || ""}
990
995
  step={0.01}
996
+ max={or_if_undef(field?.attributes?.max, undefined)}
997
+ min={or_if_undef(field?.attributes?.min, undefined)}
991
998
  onChange={(e) => e.target && myOnChange(e.target.value)}
992
999
  />
993
1000
  ),
@@ -1538,18 +1545,32 @@ const ButtonOrLinkSettingsRows = ({
1538
1545
  ]
1539
1546
  : []),
1540
1547
  values[keyPrefix + "style"] !== "on_page_load" ? (
1541
- <tr key="btntitle">
1542
- <td>
1543
- <label>Hover title</label>
1544
- </td>
1545
- <td>
1546
- <input
1547
- className="form-control"
1548
- value={values[keyPrefix + "title"]}
1549
- onChange={setAProp(keyPrefix + "title")}
1550
- />
1551
- </td>
1552
- </tr>
1548
+ <Fragment>
1549
+ <tr key="btntitle">
1550
+ <td>
1551
+ <label>Hover title</label>
1552
+ </td>
1553
+ <td>
1554
+ <input
1555
+ className="form-control linkoractiontitle"
1556
+ value={values[keyPrefix + "title"]}
1557
+ onChange={setAProp(keyPrefix + "title")}
1558
+ />
1559
+ </td>
1560
+ </tr>
1561
+ <tr key="btnclass">
1562
+ <td>
1563
+ <label>Class</label>
1564
+ </td>
1565
+ <td>
1566
+ <input
1567
+ className="form-control linkoractionclass"
1568
+ value={values[keyPrefix + "class"]}
1569
+ onChange={setAProp(keyPrefix + "class")}
1570
+ />
1571
+ </td>
1572
+ </tr>
1573
+ </Fragment>
1553
1574
  ) : null,
1554
1575
  ];
1555
1576
  };
@@ -194,6 +194,7 @@ const layoutToNodes = (
194
194
  action_size={segment.action_size || ""}
195
195
  action_icon={segment.action_icon || ""}
196
196
  action_title={segment.action_title || ""}
197
+ action_class={segment.action_class || ""}
197
198
  action_bgcol={segment.action_bgcol || ""}
198
199
  action_bordercol={segment.action_bordercol || ""}
199
200
  action_textcol={segment.action_textcol || ""}
@@ -202,6 +203,7 @@ const layoutToNodes = (
202
203
  step_action_names={segment.step_action_names || ""}
203
204
  confirm={segment.confirm}
204
205
  spinner={segment.spinner}
206
+ is_submit_action={segment.is_submit_action}
205
207
  configuration={segment.configuration || {}}
206
208
  block={segment.block || false}
207
209
  minRole={segment.minRole || 10}
@@ -254,6 +256,7 @@ const layoutToNodes = (
254
256
  : segment.fullPageWidth
255
257
  }
256
258
  bgFileId={segment.bgFileId}
259
+ bgField={segment.bgField}
257
260
  imageSize={segment.imageSize || "contain"}
258
261
  imgResponsiveWidths={segment.imgResponsiveWidths}
259
262
  bgType={segment.bgType || "None"}
@@ -543,6 +546,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
543
546
  display: node.props.display,
544
547
  fullPageWidth: node.props.fullPageWidth || false,
545
548
  bgFileId: node.props.bgFileId,
549
+ bgField: node.props.bgField,
546
550
  bgType: node.props.bgType,
547
551
  imageSize: node.props.imageSize,
548
552
  imgResponsiveWidths: node.props.imgResponsiveWidths,
@@ -682,12 +686,14 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
682
686
  action_size: node.props.action_size,
683
687
  action_icon: node.props.action_icon,
684
688
  action_title: node.props.action_title,
689
+ action_class: node.props.action_class,
685
690
  action_bgcol: node.props.action_bgcol,
686
691
  action_bordercol: node.props.action_bordercol,
687
692
  action_textcol: node.props.action_textcol,
688
693
  minRole: node.props.minRole,
689
694
  confirm: node.props.confirm,
690
695
  spinner: node.props.spinner,
696
+ is_submit_action: node.props.is_submit_action,
691
697
  nsteps: node.props.nsteps,
692
698
  step_only_ifs: node.props.step_only_ifs,
693
699
  step_action_names: node.props.step_action_names,
@@ -700,7 +706,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
700
706
  block: node.props.block,
701
707
  configuration: node.props.configuration,
702
708
  confirm: node.props.confirm,
703
- spinner: node.props.spinner,
709
+ is_submit_action: node.props.is_submit_action,
704
710
  action_name: node.props.name,
705
711
  ...(node.props.name !== "Clear" && node.props.action_row_variable
706
712
  ? {
@@ -713,7 +719,9 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
713
719
  action_size: node.props.action_size,
714
720
  action_icon: node.props.action_icon,
715
721
  action_title: node.props.action_title,
722
+ action_class: node.props.action_class,
716
723
  action_bgcol: node.props.action_bgcol,
724
+ spinner: node.props.spinner,
717
725
  action_bordercol: node.props.action_bordercol,
718
726
  action_textcol: node.props.action_textcol,
719
727
  nsteps: node.props.nsteps,