@saltcorn/builder 1.0.0-beta.9 → 1.0.0-rc.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": "1.0.0-beta.9",
3
+ "version": "1.0.0-rc.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",
@@ -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.0.0-beta.9",
23
+ "@saltcorn/common-code": "1.0.0-rc.10",
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",
@@ -257,6 +257,32 @@ const SettingsPanel = () => {
257
257
  );
258
258
  };
259
259
 
260
+ // https://stackoverflow.com/questions/36862334/get-viewport-window-height-in-reactjs
261
+ function getWindowDimensions() {
262
+ const { innerWidth: windowWidth, innerHeight: windowHeight } = window;
263
+ return {
264
+ windowWidth,
265
+ windowHeight,
266
+ };
267
+ }
268
+
269
+ function useWindowDimensions() {
270
+ const [windowDimensions, setWindowDimensions] = useState(
271
+ getWindowDimensions()
272
+ );
273
+
274
+ useEffect(() => {
275
+ function handleResize() {
276
+ setWindowDimensions(getWindowDimensions());
277
+ }
278
+
279
+ window.addEventListener("resize", handleResize);
280
+ return () => window.removeEventListener("resize", handleResize);
281
+ }, []);
282
+
283
+ return windowDimensions;
284
+ }
285
+
260
286
  const AddColumnButton = () => {
261
287
  const { query, actions } = useEditor(() => {});
262
288
  const options = useContext(optionsCtx);
@@ -369,6 +395,23 @@ const Builder = ({ options, layout, mode }) => {
369
395
  const [isEnlarged, setIsEnlarged] = useState(false);
370
396
  const [isLeftEnlarged, setIsLeftEnlarged] = useState(false);
371
397
  const [relationsCache, setRelationsCache] = useState({});
398
+ const { windowWidth, windowHeight } = useWindowDimensions();
399
+
400
+ const [builderHeight, setBuilderHeight] = useState(0);
401
+ const [builderTop, setBuilderTop] = useState(0);
402
+
403
+ const ref = useRef(null);
404
+
405
+ useEffect(() => {
406
+ if (!ref.current) return;
407
+ setBuilderHeight(ref.current.clientHeight);
408
+ const rect = ref.current.getBoundingClientRect();
409
+ setBuilderTop(rect.top);
410
+ });
411
+
412
+ const canvasHeight =
413
+ Math.max(windowHeight - builderTop, builderHeight, 600) - 10;
414
+
372
415
  return (
373
416
  <ErrorBoundary>
374
417
  <Editor onRender={RenderNode}>
@@ -382,7 +425,7 @@ const Builder = ({ options, layout, mode }) => {
382
425
  setRelationsCache,
383
426
  }}
384
427
  >
385
- <div className="row" style={{ marginTop: "-5px" }}>
428
+ <div className="row" ref={ref} style={{ marginTop: "-5px" }}>
386
429
  <div
387
430
  className={`col-sm-auto left-builder-col ${
388
431
  isLeftEnlarged
@@ -437,6 +480,7 @@ const Builder = ({ options, layout, mode }) => {
437
480
  </div>
438
481
  <div
439
482
  id="builder-main-canvas"
483
+ style={{ height: canvasHeight }}
440
484
  className={`col builder-mode-${options.mode} ${
441
485
  options.mode !== "list" ? "emptymsg" : ""
442
486
  }`}
@@ -457,7 +457,11 @@ const ActionElem = ({ connectors, options }) => (
457
457
  title="Action button"
458
458
  >
459
459
  <Action
460
- name={options.actions[0]}
460
+ name={
461
+ options.actions[0].optgroup
462
+ ? options.actions[0].options[0]
463
+ : options.actions[0]
464
+ }
461
465
  action_row_variable={""}
462
466
  block={false}
463
467
  minRole={100}
@@ -142,7 +142,16 @@ const ActionSettings = () => {
142
142
  name === "Multi-step action"
143
143
  ? getCfgFields(step_action_names?.[use_setting_action_n])
144
144
  : null;
145
-
145
+ const cfg_link = (options.triggerActions || []).includes(name)
146
+ ? `/actions/configure/${encodeURIComponent(name)}`
147
+ : name === "Multi-step action" &&
148
+ (options.triggerActions || []).includes(
149
+ step_action_names?.[use_setting_action_n]
150
+ )
151
+ ? `/actions/configure/${encodeURIComponent(
152
+ step_action_names?.[use_setting_action_n]
153
+ )}`
154
+ : "";
146
155
  return (
147
156
  <div>
148
157
  <table className="w-100">
@@ -185,14 +194,21 @@ const ActionSettings = () => {
185
194
  setInitialConfig(setProp, value, getCfgFields(value));
186
195
  }}
187
196
  >
188
- {options.actions.map((f, ix) => (
189
- <option key={ix} value={f}>
190
- {f}
191
- </option>
192
- ))}
193
- {options.allowMultiStepAction ? (
194
- <option value={"Multi-step action"}>Multi-step action</option>
195
- ) : null}
197
+ {options.actions.map((f, ix) =>
198
+ f.optgroup && !f.options.length ? null : f.optgroup ? (
199
+ <optgroup key={ix} label={f.label}>
200
+ {f.options.map((a, jx) => (
201
+ <option key={jx} value={a}>
202
+ {a}
203
+ </option>
204
+ ))}
205
+ </optgroup>
206
+ ) : (
207
+ <option key={ix} value={f}>
208
+ {f}
209
+ </option>
210
+ )
211
+ )}
196
212
  </select>
197
213
  </td>
198
214
  </tr>
@@ -355,11 +371,29 @@ const ActionSettings = () => {
355
371
  </option>
356
372
  {options.actions
357
373
  .filter((f) => !(options.builtInActions || []).includes(f))
358
- .map((f, ix) => (
359
- <option key={ix} value={f}>
360
- {f}
361
- </option>
362
- ))}
374
+ .map((f, ix) =>
375
+ f.optgroup ? (
376
+ <optgroup key={ix} label={f.label}>
377
+ {f.options
378
+ .filter(
379
+ (f) =>
380
+ ![
381
+ "Multi-step action",
382
+ ...(options.builtInActions || []),
383
+ ].includes(f)
384
+ )
385
+ .map((a, jx) => (
386
+ <option key={jx} value={a}>
387
+ {a}
388
+ </option>
389
+ ))}
390
+ </optgroup>
391
+ ) : (
392
+ <option key={ix} value={f}>
393
+ {f}
394
+ </option>
395
+ )
396
+ )}
363
397
  </select>
364
398
  {options.mode !== "page" ? (
365
399
  <Fragment>
@@ -407,6 +441,11 @@ const ActionSettings = () => {
407
441
  node={node}
408
442
  />
409
443
  ) : null}
444
+ {cfg_link ? (
445
+ <a className="d-block mt-2" target="_blank" href={cfg_link}>
446
+ Configure this action
447
+ </a>
448
+ ) : null}
410
449
  </div>
411
450
  );
412
451
  };
@@ -14,6 +14,7 @@ import {
14
14
  setAPropGen,
15
15
  buildOptions,
16
16
  ConfigForm,
17
+ HelpTopicLink,
17
18
  } from "./utils";
18
19
 
19
20
  export /**
@@ -215,7 +216,16 @@ const AggregationSettings = () => {
215
216
  </tr>
216
217
  <tr>
217
218
  <td>
218
- <label>Where</label>
219
+ <label>
220
+ Where
221
+ <HelpTopicLink
222
+ topic="Aggregation where formula"
223
+ table_name={options.tableName}
224
+ mode={options.mode}
225
+ agg_relation={agg_relation}
226
+ agg_field={agg_field}
227
+ ></HelpTopicLink>
228
+ </label>
219
229
  </td>
220
230
  <td>
221
231
  <input
@@ -118,7 +118,7 @@ const Container = ({
118
118
  htmlElement,
119
119
  {
120
120
  ref: (dom) => connect(drag(dom)),
121
- className: `${customClass || ""} canvas text-${hAlign} ${
121
+ className: `${customClass || ""} kontainer canvas text-${hAlign} ${
122
122
  vAlign === "middle" ? "d-flex align-items-center" : ""
123
123
  } ${
124
124
  vAlign === "middle" && hAlign === "center" && "justify-content-center"
@@ -677,7 +677,7 @@ const ConfigForm = ({
677
677
  if (noshow) return null;
678
678
  }
679
679
  return (
680
- <div key={ix} className="builder-config-field">
680
+ <div key={ix} className="builder-config-field" data-field-name={f.name}>
681
681
  {!isCheckbox(f) ? (
682
682
  <label>
683
683
  {f.label || f.name}
@@ -783,7 +783,7 @@ const ConfigField = ({
783
783
  field.options =
784
784
  typeof field.attributes?.options === "string"
785
785
  ? field.attributes?.options.split(",").map((s) => s.trim())
786
- : field.attributes?.options;
786
+ : [...field.attributes?.options];
787
787
  if (!field.required && field.options) field.options.unshift("");
788
788
  }
789
789
  const field_type = field.input_type || field.type.name || field.type;
@@ -799,7 +799,7 @@ const ConfigField = ({
799
799
  //pick first value to mimic html form behaviour
800
800
  const options = getOptions();
801
801
  let o;
802
- if ((o = options[0]))
802
+ if (options && (o = options[0]))
803
803
  useEffect(() => {
804
804
  myOnChange(typeof o === "string" ? o : o.value || o.name || o);
805
805
  }, []);
@@ -939,7 +939,7 @@ const ConfigField = ({
939
939
  onChange={(e) => e.target && myOnChange(e.target.value)}
940
940
  onBlur={(e) => e.target && myOnChange(e.target.value)}
941
941
  >
942
- {field.options.map((o, ix) =>
942
+ {(field.options || []).map((o, ix) =>
943
943
  o.name && o.label ? (
944
944
  <option key={ix} value={o.name}>
945
945
  {o.label}
@@ -127,15 +127,25 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
127
127
  props.isFormula = segment.isFormula;
128
128
  if (related.hasContents)
129
129
  return (
130
- <Element key={ix} canvas {...props} is={MatchElement}>
130
+ <Element
131
+ key={ix}
132
+ canvas
133
+ {...props}
134
+ is={MatchElement}
135
+ custom={segment._custom || {}}
136
+ >
131
137
  {toTag(segment.contents)}
132
138
  </Element>
133
139
  );
134
- else return <MatchElement key={ix} {...props} />;
140
+ else
141
+ return (
142
+ <MatchElement key={ix} custom={segment._custom || {}} {...props} />
143
+ );
135
144
  }
136
145
  if (segment.type === "blank") {
137
146
  return (
138
147
  <Text
148
+ custom={segment._custom || {}}
139
149
  key={ix}
140
150
  text={segment.contents}
141
151
  isFormula={segment.isFormula || {}}
@@ -151,6 +161,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
151
161
  } else if (segment.type === "view") {
152
162
  return (
153
163
  <View
164
+ custom={segment._custom || {}}
154
165
  key={ix}
155
166
  view={segment.view}
156
167
  relation={segment.relation}
@@ -165,6 +176,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
165
176
  } else if (segment.type === "action") {
166
177
  return (
167
178
  <Action
179
+ custom={segment._custom || {}}
168
180
  key={ix}
169
181
  name={segment.action_name}
170
182
  rndid={segment.rndid || "not_assigned"}
@@ -193,6 +205,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
193
205
  return (
194
206
  <Element
195
207
  key={ix}
208
+ custom={segment._custom || {}}
196
209
  canvas
197
210
  gradStartColor={segment.gradStartColor}
198
211
  gradEndColor={segment.gradEndColor}
@@ -259,6 +272,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
259
272
  return (
260
273
  <Tabs
261
274
  key={ix}
275
+ custom={segment._custom || {}}
262
276
  titles={segment.titles}
263
277
  showif={segment.showif}
264
278
  ntabs={segment.ntabs}
@@ -278,6 +292,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
278
292
  return (
279
293
  <Table
280
294
  key={ix}
295
+ custom={segment._custom || {}}
281
296
  rows={segment.rows || 2}
282
297
  columns={segment.columns || 2}
283
298
  bs_style={segment.bs_style || false}
@@ -301,6 +316,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
301
316
  });
302
317
  return (
303
318
  <ListColumn
319
+ custom={segment._custom || {}}
304
320
  key={jx}
305
321
  alignment={col.alignment}
306
322
  header_label={col.header_label}
@@ -316,6 +332,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
316
332
  return (
317
333
  <Columns
318
334
  key={ix}
335
+ custom={segment._custom || {}}
319
336
  breakpoints={segment.breakpoints || default_breakpoints(segment)}
320
337
  ncols={segment.besides.length}
321
338
  widths={getColWidths(segment)}
@@ -423,6 +440,9 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
423
440
  */
424
441
  const go = (node) => {
425
442
  if (!node) return;
443
+ let customProps = {};
444
+ if (Object.keys(node?.custom || {}).length)
445
+ customProps = { _custom: { ...node?.custom } };
426
446
  const matchElement = allElements.find(
427
447
  (e) =>
428
448
  e.craft.related &&
@@ -432,7 +452,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
432
452
  );
433
453
  if (matchElement) {
434
454
  const related = matchElement.craft.related;
435
- const s = { type: related.segment_type };
455
+ const s = { type: related.segment_type, ...customProps };
436
456
  if (related.hasContents) s.contents = get_nodes(node);
437
457
  related.fields.forEach((f) => {
438
458
  if (f.type === "Nodes" && f.nodeID) {
@@ -459,6 +479,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
459
479
  return {
460
480
  besides: node.nodes.map((nm) => go(nodes[nm])),
461
481
  list_columns: true,
482
+ ...customProps,
462
483
  };
463
484
  }
464
485
  if (node.displayName === ListColumn.craft.displayName) {
@@ -471,6 +492,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
471
492
  alignment: node.props.alignment,
472
493
  header_label: node.props.header_label,
473
494
  showif: node.props.showif,
495
+ ...customProps,
474
496
  };
475
497
  (addFields || []).forEach((f) => {
476
498
  lc[f.name] = node.props[f.name];
@@ -519,6 +541,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
519
541
  click_action: node.props.click_action,
520
542
  rotate: node.props.rotate,
521
543
  style: node.props.style,
544
+ ...customProps,
522
545
  };
523
546
  else return get_nodes(node);
524
547
  }
@@ -535,6 +558,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
535
558
  style: node.props.style,
536
559
  icon: node.props.icon,
537
560
  font: node.props.font,
561
+ ...customProps,
538
562
  };
539
563
  }
540
564
 
@@ -556,6 +580,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
556
580
  bs_bordered: node.props.bs_bordered,
557
581
  bs_borderless: node.props.bs_borderless,
558
582
  bs_wauto: node.props.bs_wauto,
583
+ ...customProps,
559
584
  };
560
585
  }
561
586
 
@@ -572,6 +597,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
572
597
  colStyles: node.props.colStyles,
573
598
  style: node.props.style,
574
599
  widths,
600
+ ...customProps,
575
601
  };
576
602
  }
577
603
  if (node.displayName === Tabs.craft.displayName) {
@@ -600,6 +626,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
600
626
  serverRendered: node.props.serverRendered,
601
627
  tabId: node.props.tabId,
602
628
  ntabs: node.props.ntabs,
629
+ ...customProps,
603
630
  };
604
631
  }
605
632
 
@@ -614,6 +641,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
614
641
  state: node.props.state,
615
642
  configuration: node.props.configuration,
616
643
  extra_state_fml: node.props.extra_state_fml,
644
+ ...customProps,
617
645
  };
618
646
  }
619
647
  if (node.displayName === Action.craft.displayName) {
@@ -672,6 +700,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
672
700
  minRole: node.props.minRole,
673
701
  isFormula: node.props.isFormula,
674
702
  rndid: node.props.rndid === "not_assigned" ? newid : node.props.rndid,
703
+ ...customProps,
675
704
  };
676
705
  }
677
706
  };