@saltcorn/builder 1.6.0-alpha.1 → 1.6.0-alpha.11

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.
Files changed (40) hide show
  1. package/dist/builder_bundle.js +85 -1
  2. package/dist/builder_bundle.js.LICENSE.txt +18 -51
  3. package/package.json +31 -27
  4. package/src/components/Builder.js +445 -155
  5. package/src/components/Library.js +25 -13
  6. package/src/components/RenderNode.js +26 -8
  7. package/src/components/Toolbox.js +333 -269
  8. package/src/components/elements/Action.js +144 -29
  9. package/src/components/elements/Aggregation.js +20 -23
  10. package/src/components/elements/ArrayManager.js +17 -10
  11. package/src/components/elements/BoxModelEditor.js +19 -17
  12. package/src/components/elements/Card.js +47 -34
  13. package/src/components/elements/Clone.js +74 -2
  14. package/src/components/elements/Column.js +1 -1
  15. package/src/components/elements/Columns.js +130 -121
  16. package/src/components/elements/Container.js +185 -92
  17. package/src/components/elements/DropDownFilter.js +10 -8
  18. package/src/components/elements/DropMenu.js +18 -9
  19. package/src/components/elements/Field.js +9 -7
  20. package/src/components/elements/HTMLCode.js +3 -1
  21. package/src/components/elements/Image.js +20 -15
  22. package/src/components/elements/JoinField.js +15 -11
  23. package/src/components/elements/Link.js +18 -16
  24. package/src/components/elements/ListColumn.js +7 -3
  25. package/src/components/elements/ListColumns.js +4 -1
  26. package/src/components/elements/MonacoEditor.js +4 -2
  27. package/src/components/elements/Page.js +7 -4
  28. package/src/components/elements/RelationBadges.js +16 -11
  29. package/src/components/elements/RelationOnDemandPicker.js +18 -12
  30. package/src/components/elements/SearchBar.js +37 -10
  31. package/src/components/elements/Table.js +72 -65
  32. package/src/components/elements/Tabs.js +18 -15
  33. package/src/components/elements/Text.js +19 -14
  34. package/src/components/elements/ToggleFilter.js +28 -25
  35. package/src/components/elements/View.js +36 -18
  36. package/src/components/elements/ViewLink.js +15 -11
  37. package/src/components/elements/utils.js +224 -55
  38. package/src/components/storage.js +33 -134
  39. package/src/hooks/useTranslation.js +11 -0
  40. package/src/index.js +6 -3
@@ -3,10 +3,11 @@
3
3
  * @module components/elements/Action
4
4
  * @subcategory components / elements
5
5
  */
6
- /*global notifyAlert*/
6
+ /*global notifyAlert, apply_showif*/
7
7
 
8
- import React, { Fragment, useContext, useEffect } from "react";
8
+ import React, { Fragment, useContext, useEffect, useState } from "react";
9
9
  import { useNode } from "@craftjs/core";
10
+ import useTranslation from "../../hooks/useTranslation";
10
11
  import optionsCtx from "../context";
11
12
  import {
12
13
  BlockSetting,
@@ -19,9 +20,12 @@ import {
19
20
  setAPropGen,
20
21
  buildOptions,
21
22
  ConfigField,
23
+ reactSelectStyles,
24
+ builderSelectClassName,
22
25
  } from "./utils";
23
26
  import { ntimes } from "./Columns";
24
27
  import { ArrayManager } from "./ArrayManager";
28
+ import { MultiLineCodeEditor } from "./MonacoEditor";
25
29
  import Select from "react-select";
26
30
 
27
31
  export /**
@@ -91,6 +95,7 @@ export /**
91
95
  * @returns {div}
92
96
  */
93
97
  const ActionSettings = () => {
98
+ const { t } = useTranslation();
94
99
  const node = useNode((node) => ({
95
100
  name: node.data.props.name,
96
101
  action_row_variable: node.data.props.action_row_variable,
@@ -141,6 +146,13 @@ const ActionSettings = () => {
141
146
  const options = useContext(optionsCtx);
142
147
  const getCfgFields = (fv) => (options.actionConfigForms || {})[fv];
143
148
  const cfgFields = getCfgFields(name);
149
+ const cfgFieldsForForm =
150
+ name === "run_js_code"
151
+ ? (cfgFields || []).filter((f) => f.name !== "code")
152
+ : cfgFields;
153
+
154
+ const runJsCodeModalOnly = false;
155
+ const [codeModalOpen, setCodeModalOpen] = useState(false);
144
156
  const setAProp = setAPropGen(setProp);
145
157
  const use_setting_action_n =
146
158
  setting_action_n || setting_action_n === 0 ? setting_action_n : 0;
@@ -229,6 +241,7 @@ const ActionSettings = () => {
229
241
  step_action_names?.[use_setting_action_n] || "",
230
242
  JSON.stringify(configuration?.steps?.[use_setting_action_n]),
231
243
  ]);
244
+
232
245
 
233
246
  return (
234
247
  <div>
@@ -236,20 +249,19 @@ const ActionSettings = () => {
236
249
  <tbody>
237
250
  <tr>
238
251
  <td>
239
- <label>Action</label>
252
+ <label>{t("Action")}</label>
240
253
  </td>
241
254
  <td>
242
255
  {options.inJestTestingMode ? null : (
243
256
  <Select
244
257
  options={actionOptions}
245
- className="react-select action-selector"
258
+ className={builderSelectClassName("react-select action-selector")}
259
+ classNamePrefix="builder-select"
246
260
  value={selectedAction}
247
261
  defaultValue={selectedAction}
248
262
  onChange={setAction}
249
263
  menuPortalTarget={document.body}
250
- styles={{
251
- menuPortal: (base) => ({ ...base, zIndex: 19999 }),
252
- }}
264
+ styles={reactSelectStyles()}
253
265
  ></Select>
254
266
  )}
255
267
  </td>
@@ -257,7 +269,7 @@ const ActionSettings = () => {
257
269
  {name !== "Clear" && options.mode === "filter" ? (
258
270
  <tr>
259
271
  <td>
260
- <label>Row variable</label>
272
+ <label>{t("Row variable")}</label>
261
273
  </td>
262
274
  <td>
263
275
  <select
@@ -290,7 +302,7 @@ const ActionSettings = () => {
290
302
  {action_row_variable === "each_matching_row" ? (
291
303
  <tr>
292
304
  <td>
293
- <label>Rows limit</label>
305
+ <label>{t("Rows limit")}</label>
294
306
  </td>
295
307
  <td>
296
308
  <input
@@ -306,7 +318,7 @@ const ActionSettings = () => {
306
318
  {action_style !== "on_page_load" ? (
307
319
  <tr>
308
320
  <td colSpan="2">
309
- <label>Label (leave blank for default)</label>
321
+ <label>{t("Label (leave blank for default)")}</label>
310
322
  <OrFormula
311
323
  nodekey="action_label"
312
324
  {...{ setProp, isFormula, node }}
@@ -339,7 +351,7 @@ const ActionSettings = () => {
339
351
  checked={confirm}
340
352
  onChange={setAProp("confirm", { checked: true })}
341
353
  />
342
- <label className="form-check-label">User confirmation?</label>
354
+ <label className="form-check-label">{t("User confirmation?")}</label>
343
355
  </div>
344
356
  <div className="form-check">
345
357
  <input
@@ -349,7 +361,7 @@ const ActionSettings = () => {
349
361
  checked={spinner}
350
362
  onChange={setAProp("spinner", { checked: true })}
351
363
  />
352
- <label className="form-check-label">Spinner on click</label>
364
+ <label className="form-check-label">{t("Spinner on click")}</label>
353
365
  </div>
354
366
  <div className="form-check">
355
367
  <input
@@ -359,7 +371,7 @@ const ActionSettings = () => {
359
371
  checked={run_async}
360
372
  onChange={setAProp("run_async", { checked: true })}
361
373
  />
362
- <label className="form-check-label">Run async</label>
374
+ <label className="form-check-label">{t("Run async")}</label>
363
375
  </div>
364
376
  {action_style !== "on_page_load" ? (
365
377
  <BlockSetting block={block} setProp={setProp} />
@@ -373,12 +385,12 @@ const ActionSettings = () => {
373
385
  checked={is_submit_action}
374
386
  onChange={setAProp("is_submit_action", { checked: true })}
375
387
  />
376
- <label className="form-check-label">This is the submit action</label>
388
+ <label className="form-check-label">{t("This is the submit action")}</label>
377
389
  </div>
378
390
  ) : null}
379
391
  {name === "Multi-step action" ? (
380
392
  <Fragment>
381
- <label>Steps</label>
393
+ <label>{t("Steps")}</label>
382
394
 
383
395
  <ArrayManager
384
396
  node={node}
@@ -392,23 +404,22 @@ const ActionSettings = () => {
392
404
  ]}
393
405
  ></ArrayManager>
394
406
 
395
- <label>Action</label>
407
+ <label>{t("Action")}</label>
396
408
  {options.inJestTestingMode ? null : (
397
409
  <Select
398
410
  options={multiStepActionOptions}
399
- className="react-select multistep-action-selector"
411
+ className={builderSelectClassName("react-select multistep-action-selector")}
412
+ classNamePrefix="builder-select"
400
413
  value={selectedMultiStepAction}
401
414
  defaultValue={selectedMultiStepAction}
402
415
  onChange={setMultistepAction}
403
416
  menuPortalTarget={document.body}
404
- styles={{
405
- menuPortal: (base) => ({ ...base, zIndex: 19999 }),
406
- }}
417
+ styles={reactSelectStyles()}
407
418
  ></Select>
408
419
  )}
409
420
  {options.mode !== "page" ? (
410
421
  <Fragment>
411
- <label>Only if... (formula)</label>
422
+ <label>{t("Only if... (formula)")}</label>
412
423
  <input
413
424
  type="text"
414
425
  className="form-control text-to-display"
@@ -427,7 +438,7 @@ const ActionSettings = () => {
427
438
  ) : null}
428
439
  {stepCfgFields ? (
429
440
  <Fragment>
430
- Step configuration:
441
+ {t("Step configuration:")}
431
442
  <ConfigForm
432
443
  fields={stepCfgFields}
433
444
  configuration={
@@ -446,16 +457,120 @@ const ActionSettings = () => {
446
457
  ) : null}
447
458
  </Fragment>
448
459
  ) : cfgFields ? (
449
- <ConfigForm
450
- fields={cfgFields}
451
- configuration={configuration}
452
- setProp={setProp}
453
- node={node}
454
- />
460
+ <Fragment>
461
+ {name === "run_js_code" && runJsCodeModalOnly ? (
462
+ <div className="builder-config-field" data-field-name="code">
463
+ <label>{t("Code")}</label>
464
+ <button
465
+ type="button"
466
+ className="btn btn-secondary btn-sm"
467
+ onClick={() => setCodeModalOpen(true)}
468
+ >
469
+ {t("Open Code Popup")}
470
+ </button>
471
+ </div>
472
+ ) : null}
473
+ {name === "run_js_code" && !runJsCodeModalOnly ? (
474
+ <Fragment>
475
+ <ConfigForm
476
+ fields={(cfgFields || []).filter((f) => f.name === "code")}
477
+ configuration={configuration}
478
+ setProp={setProp}
479
+ node={node}
480
+ openPopup={() => setCodeModalOpen(true)}
481
+ />
482
+ {/* <div className="builder-config-field mt-2" data-field-name="code-modal-trigger">
483
+ <button
484
+ type="button"
485
+ className="btn btn-secondary btn-sm"
486
+ onClick={() => setCodeModalOpen(true)}
487
+ >
488
+ {t("Open Code Popup")}
489
+ </button>
490
+ </div> */}
491
+ <ConfigForm
492
+ fields={cfgFieldsForForm}
493
+ configuration={configuration}
494
+ setProp={setProp}
495
+ node={node}
496
+ />
497
+ </Fragment>
498
+ ) : (
499
+ <ConfigForm
500
+ fields={runJsCodeModalOnly ? cfgFieldsForForm : cfgFields}
501
+ configuration={configuration}
502
+ setProp={setProp}
503
+ node={node}
504
+ />
505
+ )}
506
+ {name === "run_js_code" && codeModalOpen ? (
507
+ <div
508
+ className={`modal fade ${codeModalOpen ? "show" : ""}`}
509
+ style={{
510
+ display: codeModalOpen ? "block" : "none",
511
+ zIndex: 1055,
512
+ }}
513
+ tabIndex={-1}
514
+ role="dialog"
515
+ aria-labelledby="codeModalLabel"
516
+ aria-hidden={!codeModalOpen}
517
+ >
518
+ <div
519
+ className="modal-backdrop fade show"
520
+ style={{ zIndex: 1050 }}
521
+ onClick={() => setCodeModalOpen(false)}
522
+ aria-hidden="true"
523
+ />
524
+ <div
525
+ className="modal-dialog modal-dialog-centered modal-lg"
526
+ role="document"
527
+ style={{ zIndex: 1060 }}
528
+ onClick={(e) => e.stopPropagation()}
529
+ >
530
+ <div className="modal-content code-modal">
531
+ <div className="modal-header">
532
+ <h5 className="modal-title" id="codeModalLabel">
533
+ {t("Code")}
534
+ </h5>
535
+ <button
536
+ type="button"
537
+ className="btn-close"
538
+ aria-label="Close"
539
+ onClick={() => setCodeModalOpen(false)}
540
+ />
541
+ </div>
542
+ <div className="modal-body">
543
+ <MultiLineCodeEditor
544
+ setProp={setProp}
545
+ value={configuration?.code ?? ""}
546
+ onChange={(code) =>
547
+ setProp((prop) => {
548
+ if (!prop.configuration)
549
+ prop.configuration = {};
550
+ prop.configuration.code = code;
551
+ })
552
+ }
553
+ isModalEditor
554
+ />
555
+ </div>
556
+ <div className="modal-footer">
557
+ <button
558
+ type="button"
559
+ className="btn btn-secondary"
560
+ onClick={() => setCodeModalOpen(false)}
561
+ >
562
+ {t("Close")}
563
+ </button>
564
+ </div>
565
+ </div>
566
+ </div>
567
+ </div>
568
+ ) : null}
569
+ </Fragment>
455
570
  ) : null}
456
571
  {cfg_link ? (
457
572
  <a className="d-block mt-2" target="_blank" href={cfg_link}>
458
- Configure this action
573
+ {t("Configure this action")}
459
574
  </a>
460
575
  ) : null}
461
576
  </div>
@@ -3,14 +3,10 @@
3
3
  * @module components/elements/Aggregation
4
4
  * @subcategory components / elements
5
5
  */
6
+ /* globals validate_expression_elem */
6
7
 
7
- import React, {
8
- useContext,
9
- useState,
10
- useRef,
11
- useEffect,
12
- Fragment,
13
- } from "react";
8
+ import React, { Fragment, useState, useContext, useEffect, useRef } from "react";
9
+ import useTranslation from "../../hooks/useTranslation";
14
10
  import { useNode } from "@craftjs/core";
15
11
  import optionsCtx from "../context";
16
12
  import {
@@ -59,6 +55,7 @@ export /**
59
55
  * @namespace
60
56
  */
61
57
  const AggregationSettings = () => {
58
+ const { t } = useTranslation();
62
59
  const {
63
60
  actions: { setProp },
64
61
  agg_relation,
@@ -132,7 +129,7 @@ const AggregationSettings = () => {
132
129
  {options.mode === "filter" ? null : (
133
130
  <tr>
134
131
  <td>
135
- <label>Relation</label>
132
+ <label>{t("Relation")}</label>
136
133
  </td>
137
134
  <td>
138
135
  <select
@@ -160,7 +157,7 @@ const AggregationSettings = () => {
160
157
  <tr>
161
158
  <td>
162
159
  <label>
163
- {options.mode === "filter" ? "Field" : "Child table field"}
160
+ {options.mode === "filter" ? t("Field") : t("Child table field")}
164
161
  </label>
165
162
  </td>
166
163
  <td>
@@ -179,7 +176,7 @@ const AggregationSettings = () => {
179
176
  </tr>
180
177
  <tr>
181
178
  <td>
182
- <label>Statistic</label>
179
+ <label>{t("Statistic")}</label>
183
180
  </td>
184
181
  <td>
185
182
  <select
@@ -190,34 +187,34 @@ const AggregationSettings = () => {
190
187
  >
191
188
  {buildOptions(
192
189
  [
193
- "Count",
194
- "CountUnique",
195
- "Avg",
196
- "Sum",
197
- "Max",
198
- "Min",
199
- "Array_Agg",
190
+ t("Count"),
191
+ t("CountUnique"),
192
+ t("Avg"),
193
+ t("Sum"),
194
+ t("Max"),
195
+ t("Min"),
196
+ t("Array_Agg"),
200
197
  ],
201
198
  { valAttr: true }
202
199
  )}
203
200
  {targetFieldType === "Bool" ? (
204
- <option value={`Percent true`}>Percent true</option>
201
+ <option value={`Percent true`}>{t("Percent true")}</option>
205
202
  ) : null}
206
203
  {targetFieldType === "Bool" ? (
207
- <option value={`Percent false`}>Percent false</option>
204
+ <option value={`Percent false`}>{t("Percent false")}</option>
208
205
  ) : null}
209
206
  {(options.agg_field_opts[agg_relation] || [])
210
207
  .filter((f) => f.ftype === "Date")
211
208
  .map((f, ix) => (
212
209
  <option key={ix} value={`Latest ${f.name}`}>
213
- Latest {f.name}
210
+ {t("Latest")} {f.name}
214
211
  </option>
215
212
  ))}
216
213
  {(options.agg_field_opts[agg_relation] || [])
217
214
  .filter((f) => f.ftype === "Date")
218
215
  .map((f, ix) => (
219
216
  <option key={ix} value={`Earliest ${f.name}`}>
220
- Earliest {f.name}
217
+ {t("Earliest")} {f.name}
221
218
  </option>
222
219
  ))}
223
220
  </select>
@@ -226,7 +223,7 @@ const AggregationSettings = () => {
226
223
  <tr>
227
224
  <td>
228
225
  <label>
229
- Where
226
+ {t("Where")}
230
227
  <HelpTopicLink
231
228
  topic="Aggregation where formula"
232
229
  table_name={options.tableName}
@@ -251,7 +248,7 @@ const AggregationSettings = () => {
251
248
  {fvs && (
252
249
  <tr>
253
250
  <td>
254
- <label>Field view</label>
251
+ <label>{t("Field view")}</label>
255
252
  </td>
256
253
 
257
254
  <td>
@@ -5,6 +5,7 @@
5
5
  */
6
6
  /* globals $, _sc_globalCsrf*/
7
7
  import React, { Fragment, useState, useEffect } from "react";
8
+ import useTranslation from "../../hooks/useTranslation";
8
9
  import optionsCtx from "../context";
9
10
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
10
11
  import {
@@ -55,7 +56,10 @@ export const ArrayManager = ({
55
56
  managedArrays,
56
57
  manageContents,
57
58
  initialAddProps,
59
+ contentsKey = "contents",
60
+ onLayoutChange,
58
61
  }) => {
62
+ const { t } = useTranslation();
59
63
  const { actions, query, connectors } = useEditor((state, query) => {
60
64
  return {};
61
65
  });
@@ -75,13 +79,14 @@ export const ArrayManager = ({
75
79
  node.id,
76
80
  options
77
81
  );
78
- layout.contents.splice(rmIx, 1);
82
+ layout[contentsKey].splice(rmIx, 1);
79
83
 
80
84
  managedArrays.forEach((arrNm) => {
81
- layout[arrNm].splice(rmIx, 1);
85
+ if (layout[arrNm]) layout[arrNm].splice(rmIx, 1);
82
86
  });
83
87
  layout[countProp] = node[countProp] - 1;
84
- layout[currentProp] = node[currentProp] - 1;
88
+ layout[currentProp] = Math.max(0, node[currentProp] - 1);
89
+ if (onLayoutChange) onLayoutChange(layout, "delete");
85
90
  actions.delete(node.id);
86
91
  layoutToNodes(layout, query, actions, parentId, options, sibIx);
87
92
  } else {
@@ -117,7 +122,7 @@ export const ArrayManager = ({
117
122
  options
118
123
  );
119
124
 
120
- swapElements(layout.contents, curIx, curIx + delta);
125
+ swapElements(layout[contentsKey], curIx, curIx + delta);
121
126
 
122
127
  managedArrays.forEach((arrNm) => {
123
128
  if (arrNm.includes(".")) {
@@ -128,6 +133,7 @@ export const ArrayManager = ({
128
133
  swapElements(layout[arrNm], curIx, curIx + delta);
129
134
  });
130
135
  layout[currentProp] = node[currentProp] + delta;
136
+ if (onLayoutChange) onLayoutChange(layout, "move");
131
137
  actions.delete(node.id);
132
138
  layoutToNodes(layout, query, actions, parentId, options, sibIx);
133
139
  } else
@@ -154,7 +160,7 @@ export const ArrayManager = ({
154
160
  options
155
161
  );
156
162
 
157
- layout.contents.push(null);
163
+ layout[contentsKey].push(null);
158
164
  managedArrays.forEach((arrNm) => {
159
165
  if (initialAddProps?.[arrNm])
160
166
  layout[arrNm][node[countProp]] = initialAddProps?.[arrNm];
@@ -162,6 +168,7 @@ export const ArrayManager = ({
162
168
  layout[currentProp] = +node[countProp];
163
169
  layout[countProp] = +node[countProp] + 1;
164
170
 
171
+ if (onLayoutChange) onLayoutChange(layout, "add");
165
172
  actions.delete(node.id);
166
173
  layoutToNodes(layout, query, actions, parentId, options, sibIx);
167
174
  } else
@@ -187,7 +194,7 @@ export const ArrayManager = ({
187
194
  <ConfigField
188
195
  field={{
189
196
  name: currentProp,
190
- label: "Number of things",
197
+ label: t("Number of things"),
191
198
  type: "btn_select",
192
199
  options: ntimes(node[countProp], (i) => ({
193
200
  value: i,
@@ -201,7 +208,7 @@ export const ArrayManager = ({
201
208
  ></ConfigField>
202
209
  <div className="btn-group w-100" role="group">
203
210
  <button
204
- title="Move left"
211
+ title={t("Move left")}
205
212
  type="button"
206
213
  style={{ width: "25%" }}
207
214
  className="btn btn-outline-secondary btn-sm"
@@ -211,7 +218,7 @@ export const ArrayManager = ({
211
218
  <FontAwesomeIcon icon={faAngleDoubleLeft} />
212
219
  </button>
213
220
  <button
214
- title="Add"
221
+ title={t("Add")}
215
222
  type="button"
216
223
  style={{ width: "25%" }}
217
224
  className="btn btn-outline-secondary btn-sm"
@@ -220,7 +227,7 @@ export const ArrayManager = ({
220
227
  <FontAwesomeIcon icon={faPlus} />
221
228
  </button>
222
229
  <button
223
- title="Delete"
230
+ title={t("Delete")}
224
231
  type="button"
225
232
  style={{ width: "25%" }}
226
233
  className="btn btn-outline-secondary btn-sm"
@@ -229,7 +236,7 @@ export const ArrayManager = ({
229
236
  <FontAwesomeIcon icon={faTrashAlt} />
230
237
  </button>
231
238
  <button
232
- title="Move right"
239
+ title={t("Move right")}
233
240
  type="button"
234
241
  disabled={node[currentProp] === node[countProp] - 1}
235
242
  style={{ width: "25%" }}