@saltcorn/builder 0.7.1-beta.3 → 0.7.2-beta.10

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.7.1-beta.3",
3
+ "version": "0.7.2-beta.10",
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",
@@ -279,7 +279,7 @@ const SearchElem = ({ connectors }) => (
279
279
  title="Search bar"
280
280
  label="Search"
281
281
  >
282
- <SearchBar />
282
+ <Element canvas is={SearchBar}></Element>
283
283
  </WrapElem>
284
284
  );
285
285
  /**
@@ -165,7 +165,7 @@ const ActionSettings = () => {
165
165
  <MinRoleSettingRow minRole={minRole} setProp={setProp} />
166
166
  </tbody>
167
167
  </table>
168
- {options.mode === "show" ? (
168
+ {options.mode === "show" || name === "Delete" || name === "Reset" ? (
169
169
  <div className="form-check">
170
170
  <input
171
171
  className="form-check-input"
@@ -41,7 +41,7 @@ const Card = ({ children, isFormula, title, shadow, noPadding, style }) => {
41
41
  >
42
42
  {title && title.length > 0 && (
43
43
  <div className="card-header">
44
- {isFormula.title ? (
44
+ {isFormula?.title ? (
45
45
  <span className="font-monospace">={title}</span>
46
46
  ) : (
47
47
  title
@@ -229,6 +229,7 @@ const ContainerSettings = () => {
229
229
  rotate: node.data.props.rotate,
230
230
  display: node.data.props.display,
231
231
  style: node.data.props.style,
232
+ imgResponsiveWidths: node.data.props.imgResponsiveWidths,
232
233
  }));
233
234
  const {
234
235
  actions: { setProp },
@@ -257,6 +258,7 @@ const ContainerSettings = () => {
257
258
  fullPageWidth,
258
259
  overflow,
259
260
  htmlElement,
261
+ imgResponsiveWidths,
260
262
  } = node;
261
263
  const options = useContext(optionsCtx);
262
264
  const { uploadedFiles } = useContext(previewCtx);
@@ -520,6 +522,26 @@ const ContainerSettings = () => {
520
522
  </select>
521
523
  </td>
522
524
  </tr>
525
+ {imageSize !== "repeat" && (
526
+ <tr>
527
+ <td>
528
+ <label>Responsive widths</label>
529
+ </td>
530
+
531
+ <td>
532
+ <input
533
+ type="text"
534
+ value={imgResponsiveWidths}
535
+ className="form-control"
536
+ onChange={setAProp("imgResponsiveWidths")}
537
+ />
538
+ <small>
539
+ <i>List of widths to serve resized images,
540
+ e.g. 300, 400, 600</i>
541
+ </small>
542
+ </td>
543
+ </tr>
544
+ )}
523
545
  </Fragment>
524
546
  )}
525
547
  {bgType === "Color" && (
@@ -46,7 +46,8 @@ const Field = ({
46
46
  const { previews, setPreviews } = useContext(previewCtx);
47
47
  const myPreview = previews[node_id];
48
48
  const options = useContext(optionsCtx);
49
-
49
+ const blockDisplays = (options.blockDisplay || {})[name];
50
+ const blockDisplay = blockDisplays && blockDisplays.includes(fieldview);
50
51
  useEffect(() => {
51
52
  fetchFieldPreview({
52
53
  options,
@@ -60,7 +61,9 @@ const Field = ({
60
61
  return (
61
62
  <div
62
63
  className={`${textStyle} ${selected ? "selected-node" : ""} ${
63
- isBlock(block, inline, textStyle) ? "d-block" : "d-inline-block"
64
+ isBlock(block, inline, textStyle) || blockDisplay
65
+ ? "d-block"
66
+ : "d-inline-block"
64
67
  }`}
65
68
  ref={(dom) => connect(drag(dom))}
66
69
  >
@@ -106,6 +109,7 @@ const FieldSettings = () => {
106
109
 
107
110
  const fvs = options.field_view_options[name];
108
111
  const handlesTextStyle = (options.handlesTextStyle || {})[name];
112
+ const blockDisplay = (options.blockDisplay || {})[name];
109
113
  const getCfgFields = (fv) =>
110
114
  ((options.fieldViewConfigForms || {})[name] || {})[fv];
111
115
  const cfgFields = getCfgFields(fieldview);
@@ -184,17 +188,19 @@ const FieldSettings = () => {
184
188
  </td>
185
189
  </tr>
186
190
  )}
187
- <tr>
188
- <td></td>
189
- <td>
190
- <BlockOrInlineSetting
191
- block={block}
192
- inline={inline}
193
- textStyle={textStyle}
194
- setProp={setProp}
195
- />
196
- </td>
197
- </tr>
191
+ {!(blockDisplay && blockDisplay.includes(fieldview)) && (
192
+ <tr>
193
+ <td></td>
194
+ <td>
195
+ <BlockOrInlineSetting
196
+ block={block}
197
+ inline={inline}
198
+ textStyle={textStyle}
199
+ setProp={setProp}
200
+ />
201
+ </td>
202
+ </tr>
203
+ )}
198
204
  {!(handlesTextStyle && handlesTextStyle.includes(fieldview)) && (
199
205
  <TextStyleRow textStyle={textStyle} setProp={setProp} />
200
206
  )}
@@ -71,6 +71,7 @@ const ImageSettings = () => {
71
71
  block: node.data.props.block,
72
72
  style: node.data.props.style,
73
73
  isFormula: node.data.props.isFormula,
74
+ imgResponsiveWidths: node.data.props.imgResponsiveWidths,
74
75
  }));
75
76
  const {
76
77
  actions: { setProp },
@@ -82,6 +83,7 @@ const ImageSettings = () => {
82
83
  block,
83
84
  isFormula,
84
85
  filepath,
86
+ imgResponsiveWidths,
85
87
  style,
86
88
  } = node;
87
89
  const options = useContext(optionsCtx);
@@ -252,6 +254,27 @@ const ImageSettings = () => {
252
254
  </td>
253
255
  </tr>
254
256
  )}
257
+ {srctype !== "Upload" && (
258
+ <tr>
259
+ <td>
260
+ <label>Responsive widths</label>
261
+ </td>
262
+
263
+ <td>
264
+ <input
265
+ type="text"
266
+ value={imgResponsiveWidths}
267
+ className="form-control"
268
+ onChange={setAProp("imgResponsiveWidths")}
269
+ />
270
+ <small>
271
+ <i>
272
+ List of widths to serve resized images, e.g. 300, 400, 600
273
+ </i>
274
+ </small>
275
+ </td>
276
+ </tr>
277
+ )}
255
278
  {srctype !== "Upload" && (
256
279
  <tr>
257
280
  <td colSpan="2">
@@ -290,6 +313,7 @@ Image.craft = {
290
313
  { name: "fileid", default: 0 },
291
314
  "field",
292
315
  "block",
316
+ "imgResponsiveWidths",
293
317
  { name: "style", default: {} },
294
318
  ],
295
319
  },
@@ -53,7 +53,7 @@ const Link = ({
53
53
  <span
54
54
  className={`${textStyle} is-builder-link ${
55
55
  selected ? "selected-node" : ""
56
- } ${isFormula.text ? "font-monospace" : ""} ${link_style} ${link_size}`}
56
+ } ${isFormula?.text ? "font-monospace" : ""} ${link_style} ${link_size}`}
57
57
  {...blockProps(block)}
58
58
  ref={(dom) => connect(drag(dom))}
59
59
  style={
@@ -67,7 +67,7 @@ const Link = ({
67
67
  }
68
68
  >
69
69
  <DynamicFontAwesomeIcon icon={link_icon} className="me-1" />
70
- {isFormula.text ? `=${text}` : text}
70
+ {isFormula?.text ? `=${text}` : text}
71
71
  </span>
72
72
  );
73
73
  };
@@ -86,6 +86,7 @@ const LinkSettings = () => {
86
86
  isFormula: node.data.props.isFormula,
87
87
  textStyle: node.data.props.textStyle,
88
88
  nofollow: node.data.props.nofollow,
89
+ in_modal: node.data.props.in_modal,
89
90
  link_src: node.data.props.link_src,
90
91
  target_blank: node.data.props.target_blank,
91
92
  link_style: node.data.props.link_style,
@@ -105,6 +106,7 @@ const LinkSettings = () => {
105
106
  nofollow,
106
107
  target_blank,
107
108
  link_src,
109
+ in_modal,
108
110
  } = node;
109
111
  const options = useContext(optionsCtx);
110
112
  const setAProp = (key) => (e) => {
@@ -247,6 +249,18 @@ const LinkSettings = () => {
247
249
  />
248
250
  <label className="form-check-label">Open in new tab</label>
249
251
  </div>
252
+ <div className="form-check">
253
+ <input
254
+ className="form-check-input"
255
+ name="block"
256
+ type="checkbox"
257
+ checked={in_modal}
258
+ onChange={(e) =>
259
+ setProp((prop) => (prop.in_modal = e.target.checked))
260
+ }
261
+ />
262
+ <label className="form-check-label">Open in popup modal?</label>
263
+ </div>
250
264
  <BlockSetting block={block} setProp={setProp} />
251
265
  <TextStyleSetting textStyle={textStyle} setProp={setProp} />
252
266
  </div>
@@ -285,6 +299,7 @@ Link.craft = {
285
299
  "link_bgcol",
286
300
  "link_bordercol",
287
301
  "link_textcol",
302
+ "in_modal",
288
303
  ],
289
304
  },
290
305
  };
@@ -88,35 +88,60 @@ const Tabs = ({ contents, titles, tabsStyle, ntabs, independent, field }) => {
88
88
  } builder ${selected ? "selected-node" : ""}`}
89
89
  ref={(dom) => connect(drag(dom))}
90
90
  >
91
- {ntimes(ntabs, (ix) => (
92
- <li key={ix} className="nav-item" role="presentation">
93
- <a
94
- className={`nav-link ${ix === showTab ? `active` : ""}`}
95
- onClick={() => setShowTab(ix)}
96
- >
97
- {titles[ix] &&
98
- (typeof titles[ix].label === "undefined"
99
- ? titles[ix]
100
- : titles[ix].label === ""
101
- ? "(empty)"
102
- : titles[ix].label)}
103
- </a>
104
- </li>
105
- ))}
91
+ {ntimes(ntabs, (ix) => {
92
+ if (!titles[ix]) return null;
93
+ const targetIx =
94
+ typeof titles[ix].value === "undefined" ? ix : titles[ix].value;
95
+ return (
96
+ <li key={ix} className="nav-item" role="presentation">
97
+ <a
98
+ className={`nav-link ${targetIx === showTab ? `active` : ""}`}
99
+ onClick={() => setShowTab(targetIx)}
100
+ >
101
+ {titles[ix] &&
102
+ (typeof titles[ix].label === "undefined"
103
+ ? titles[ix]
104
+ : titles[ix].label === ""
105
+ ? "(empty)"
106
+ : titles[ix].label)}
107
+ </a>
108
+ </li>
109
+ );
110
+ })}
106
111
  </ul>
107
112
  <div className="tab-content" id="myTabContent">
108
- {ntimes(ntabs, (ix) => (
109
- <div
110
- key={ix}
111
- className={`tab-pane fade ${ix === showTab ? `show active` : ""}`}
112
- role="tabpanel"
113
- aria-labelledby="home-tab"
114
- >
115
- <Element canvas id={`Tab${ix}`} is={Column}>
116
- {contents[ix]}
117
- </Element>
118
- </div>
119
- ))}
113
+ {ntimes(ntabs, (ix) => {
114
+ if (!titles[ix]) return null;
115
+
116
+ const useIx =
117
+ typeof titles[ix].value === "undefined" ? ix : titles[ix].value;
118
+
119
+ if (useIx !== showTab)
120
+ return (
121
+ <div className="d-none" key={ix}>
122
+ <Element canvas id={`Tab${useIx}`} is={Column}>
123
+ {contents[useIx]}
124
+ </Element>
125
+ </div>
126
+ );
127
+ //d-none display of useIx is bug workaround? needed
128
+ else
129
+ return (
130
+ <div
131
+ key={ix}
132
+ className={`tab-pane fade ${
133
+ useIx === showTab ? `show active` : ""
134
+ }`}
135
+ role="tabpanel"
136
+ aria-labelledby="home-tab"
137
+ >
138
+ <div className="d-none">{useIx}</div>
139
+ <Element canvas id={`Tab${useIx}`} is={Column}>
140
+ {contents[useIx]}
141
+ </Element>
142
+ </div>
143
+ );
144
+ })}
120
145
  </div>
121
146
  </Fragment>
122
147
  );
@@ -163,6 +188,7 @@ const TabsSettings = () => {
163
188
  .then(function (data) {
164
189
  if (data.success) {
165
190
  const len = data.success.length;
191
+
166
192
  setProp((prop) => (prop.ntabs = len));
167
193
  setProp((prop) => (prop.titles = data.success));
168
194
  }
@@ -79,6 +79,7 @@ const ViewSettings = () => {
79
79
  name: node.data.props.name,
80
80
  view: node.data.props.view,
81
81
  state: node.data.props.state,
82
+ extra_state_fml: node.data.props.extra_state_fml,
82
83
  configuration: node.data.props.configuration, // fixed states
83
84
  node_id: node.id,
84
85
  }));
@@ -90,6 +91,7 @@ const ViewSettings = () => {
90
91
  state,
91
92
  node_id,
92
93
  configuration,
94
+ extra_state_fml,
93
95
  } = node;
94
96
  const options = useContext(optionsCtx);
95
97
  const views = options.views;
@@ -149,6 +151,20 @@ const ViewSettings = () => {
149
151
  )}
150
152
  </Fragment>
151
153
  )}
154
+ {(state === "shared" || options.mode === "page") && (
155
+ <Fragment>
156
+ {" "}
157
+ <label>Extra state Formula</label>
158
+ <input
159
+ type="text"
160
+ className="viewlink-label form-control"
161
+ value={extra_state_fml}
162
+ onChange={(e) =>
163
+ setProp((prop) => (prop.extra_state_fml = e.target.value))
164
+ }
165
+ />
166
+ </Fragment>
167
+ )}
152
168
  {view ? (
153
169
  <a
154
170
  className="d-block mt-2"
@@ -33,7 +33,7 @@ export /**
33
33
  * @returns {tr}
34
34
  * @category saltcorn-builder
35
35
  * @subcategory components
36
- * @namespace
36
+ * @namespace
37
37
  */
38
38
  const ViewLink = ({
39
39
  name,
@@ -99,6 +99,7 @@ const ViewLinkSettings = () => {
99
99
  link_bgcol: node.data.props.link_bgcol,
100
100
  link_bordercol: node.data.props.link_bordercol,
101
101
  link_textcol: node.data.props.link_textcol,
102
+ extra_state_fml: node.data.props.extra_state_fml,
102
103
  }));
103
104
  const {
104
105
  actions: { setProp },
@@ -109,6 +110,7 @@ const ViewLinkSettings = () => {
109
110
  isFormula,
110
111
  inModal,
111
112
  textStyle,
113
+ extra_state_fml,
112
114
  } = node;
113
115
  const options = useContext(optionsCtx);
114
116
  return (
@@ -148,6 +150,20 @@ const ViewLinkSettings = () => {
148
150
  </OrFormula>
149
151
  </td>
150
152
  </tr>
153
+ <tr>
154
+ <td colSpan="2">
155
+ <label>Extra state Formula</label>
156
+ <input
157
+ type="text"
158
+ className="viewlink-label form-control"
159
+ value={extra_state_fml}
160
+ onChange={(e) =>
161
+ setProp((prop) => (prop.extra_state_fml = e.target.value))
162
+ }
163
+ />
164
+ </td>
165
+ </tr>
166
+
151
167
  <ButtonOrLinkSettingsRows
152
168
  setProp={setProp}
153
169
  keyPrefix="link_"
@@ -203,6 +219,7 @@ ViewLink.craft = {
203
219
  "link_bgcol",
204
220
  "link_bordercol",
205
221
  "link_textcol",
222
+ "extra_state_fml",
206
223
  ],
207
224
  },
208
225
  };
@@ -387,53 +387,57 @@ const fetchPreview = ({ url, body, options, setPreviews, node_id, isView }) => {
387
387
  * @param {object} [args = {}]
388
388
  * @return {function}
389
389
  */
390
- export const fetchFieldPreview = (args = {}) => (changes = {}) => {
391
- const { node_id, options, name, fieldview, setPreviews } = {
392
- ...args,
393
- ...changes,
394
- };
395
- const configuration = {
396
- ...(args.configuration || {}),
397
- ...(changes.configuration || {}),
390
+ export const fetchFieldPreview =
391
+ (args = {}) =>
392
+ (changes = {}) => {
393
+ const { node_id, options, name, fieldview, setPreviews } = {
394
+ ...args,
395
+ ...changes,
396
+ };
397
+ const configuration = {
398
+ ...(args.configuration || {}),
399
+ ...(changes.configuration || {}),
400
+ };
401
+ fetchPreview({
402
+ options,
403
+ node_id,
404
+ setPreviews,
405
+ url: `/field/preview/${options.tableName}/${name}/${fieldview}`,
406
+ body: { configuration },
407
+ });
398
408
  };
399
- fetchPreview({
400
- options,
401
- node_id,
402
- setPreviews,
403
- url: `/field/preview/${options.tableName}/${name}/${fieldview}`,
404
- body: { configuration },
405
- });
406
- };
407
409
 
408
410
  /**
409
411
  * @function
410
412
  * @param {object} [args = {}]
411
413
  * @return {function}
412
414
  */
413
- export const fetchViewPreview = (args = {}) => (changes = {}) => {
414
- const { node_id, options, view, setPreviews, configuration } = {
415
- ...args,
416
- ...changes,
417
- };
418
- let viewname,
419
- body = configuration ? { ...configuration } : {};
420
- if (view.includes(":")) {
421
- const [reltype, rest] = view.split(":");
422
- const [vnm] = rest.split(".");
423
- viewname = vnm;
424
- body.reltype = reltype;
425
- body.path = rest;
426
- } else viewname = view;
415
+ export const fetchViewPreview =
416
+ (args = {}) =>
417
+ (changes = {}) => {
418
+ const { node_id, options, view, setPreviews, configuration } = {
419
+ ...args,
420
+ ...changes,
421
+ };
422
+ let viewname,
423
+ body = configuration ? { ...configuration } : {};
424
+ if (view.includes(":")) {
425
+ const [reltype, rest] = view.split(":");
426
+ const [vnm] = rest.split(".");
427
+ viewname = vnm;
428
+ body.reltype = reltype;
429
+ body.path = rest;
430
+ } else viewname = view;
427
431
 
428
- fetchPreview({
429
- options,
430
- node_id,
431
- setPreviews,
432
- url: `/view/${viewname}/preview`,
433
- body,
434
- isView: true,
435
- });
436
- };
432
+ fetchPreview({
433
+ options,
434
+ node_id,
435
+ setPreviews,
436
+ url: `/view/${viewname}/preview`,
437
+ body,
438
+ isView: true,
439
+ });
440
+ };
437
441
 
438
442
  export /**
439
443
  * @param {object} props
@@ -650,8 +654,11 @@ const ConfigField = ({
650
654
  if (field.input_type === "fromtype") field.input_type = null;
651
655
  if (field.type && field.type.name === "String" && field.attributes.options) {
652
656
  field.input_type = "select";
653
- field.options = field.attributes.options;
654
- if (!field.required) field.options.unshift("");
657
+ field.options =
658
+ typeof field.attributes.options === "string"
659
+ ? field.attributes.options.split(",").map((s) => s.trim())
660
+ : field.attributes.options;
661
+ if (!field.required && field.options) field.options.unshift("");
655
662
  }
656
663
  const dispatch = {
657
664
  String() {
@@ -142,6 +142,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT") => {
142
142
  view={segment.view}
143
143
  name={segment.name}
144
144
  state={segment.state}
145
+ extra_state_fml={segment.extra_state_fml}
145
146
  configuration={segment.configuration || {}}
146
147
  />
147
148
  );
@@ -205,6 +206,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT") => {
205
206
  }
206
207
  bgFileId={segment.bgFileId}
207
208
  imageSize={segment.imageSize || "contain"}
209
+ imgResponsiveWidths={segment.imgResponsiveWidths}
208
210
  bgType={segment.bgType || "None"}
209
211
  style={segment.style || {}}
210
212
  bgColor={segment.bgColor || "#ffffff"}
@@ -222,6 +224,14 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT") => {
222
224
  </Element>
223
225
  );
224
226
  } else if (segment.type === "tabs") {
227
+ let contentsArray = segment.contents.map(toTag);
228
+ let contents;
229
+ if (segment.tabsStyle === "Value switch") {
230
+ contents = {};
231
+ segment.titles.forEach(({ label, value }, ix) => {
232
+ contents[value] = contentsArray[ix];
233
+ });
234
+ } else contents = contentsArray;
225
235
  return (
226
236
  <Tabs
227
237
  key={ix}
@@ -231,7 +241,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT") => {
231
241
  deeplink={segment.deeplink}
232
242
  field={segment.field}
233
243
  tabsStyle={segment.tabsStyle}
234
- contents={segment.contents.map(toTag)}
244
+ contents={contents}
235
245
  />
236
246
  );
237
247
  } else if (segment.besides) {
@@ -320,6 +330,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT") => {
320
330
  * @returns {object}
321
331
  */
322
332
  const go = (node) => {
333
+ if (!node) return;
323
334
  const matchElement = allElements.find(
324
335
  (e) =>
325
336
  e.craft.related &&
@@ -372,6 +383,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT") => {
372
383
  bgFileId: node.props.bgFileId,
373
384
  bgType: node.props.bgType,
374
385
  imageSize: node.props.imageSize,
386
+ imgResponsiveWidths: node.props.imgResponsiveWidths,
375
387
  bgColor: node.props.bgColor,
376
388
  setTextColor: node.props.setTextColor,
377
389
  textColor: node.props.textColor,
@@ -415,11 +427,19 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT") => {
415
427
  };
416
428
  }
417
429
  if (node.displayName === Tabs.craft.displayName) {
430
+ let contents;
431
+ if (node.props.tabsStyle === "Value switch") {
432
+ contents = node.props.titles.map(({ value }, ix) => {
433
+ const useIx = typeof value === "undefined" ? ix : value;
434
+ return go(nodes[node.linkedNodes["Tab" + useIx]]);
435
+ });
436
+ } else
437
+ contents = ntimes(node.props.ntabs, (ix) =>
438
+ go(nodes[node.linkedNodes["Tab" + ix]])
439
+ );
418
440
  return {
419
441
  type: "tabs",
420
- contents: ntimes(node.props.ntabs, (ix) =>
421
- go(nodes[node.linkedNodes["Tab" + ix]])
422
- ),
442
+ contents,
423
443
  titles: node.props.titles,
424
444
  tabsStyle: node.props.tabsStyle,
425
445
  field: node.props.field,
@@ -437,6 +457,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT") => {
437
457
  node.props.name === "not_assigned" ? rand_ident() : node.props.name,
438
458
  state: node.props.state,
439
459
  configuration: node.props.configuration,
460
+ extra_state_fml: node.props.extra_state_fml,
440
461
  };
441
462
  }
442
463